Spring - Autowire fails when adding an aspect-backed annotation to overridden method - spring

I have an authentication service implementing org.springframework.security.core.userdetails.UserDetailsService and defined as:
#Service
public class AuthService implements UserDetailsService {
// Autowires ..
#Override
//#Logged(successMessage = "User %s logged in")
public UserDetails loadUserByUsername(String username) { .. }
}
It is autowired into an extension of WebSecurityConfigurerAdapter and used to configure authentication:
// Annotations ...
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthService authService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService(authService).passwordEncoder(encoder);
}
// Other security config stuff ...
}
And last, I have created a custom annotation called #Logged, that is picked up by an aspect that applies around advice to the annotated method and performs some logging logic. Lets just say that if you pass only one argument to it, it will log successful returns.
Applying the #Logged annotation to the overriden AuthService.loadUserByUsername method causes the application to crash on startup, displaying the message about not being able to autowire AuthService into the SecurityConfig. Everything works flawlessly if I don't put the annotation to that overriden method - authentication works (complete with autowiring) and logging annotation works in other parts of the system.
Question
Why is this happening and can it be fixed?
I would really like to log that exact method using my fancy logging anotation.
Thanks :)
Technical details
Spring 4 with Boot, Jetty 9
Stack trace (part of):
2014-09-22 12:41:05 WARN AbstractLifeCycle - FAILED org.springframework.boot.context.embedded.jetty.ServletContextInitializerConfiguration$InitializerListener#8bb473: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.setFilterChainProxySecurityConfigurer(org.springframework.security.config.annotation.ObjectPostProcessor,java.util.List) throws java.lang.Exception; nested exception is org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityConfig': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.bluepixelflag.services.AuthService com.bluepixelflag.config.SecurityConfig.authService; nested exception is java.lang.IllegalArgumentException: Can not set com.bluepixelflag.services.AuthService field com.bluepixelflag.config.SecurityConfig.authService to com.sun.proxy.$Proxy90

Spring is using a proxy to add aspects to AuthService. Without the annotation it would just use a simple class.
Change
private AuthService authService;
to
private UserDetailsService authService;
and it ought to work. Or, use cglib proxies instead of JDK dynamic proxies.
Aside: the point of using aspects for logging is to try to avoid one line of code per logging call. If you use an aspect annotation for every method call you want logged, you haven't saved any complexity (in fact you've increased it), and might as well just call a logger in code.

Related

Why do spring tests do not reuse the same Context with Embedded Mongo

I am doing integration tests and unit tests with spring boot. I made three test classes.
ApplicationRunnterTest: to test contextLoads
DecisionTreeServiceTest: to test the save function of the service
DecisionTreeRepositoryTest: to test the methods of the repo
I added the embdded mongo packages (flapdoodle.embed.mongo). What i have observed is that, when all three tests are run in this order 1) => 2) => 3)
What happens is when ApplicationRunnerTest loads the context then the second test fails (the service) and the third is success (repository). The problem is with the context being used or something in the test that fails it says "Error to create bean embedded mongo"
The solution i found is to add the annotation #DirtiesContext() and from what i understand is for each test class, it will make a new context and it worked.
My questions are the following :
Why doesnt spring use the same context for all the classes
if it does, why there is an ambiguity or an error ?
is there a better way instead of #DirtiestContext() since i guess its a lot to load a new context for each test class.
Here are the classes and the erroes.
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration()
public class ApplicationRunnerTest {
#Test
public void contextLoads(){
}
}
Service class
#RunWith(SpringRunner.class)
#SpringBootTest
//#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class DecisionTreeServiceTest {
#Autowired
private DecisionTreeRepository decisionTreeRepository;
#MockBean
private HistorySupervisionService historySupervisionService;
#Autowired
private DecisionTreeService decisionTreeService;
#Autowired
private DecisionTreeMapper decisionTreeMapper;
private DecisionTree[] trees;
#Before
public void init() throws IOException {
// get test data
JacksonMapper jacksonMapper = new JacksonMapper();
trees = jacksonMapper.loadJsonFileToObject("src/test/java/com/almerys/cpm/back/utils/decisionTree/decisionTreeObject",
DecisionTree[].class
);
//clear databse first
this.decisionTreeRepository.deleteAll();
// save object 2 and object 3
this.decisionTreeRepository.save(trees[1]);
this.decisionTreeRepository.save(trees[2]);
}
#Test
public void saveTest(){
// TEST CREATE SCENARIO
DecisionTreeDTO decisionTree = this.decisionTreeMapper.toDto(trees[0]);
DecisionTree savedDecisionTree = trees[0];
savedDecisionTree.setId("1");
savedDecisionTree.setInternalId(4);
// Mockito.when(decisionTreeRepository.findFirstByOrderByInternalIdDesc()).thenReturn(Optional.of(trees[2]));
// Mockito.when(decisionTreeRepository.save(Mockito.any())).thenReturn(savedDecisionTree);
Mockito.when(historySupervisionService.getLastVersionByContext(Mockito.anyString())).thenReturn("13.0");
DecisionTreeDTO createdDecisionTree= decisionTreeService.save(decisionTree);
assertThat(createdDecisionTree).isNotNull();
assertThat(createdDecisionTree.getInternalId()).isEqualTo(4);
assertThat(createdDecisionTree.getFactoryVersion()).contains("13.1");
// TEST UPDATE SCENARIO
}
Repo Class
#RunWith(SpringRunner.class)
#DataMongoTest
public class DecisionTreeRepositoryTest{
#Autowired
private DecisionTreeRepository decisionTreeRepository;
private void initData() throws IOException {
//clean embedded database first.
this.decisionTreeRepository.deleteAll();
// retrieve data from json
JacksonMapper jacksonMapper = new JacksonMapper();
DecisionTree[] createDecisionTree= jacksonMapper.loadJsonFileToObject("src/test/java/com/almerys/cpm/back/utils/decisionTree/decisionTreeObject",
DecisionTree[].class
);
// persist data
Arrays.stream(createDecisionTree).forEach(tree-> this.decisionTreeRepository.save(tree));
}
#Before
public void init() throws IOException {
this.initData();
}
#Test
public void findByFactoryCategoryInternalId(){
DecisionTree decisionTree = this.decisionTreeRepository.findByFactoryCategoryInternalIdAndFactoryVersion(1,"11.0");
assertThat(decisionTree).isNotNull();
}
}
Error
Unsatisfied dependency expressed through field 'mongoTemplate'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mongoTemplate' defined in class path resource [org/springframework/boot/autoconfigure/data/mongo/MongoDbFactoryDependentConfiguration.class]: Unsatisfied dependency expressed through method 'mongoTemplate' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoDbFactory' defined in class path resource [org/springframework/boot/autoconfigure/data/mongo/MongoDbFactoryConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.mongodb.core.MongoDbFactorySupport]: Factory method 'mongoDbFactory' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'embeddedMongoServer' defined in class path resource [org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.class]: Invocation of init method failed; nested exception is java.io.IOException: Could not start process: <EOF>
Why doesnt spring use the same context for all the classes
The simple answer is: because one of the test uses #MockBean.
When using mock bean, you're asking Spring to replace the original bean instance with a mock. Obviously, other beans might depend on that bean, it might be involved in their initialization etc., so any previously created context needs to be scrapped in order for the test environment to be reliable.
Also, do all the test reside in the same package? If not, I think the consequence of using #ContextConfiguration will be that only components residing in the same package and sub-packages are going to be picked up. This implies the test context for each of the tests will not be the same, so obviously the test context cannot be reused.
why there is an ambiguity or an error ?
Hard to say without looking at the actual configuration, but my guess would be the embedded Mongo doesn't cleanup after itself properly.

NUnit 5 Spring MVC test NoSuchBeanDefinitionException for Autowired dependency in submodule

I have a project with two submodules; one is the data access layer the other is the API service.
The data access module uses JOOQ and an autowired DSLContext in a service class. Also, I'm using JUnit 5, and Spring Boot 2.2.4.
The QueryService class in the data access module has a member like #Autowired private DSLContext dsl;
The test class is set up like this:
#SpringBootTest
public class MyServiceTests {
#Autowired
QueryService service;
#Autowired
private DSLContext dsl;
#Test
public void TestDoSomething() throws Exception {
service.selectBusinessEntityRelatedByBusinessEntity("C00001234", mockAuth);
}
}
The tests in this module run correctly. Configuration is read from the application.yaml, and autowire injects either real services or a mock into both my QueryService and the local dsl.
The API service is a different story. If I use the #SpringBootTest annotation with no MVC I can successfully get the tests to inject a local DSLContext with configuration from the application.yaml. Test set up similar to this:
#SpringBootTest
public class CustomersControllerTests {
#Autowired
private Gson gson;
#Autowired
DSLContext dsl;
#Test
public void addCustomerTest() {
}
What I need though is to use #WebMvcTest so that MockMvc is initialized but switching to #WebMvcTest causes injection to fail in the service class implemented in the data access module. The injection fails to find the DSLContext bean within the query service class. I set up the test like this:
#WebMvcTest
public class CustomersControllerTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private Gson gson;
private static final String testSub = "329e6764-3809-4e47-ac48-a52881045787";
#Test
public void addCustomerTest() {
var newCustomer = new Customer().firstName("John").lastName("Doe");
mockMvc.perform(post("/customers").content(gson.toJson(newCustomer)).contentType(MediaType.APPLICATION_JSON)
.with(jwt().jwt(jwt -> jwt.claim("sub", testSub)))).andExpect(status().isNotImplemented());
}
This is the actual error:
2020-02-25 18:14:33.655 WARN 10776 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customersController': Unsatisfied dependency expressed through field '_customersService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customersService': Unsatisfied dependency expressed through field '_queryService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'queryService': Unsatisfied dependency expressed through field '_dsl'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.jooq.DSLContext' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
So, I know the test application configuration is correct because it works when not using MVC annotation. Also, I can create a DSLContext in the API project tests and I can actually run the API service outside the test.
So, why cant the DSLContext be found when using the MVC test setup?
This might be because #WebMvcTest fully disables Spring Boot's Autoconfiguration and only scans in #Controllers and a few other select classes, that you need for your ..well...MVC tests..
The Spring documentation recommends doing this in your case:
If you are looking to load your full application configuration and use MockMVC, you should consider #SpringBootTest combined with #AutoConfigureMockMvc rather than this annotation.

Spring boot #SpyBean tries to instantiate a new bean instead of spying the one in context

I'm trying to unit-testing a Spring batch job inside Spring boot using JUnit.
I wrote this test class where I want to spy the bean ItemReader :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.NONE)
#ActiveProfiles({"dev", "batch", "test-jobs"})
public class BatchJobTest {
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
private #Autowired #Qualifier("contactDownloadAckJob") Job contactDownloadAckTaskJob;
#SpyBean
private ItemReader<CrsOscContact> reader;
#Test
public void testJob() throws Exception {
given(this.reader.read()).willReturn(new CrsOscContact());
//... blah blah blah
}
}
When I run this test, it seems that the #SpyBean annotation does not do its job, that should be proxying the ItemReader bean that's already present in the context, and so I obtain the (correct) exception because, as per definition, if the bean is not found it tries to instantiate a new bean of that type (and I have specified an interface) :
org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.ItemReader]: Specified class is an interface
I'm pretty sure that the bean (of ItemReader type) is already in the context because :
debugging, I see that the target bean instantiation is correctly processed
if I change the #SpyBean annotation to an #Autowired annotation, the instance of previous point is correctly injected
Any hint? Thank you
It was a Spring Core issue, now fixed.
https://github.com/spring-projects/spring-boot/issues/7625
https://jira.spring.io/browse/SPR-15011

Spring Test + Mockito.mock - Spring fails because it tries to load the mocked bean #Autowired dependencies

I can't find out why the following simple scenario is failing: I have a Spring application with a filter that loads a Spring bean from the application context:
public class MyFilter implements Filter{
private IPermissionService permissionService;
public void init(FilterConfig filterConfig) throws ServletException {
WebApplicationContext ac = null;
try{
ac = WebApplicationContextUtils.getRequiredWebApplicationContext(filterConfig.getServletContext());
permissionService = ac.getBean(PermissionServiceImpl.class);
PermissionServiceImpl has an #Autowired attribute dataSource so in my TestNG test, I mock it in the Spring applicationContext:
#Configuration
public class MyFilterSpringTestConfig{
#Bean
public IPermissionService permissionService(){
return Mockito.mock(PermissionServiceImpl.class);
}
MyTest:
#Test
#WebAppConfiguration
#ContextConfiguration(classes=MyFilterSpringTestConfig.class)
public class MyFilterSpringTest extends BaseSpringFilterTest{
...
The problem is that on Spring initialization I get an exception complaining that PermissionServiceImpl's dataSource dependency is not satisfied. Since I wrapped it with a mock, why is it still failing? How could I fix it?
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true), #org.springframework.beans.factory.annotation.Qualifier(value=myDataSource)}
When mocking a class using Mockito (or any other mocking framework) that class is still an instance of the original class. With that comes that it also contains all the annotations and class information with it.
So when you create a mock of the class it still detects all annotations on it and tries to full fill that. I.e. #Autowire other instances.
Either don't use auto wiring or don't mock the class but the interface (which doesn't contain that information).

How to create a Spring bean for apache logging Log class?

I'd like to create an autowired bean in a Dao class in order to do logging opperations. My way was hitherto static final statement like this:
private static final Log log = LogFactory.getLog(LoggedClass.class);
But now I'm trying to use IoC to turn classes decoupled.
If just add configuration in pom.xml and try to do sth like
#Autowired
Log log;
I receive an error message:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'funciDaoImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: org.apache.commons.logging.Log br.com.bb.dirco.dao.impl.FunciDaoImpl.log; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'log' defined in class path resource [com/company/project/util/PersistenceConfig.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [java.lang.Class]: : No qualifying bean of type [java.lang.Class] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.Class] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
In order to get a logger, I had to provide a class to getLog method on LogFactory class and attribute it to Log instance. There's a way to do it using #Autowired Spring IoC? Thanks!
You can inject only those objects which are managed/created by Spring container. You have to register your bean (or factory method creating the bean) with container (with annotations like #Component/#Singleton/... or directly in xml)
In your case it's not very applicable since you have to have many different types (for every class) of logger objects provided by Spring and then when you inject they would have to be identified by different name/type for every class.
P.S. I don't see any problem using it the way you use it now
Where I work we have implemented support for #Autowired SLF4J Loggers using Springs BeanPostProcessor.
First you need to define an Logger placeholder bean in your application context. This bean is going to be injected by Spring into all bean with a #Autowired Logger field.
#Configuration
public class LoggerConfig {
#Bean
public Logger placeHolderLogger() {
return PlaceHolder.LOGGER;
}
#Bean
public AutowiredLoggerBeanPostProcessor loggerPostProcessor() {
return new AutowiredLoggerBeanPostProcessor();
}
}
Then you an AutowiredLoggerBeanPostProcessor which inspects all beans, indetify bean that contain Logger fields annotated with #Autowired (at this point should contain a reference to the Logger placeholder bean), create a new Logger for the partilcar bean an assigned it to the fields.
#Component
public class AutowiredLoggerBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
attachLogger(bean);
return bean;
}
#Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
attachLogger(bean);
return bean;
}
private void attachLogger(final Object bean) {
ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
if (Logger.class.isAssignableFrom(field.getType()) &&
(field.isAnnotationPresent(Autowired.class) ||
field.isAnnotationPresent(Inject.class))) {
ReflectionUtils.makeAccessible(field);
if (field.get(bean) == PlaceHolder.LOGGER) {
field.set(bean, LoggerFactory.getLogger(bean.getClass()));
}
}
}
});
}
#Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}

Resources