Spring3.1完全基於註解配置@Configuration類中@Autowire無法注入問題解決
在上回介紹Spring3.1+Hibernate4.1.7基於註解配置的時候(《SpringMVC3.1+Hibernate4.1.7完全基於註解配置(零配置檔案)》)說過,在修改配置方式的時候遇到過不少問題。這裡介紹一下。
方式一 OneCoder考慮在DataSourceConfig中,僅保留資料來源配置,其他的諸如SessiionFactory的配置都移到AppConfig中。程式碼如下:/**
* 資料來源配置類
*
* @author lihzh
* @alia OneCoder
* @blog http://www.coderli.com
*/
@Configuration
@PropertySource("/conf/jdbc.properties")
public class DataSourceConfig {
@Value("${jdbc.driverClass}") String driverClass;
@Value("${jdbc.url}") String url;
@Value("${jdbc.user}") String user;
@Value("${jdbc.password}") String password;
@Bean(autowire=Autowire.BY_TYPE)
public DataSource dataSource () throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driverClass);
dataSource.setJdbcUrl(url);
dataSource.setUser(user);
dataSource.setPassword(password);
return dataSource;
}
}
/**
* Spring3.1基於註解的配置類, 用於代替原來的<b>applicationContext.xml</b>配置檔案
*
* @author lihzh
* @date 2012-10-12 下午4:23:13
*/
@ComponentScan(basePackages = "com.coderli.shurnim.*.biz")
@Import(DataSourceConfig.class)
@Configuration
@EnableTransactionManagement
public class DefaultAppConfig {
@Autowired
DataSourceConfig config;
@Bean
public PropertySourcesPlaceholderConfigurer placehodlerConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
public LocalSessionFactoryBean sessionFactory()
throws PropertyVetoException {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(config.dataSource());
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect",
"org.hibernate.dialect.MySQLDialect");
sessionFactoryBean.setHibernateProperties(hibernateProperties);
sessionFactoryBean.setPackagesToScan("com.coderli.shurnim.*.model");
return sessionFactoryBean;
}
@Bean
public HibernateTransactionManager txManager() throws PropertyVetoException {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory().getObject());
return txManager;
}
}
這裡,這樣配置的依據是參考Spring中@Configuration註解的註釋說明:
With the @Import annotation
@Configuration classes may be composed using the @Import annotation, not unlike the way that works in Spring XML. Because @Configuration objects are managed as Spring beans within the container, imported configurations may be injected using @Autowired or @Inject:</p>
@Configuration public class DatabaseConfig { @Bean public DataSource dataSource() { // instantiate, configure and return DataSource } }
@Configuration @Import(DatabaseConfig.class) public class AppConfig { @Inject DatabaseConfig dataConfig;
@Bean public MyBean myBean() { // reference the dataSource() bean method return new MyBean(dataConfig.dataSource()); } }
Now both AppConfig and the imported DatabaseConfig can be bootstrapped by registering only AppConfig against the Spring context: new AnnotationConfigApplicationContext(AppConfig.class);
啟動Tomcat,報錯:
空指標異常,顯然是DataSourceConfig沒有注入進來,更換@Inject註解,問題依舊。
方式二既然注入DataSourceCOnfig無效,OneCoder考慮,可否直接注入DataSource Bean。修改程式碼如下:
/**
* Spring3.1基於註解的配置類, 用於代替原來的<b>applicationContext.xml</b>配置檔案
*
* @author lihzh
* @date 2012-10-12 下午4:23:13
*/
@ComponentScan(basePackages = "com.coderli.shurnim.config")
@Import(DataSourceConfig.class)
@Configuration
@EnableTransactionManagement
public class DefaultAppConfig {
@Autowired
DataSource dataSource;
@Bean
public PropertySourcesPlaceholderConfigurer placehodlerConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
public LocalSessionFactoryBean sessionFactory()
throws PropertyVetoException {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect",
"org.hibernate.dialect.MySQLDialect");
sessionFactoryBean.setHibernateProperties(hibernateProperties);
sessionFactoryBean.setPackagesToScan("com.coderli.shurnim.*.model");
return sessionFactoryBean;
}
啟動Tomcat,報錯:
OneCoder在各個Bean的方法上加斷點除錯,發現dataSource也是null,看來還是沒注入進來導致的。也許這兩個場景是一個問題,先解決沒注入的問題看看吧。
說實話,這個問題困擾了OneCoder近一天,搜尋也沒什麼好的頭緒,搜出來的東西差距都比較大。OneCoder只能一遍一遍閱讀自己的程式碼分析可能的原因同時仔細檢視Spring官方的註釋文件。OneCoder在Debug中似乎意識到一個問題,就是OneCoder這裡使用了
@Bean
public PropertySourcesPlaceholderConfigurer placehodlerConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
這個bean用來使用替換變數符”${}”。這個替換符正式在DataSourceConfig中配置資料來源使用的,這樣的花,是否會造成Spring初始化Bean的生命週期的混亂呢。因為初始化dataSource Bean的時候需要上述bean,而初始化上述bean的時候,已經錯過了DataSource bean的注入時機。那麼如何解決呢,OneCoder還是得求助官方文件。又是一遍又一遍的閱讀,OneCoder在@Bean註解的註釋中,發現這樣的一段話。
A note on BeanFactoryPostProcessor-returning @Bean methods <p> Special consideration must be taken for @Bean methods that return Spring BeanFactoryPostProcessor (BFPP) types. Because BFPP objects must be instantiated very early in the container lifecycle, they can interfere with processing of annotations such as @Autowired, @Value, and @PostConstruct within @Configuration classes. To avoid these lifecycle issues, mark BFPP-returning @Bean methods as static. For example:</p> <p> @Bean
public static PropertyPlaceholderConfigurer ppc() {
// instantiate, configure and return ppc …
}
By marking this method as static, it can be invoked without causing instantiation of its declaring @Configuration class, thus avoiding the above-mentioned lifecycle conflicts. Note however that static @Bean methods will not be enhanced for scoping and AOP semantics as mentioned above. This works out in BFPP cases, as they are not typically referenced by other @Bean methods. As a reminder, a WARN-level log message will be issued for any non-static @Bean methods having a return type assignable to BeanFactoryPostProcessor.</p>
果然被我猜中了,這段意思大概是說,類似PropertyPlaceholderConfigurer這種的Bean是需要在其他Bean初始化之前完成的,這會影響到Spring Bean生命週期的控制,所以如果你用到了這樣的Bean,需要把他們宣告成Static的,這樣就會不需要@Configuration的例項而呼叫,從而提前完成Bean的構造。並且,這裡還提到,如果你沒有把實現 BeanFactoryPostProcessor介面的Bean宣告為static的,他會給出警告。
OneCoder趕緊去檢查自己的控制檯,果然發現了這樣一句話:
WARNING: @Bean method DefaultAppConfig.placehodlerConfigurer is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean Javadoc for complete details
唉,本來OneCoder是很重視警告的,這次怎麼就沒注意到呢。趕緊改成static的。重新啟動。終於,一切OK了!