Behind this issue, I want to understand and debug how Spring beans are instantiated (in which order) and how dependencies are resolved.
Context
I'm using:
spring boot
spring security
spring data JPA
I added to my DAO security annotations like:
#PreAuthorize("hasRole('ROLE_ADMIN') OR hasPermission(#entity, 'save')")
<S extends T> S save(S s);
And I have a custom security configuration:
#Configuration
#Slf4j
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) throws Exception {
log.info("Configuring Authentication...");
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
Obviously, autowired beans doesn't depends on DAO which have security annotations.
It works fine if I run main method (which use Spring Boot), no matter if it's compiled jar or in my IDE). But in some case (junit tests for example), I get this exception:
java.lang.IllegalStateException: Cannot apply org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer#362b384c to already built object
I assume method AuthenticationManagerBuilder.build() has been called before Spring create and configure my SecurityConfiguration. But why? Order of WebSecurityConfigurerAdapter is set to 100, it should be done before everything else!
Question
How can I see why Spring create other beans (DAO and services) before configuration? How can I check there is no dependency from my configuration service to my services / dao?
Thanks,
The #Autowired method is going to be called immediately after that configuration class is instantiated (which will be triggered by the security filter being created when the web container starts). That's pretty early, but not early enough for your use case, since you need to be the first to use the AuthenticationManagerBuilder. I recommend you use a GlobalAuthenticationConfigurerAdapter (i.e. a new bean) like in the Spring Boot samples (Spring Security will look for beans of that type even earlier).
Related
There are spring boot 2.0.2 configuration
#Configuration
public class ApiConfig {
#Bean
#Profile("!tests")
#ConditionalOnProperty(name = "enabled", havingValue = "true")
public MyService service() {
return new MyServiceImpl();
}
}
... and some controller which should be created and added to application context only if MyService bean is initialized.
#RestController
#ConditionalOnBean(MyService.class)
public class MyController {
#Autowired
private MyService service;
}
It works Ok. But occasionally spring boot skips MyController creating. According to the logs MyService is created, but after any other beans(including all controllers), at the end.
Why boot does not process #Configuration beans prior to #RestController?
Thanks.
Why boot does not process #Configuration beans prior to #Controller?
Thanks.
Because Spring doesn't guarantee that.
As well as #ConditionalOnBean warns about this kind of issue in this specification :
The condition can only match the bean definitions that have been
processed by the application context so far and, as such, it is
strongly recommended to use this condition on auto-configuration
classes only. If a candidate bean may be created by another
auto-configuration, make sure that the one using this condition runs
after.
And you don't use the annotation in an auto-configuration class. You indeed specified it in a class annotated with #RestController.
I think that to achieve your requirement you should move the #RestController bean declaration in a #Configuration class that's imported via an EnableAutoConfiguration entry in spring.factories.
Edit Fixed by changing package.
I have this configuration file for spring framework
#Configuration
public class AppConfig {
#Bean(initMethod = "populateCache")
public AccountRepository accountRepository(){
return new JdbcAccountRepository();
}
}
JdbcAccountRepository looks like this.
#Repository
public class JdbcAccountRepository implements AccountRepository {
#Override
public Account findByAccountId(long
return new SavingAccount();
}
public void populateCache() {
System.out.println("Populating Cache");
}
public void clearCache(){
System.out.println("Clearing Cache");
}
}
I'm new to spring framework and trying to use initMethod or destroyMethod. Both of these method are showing following errors.
Caused by: org.springframework.beans.factory.support.BeanDefinitionValidationException: Could not find an init method named 'populateCache' on bean with name 'accountRepository'
Here is my main method.
public class BeanLifeCycleDemo {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new
AnnotationConfigApplicationContext(AppConfig.class);
AccountRepository bean = applicationContext.getBean(AccountRepository.class);
applicationContext.close();
}
}
Edit
I was practicing from a book and had created many packages for different chapters. Error was it was importing different JdbcAccountRepository from different package that did not have that method. I fixed it and it works now. I got hinted at this from answers.
Like you said, if you are mixing configurations types, it can be confusing. Besides, even if you created a Bean of type AccountRepository, because Spring does a lot of things at runtime, it can call your initMethod, even if the compiler couldn't.
So yes, if you have many beans with the same type, Spring can be confused an know which one to call, hence your exception.
Oh and by the way, having a Configuration creating the accountRepoisitory Bean, you can remove the #Repository from your JdbcAccountRepository... It is either #Configuration + #Bean or #Component/Repository/Service + #ComponentScan.
TL;DR
Here is more information and how Spring creates your bean : What object are injected by Spring ?
#Bean(initMethod = "populateCache")
public AccountRepository accountRepository(){
return new JdbcAccountRepository();
}
With this code, Spring will :
Detect that you want to add a Bean in the application Context
The bean information are retrieved from the method signature. In your case, it will create a bean of type AccountRepository named accountRepository... That's all Spring knows, it won't look inside your method body.
Once Spring is done analysing your classpath, or scanning the bean definitions, it will start instanciating your object.
It will therefor creates your bean accountRepository of type AccountRepository.
But Spring is "clever" and nice with us. Even if you couldn't write this code without your compiler yelling at you, Spring can still call your method.
To make sure, try writing this code :
AccountRepository accountRepository = new JdbcAccountRepository();
accountRepository.populateCache(); // Compiler error => the method is not found.
But it works for Spring... Magic.
My recommandation, but you might thinking the same now: If you have classes across many packages to answer different business case, then rely on #Configuration classes. #ComponentScan is great to kickstart your development, but reach its limit when your application grows...
You mix two different ways of spring bean declaration:
Using #Configuration classes. Spring finds all beans annotated with #Configuration and uses them as a reference to what beans should be created.
So if you follow this path of configuration - don't use #Repository on beans. Spring will detect it anyway
Using #Repository - other way around - you don't need to use #Configuration in this case. If you decide to use #Repository put #PostConstruct annotation on the method and spring will call it, in this case remove #Configuration altogether (or at least remove #Bean method that creates JdbcAccountRepository)
Annotate populateCache method with #PostConstruct and remove initMethod from #Bean. It will work.
I am relatively new to Spring in general but have read through the Apress Spring Rest text and gotten the examples running in Eclipse without problem.
Something that puzzles me in the examples is how objects appear to be automatically injected.
For example in chapter 8 security there is a QuickPollUserDetailsService class which implements the spring UserDetailsService class.
The text says the following:
"The SecurityConfig class declares a userDetailsService property, which gets injected with a QuickPollUserDetailsService instance at runtime."
#Configuration
#EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Inject
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService) .passwordEncoder(new BCryptPasswordEncoder());
}}
Nowhere in any file does it specify QuickPollUserDetailsService is to be set.
Is Spring being smart here and noticing that QuickPollUserDetailsService is the only implementer of UserDetailsService and therefore assuming that it must be injected?
If that is the case what if I had 2 or more implementer's of UserDetailsService
By default, Spring Boot will recursively scan the packages and available implementation will be automatically injected. If there is more than one implementation available, startup will fail.
I've been tearing my hair out with what should be a pretty common use case for a web application. I have a Spring-Boot application which uses REST Repositories, JPA, etc. The problem is that I have two data sources:
Embedded H2 data source containing user authentication information
MySQL data source for actual data which is specific to the authenticated user
Because the second data source is specific to the authenticated user, I'm attempting to use AbstractRoutingDataSource to route to the correct data source according to Principal user after authentication.
What's absolutely driving me crazy is that Spring-Boot is fighting me tooth and nail to instantiate this data source at startup. I've tried everything I can think of, including Lazy and Scope annotations. If I use Session scope, the application throws an error about no session existing at startup. #Lazy doesn't appear to help at all. No matter what annotations I use, the database is instantiated at startup by Spring Boot and doesn't find any lookup key which essentially crashes the entire application.
The other problem is that the Rest Repository API has IMO a terrible means of specifying the actual data source to be used. If you have multiple data sources with Spring Boot, you have to juggle Qualifier annotations which is a runtime debugging nightmare.
Any advice would be very much appreciated.
Your problem is with the authentication manager configuration. All the samples and guides set this up in a GlobalAuthenticationConfigurerAdapter, e.g. it would look like this as an inner class of your SimpleEmbeddedSecurityConfiguration:
#Configuration
public static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter
{
#Bean(name = Global.AUTHENTICATION_DATA_QUALIFIER + "DataSource")
public DataSource dataSource()
{
return new EmbeddedDatabaseBuilder().setName("authdb").setType(EmbeddedDatabaseType.H2).addScripts("security/schema.sql", "security/data.sql").build();
}
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception
{
auth.jdbcAuthentication().dataSource(dataSource()).passwordEncoder(passwordEncoder());
}
}
If you don't use GlobalAuthenticationConfigurerAdapter then the DataSource gets picked up by Spring Data REST during the creation of the Security filters (before the #Primary DataSource bean has even been registered) and the whole JPA initialization starts super early (bad idea).
UPDATE: the authentication manager is not the only problem. If you need to have a session-scoped #Primary DataSource (pretty unusual I'd say), you need to switch off everything that wants access to the database on startup (Hibernate and Spring Boot in various places). Example:
spring.datasource.initialize: false
spring.jpa.hibernate.ddlAuto: none
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults: false
spring.jpa.properties.hibernate.dialect: H2
FURTHER UPDATE: if you're using the Actuator it also wants to use the primary data source on startup for a health indicator. You can override that by prividing a bean of the same type, e.g.
#Bean
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#Lazy
public DataSourcePublicMetrics dataSourcePublicMetrics() {
return new DataSourcePublicMetrics();
}
P.S. I believe the GlobalAuthenticationConfigurerAdapter might not be necessary in Spring Boot 1.2.2, but it is in 1.2.1 or 1.1.10.
I'm trying to understand where is the right place to put #EnableTransactionManagement annotation in case of multiple JavaConfig contexts?
Consider following scenario: I have JPA config in JPAConfig.java and AppConfig.java with set of service beans. Then I compose overall application config in RootConfig.java.
I define transaction manager within JPAConfig.java as well as enable scanning for JPA repositories - as those expose transactional behavior, I put #EnableTransactionManagement over JPAConfig and it works.
However, some of service beans also need to have transactional methods e.g. accessing several repositories within single transaction. Should I also put #EnableTransactionManagement over AppConfig as well? Looking into implementation of this annotation is seems to me that such approach would cause redefinition of some beans. And actually doing so doesn't seem to work for me.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("com.mypackage.repositories")
public class JPAConfig {
// ... here are EntityManager and PlatformTransactionManager beans
}
#Configuration
#ComponentScan("com.mypackage.services")
// #EnableTransactionManagement // - ???
public class AppConfig {
}
#Configuration
#Import({AppConfig.class, JPAConfig.class})
public class RootConfig {
}
Appreciate any advices.
After some experiments I seem to have found the answer myself:
There is no need to configure #EnableTransactionManagement on each
piece of context configuration although it does matter how early this
annotation is discovered as it registers internalTransactionAdvisor
which actually processes #Transactional annotations on created beans.
In my case, I changed the order of contexts in #Import declaration so
that PersistenceConfig that holds #EnableTransactionManagement is the
first. After this beans from other pieces can use AOP declarative
transaction.
Another caveat relates to simultaneous use of #EnableTransactionManagement and #EnableGlobalMethodSecurity. Global method security uses bean post processing which seems to require whole security configuration to be wired. BeanPostProcessors are created early on context start-up so you can't use declarative #Transactional in any bean that would be needed to bootstrap spring security (in my case UserDetailsContextMapper) - advisor is not yet created then!