问题
I am stuck with null values in an autowired property. I am hoping I could get some help.
We are using for the project spring-boot version 0.5.0.M6.
The four configuration files with beans are in one package and are sorted by "area":
- Data source configuration
- Global method security configuration (as we use Spring-ACL)
- MVC configuration
- Spring Security configuration
The main method that bootstraps everything is in the following file:
@EnableAspectJAutoProxy
@EnableSpringConfigured
@EnableAutoConfiguration(exclude = {
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
SecurityAutoConfiguration.class,
ThymeleafAutoConfiguration.class,
ErrorMvcAutoConfiguration.class,
MessageSourceAutoConfiguration.class,
WebSocketAutoConfiguration.class
})
@Configuration
@ComponentScan
public class IntegrationsImcApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = SpringApplication.run(
IntegrationsImcApplication.c lass, args);
}
}
The first file that holds the data source configuration beans is as follows (I have omitted some method body parts to make it more readable):
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@Configuration
public class RootDataSourceConfig
extends TomcatDataSourceConfiguration
implements TransactionManagementConfigurer {
@Override
public DataSource dataSource() {
return jpaDataSource();
}
public PlatformTransactionManager annotationDrivenTransactionManager() {
return jpaTransactionManager();
}
@Bean
public HibernateExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
@Bean(name="jpaDataSource")
public DataSource jpaDataSource() {......}
@Bean(name = {"transactionManager","txMgr"})
public JpaTransactionManager jpaTransactionManager() {......}
@Bean(name = "entityManagerFactory")
public EntityManagerFactory jpaEmf() {......}
}
And here is the next configuration file, that depends on the data source from above. It has about 20 beans related to ACL configuration, but it fails on the firsts bean that uses data source:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class RootGlobalMethodSecurityConfig
extends GlobalMethodSecurityConfiguration
implements Ordered {
@Autowired
public DataSource dataSource;
@Override
public int getOrder() {
return IntegrationsImcApplication.ROOT_METHOD_SECURITY_CO NFIG_ORDER;
}
@Bean
public MutableAclService aclService()
throws CacheException, IOException {
MutableJdbcAclService aclService = new MutableJdbcAclService(
dataSource, aclLookupStrategy(), aclCache());
aclService.setClassIdentityQuery("SELECT @@IDENTITY");
aclService.setSidIdentityQuery("SELECT @@IDENTITY");
return aclService;
}
...................................
}
Basically invoking aclService()
throws an error as dataSource
is null. We have tried ordering the configuration files by implementing the Ordered
interface. We also tried using @AutoConfigureAfter(RootDataSourceConfig.class)
but this did not help either. Instead of doing @Autowired
on the DataSource
we also tried injecting the RootDataSourceConfig
class itself, but it was still null. We tried using @DependsOn
and @Ordered
on those beans but again no success. It seems like nothing can be injected into this configuration.
The console output at the startup is listing the beans in the order we want them, with data source being the first. We are pretty much blocked by this.
Is there anything weird or unique we are doing here that is not working? If this is as designed, then how could we inject data source differently?
Repo: github
回答1:
Eager initialization of a bean that depends on a DataSource
is definitely the problem. The root cause is nothing to do with Spring Boot or autoconfiguration, but rather plain old-fashioned chicken and egg - method security is applied via an aspect which is wrapped around your business beans by a BeanPostProcessor
. A bean can only be post processed by something that is initialized very early. In this case it is too early to have the DataSource
injected (actually the @Configuration
class that needs the DataSource
is instantiated too early to be wrapped properly in the @Configuration
processing machinery, so it cannot be autowired). My proposal (which only gets you to the same point with the missing AuthenticationManager
) is to declare the GlobalMethodSecurityConfiguration
as a nested class instead of the one that the DataSource
is needed in:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
protected static class ActualMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Autowired
@Qualifier("aclDaoAuthenticationProvider")
private AuthenticationProvider aclDaoAuthenticationProvider;
@Autowired
@Qualifier("aclAnonymousAuthenticationProvider")
private AnonymousAuthenticationProvider aclAnonymousAuthenticationProvider;
@Autowired
@Qualifier("aclExpressionHandler")
private MethodSecurityExpressionHandler aclExpressionHandler;
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(aclDaoAuthenticationProvider);
auth.authenticationProvider(aclAnonymousAuthenticationProvider);
}
@Override
public MethodSecurityExpressionHandler createExpressionHandler() {
return aclExpressionHandler;
}
}
i.e. stick that inside the RootMethodSecurityConfiguration
and remove the @EnableGlobalMethodSecurity
annotation from that class.
回答2:
I might have resolved the problem.
GlobalMethodSecurityConfiguration.class
has the following setter that tries to autowire permission evaluators:
@Autowired(required = false)
public void setPermissionEvaluator(List<PermissionEvaluator> permissionEvaluators) {
....
}
And in my case the aclPermissionEvaluator()
bean needs aclService()
bean, which in turn depends on another autowired property: dataSource
. Which seems not to be autowired yet.
To fix this I implemented BeanFactoryAware
and get dataSource
from beanFactory
instead:
public class RootMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration implements BeanFactoryAware {
private DataSource dataSource;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.dataSource = beanFactory.getBean("dataSource", DataSource.class);
}
....
}
After this, other exception showed up, whereSecurityAutoConfiguration.class
is complaining about missing AuthenticationManager, so I just excluded it from @EnableAutoConfiguration
. I am not sure if its ideal, but I have custom security configuration, so this way everything works ok.
来源:https://stackoverflow.com/questions/20856825/autowired-property-is-null-spring-boot-configuration