Java 服务端架构
Spring、Netty、日志框架与工程化实战
🎨 视觉封面Spring3.1完全基于注解配置@Configuration类中@Autowire无法注入问题解决
在上回介绍Spring3.1+Hibernate4.1.7基于注解配置的时候(《SpringMVC3.1+Hibernate4.1.7完全基于注解配置(零配置文件)》)说过,在修改配置方式的时候遇到过不少问题。这里介绍一下。
/**
* 数据源配置类
*
* @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:
@Configuration public class DatabaseConfig { @Bean public DataSource dataSource() { // instantiate, configure and return DataSource } }
@Configuration @Import(DatabaseConfig.class) public class AppConfig { @Inject DatabaseConfig dataConfig;
Code 5 行@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
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:
@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.
果然被我猜中了,这段意思大概是说,类似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了!
所有代码开源上传至 GitHub:yummy-code 仓库 · GESP 专题站:GESP WIKI
欢迎加入:C++ GESP/CSP 考级答疑群(688906745) 与 Java/Python交流群(982860385),点击可直接加群。
猜你想读 · 相关文章推荐
Java 利用ASM读取变量值(Field value)问题研究
最近在学习Spring源码的过程中,遇到了spring-asm工程的重新打包的问题,于是突然就想研究一下asm这个开源字节码操作工具。秉承我的一贯风格,想到啥就立马学啥。 对于开源产品,我的一贯风格就是通过其官方提供的源码版本管理地址(svn/git等),直接下载最新代码,构建Java工程,直接通过工程依赖的方式研究学...
SpringFramework3 源码下载和编译教程
Spring已经将源码从svn迁移到了git。所以,首先需要安装git环境。我使用的是Windows下的TortoiseGit,带图形界面,比较直观。 SpringFramework的GitHub地址:https://github.com/SpringSource/spring-framework Git clone地...
Spring研究-容器初始化之FileSystemXmlApplicationContext构造函数
宅男Coder,没有其他爱好,闲暇之余抱着瞻仰的心态去阅读一下Spring的源码,期许能收获一支半解。要学习Spring的源码,第一步自然是下载和编译Spring的源码,这个我在之前的博文中已经发表过了。具体可参考:<a href="http://www.coderli.com/springframework-sour...
OneCoder (lihongzheshuai)
一个中年人的自留地,记录学习 C++、GESP/NOI、Java、Python 与算法架构的心得体会。本站唯一网址:coderli.com