1. 程式人生 > >Spring3.1完全基於註解配置@Configuration類中@Autowire無法注入問題解決

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了!