尝试使用Java Config设置Spring 4 Web应用程序时,我遇到了将在配置类中创建的bean自动装配到另一个配置类中的问题。“数据源” Bean在MyBatisConfig类中具有空值。这似乎是配置中唯一未正确接线的bean。查看Spring调试日志(请参阅下面的最后一个代码块中的日志的最后部分),它看起来已正确实例化,但似乎也被销毁了?我的配置有什么问题?
PropertySourcesPlaceholderConfigurerConfig类:
package nl.somesite.teamshot.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.io.ClassPathResource; @Configuration public class PropertySourcesPlaceholderConfigurerConfig { @Bean public PropertySourcesPlaceholderConfigurer propertyConfigurer() { PropertySourcesPlaceholderConfigurer propertyConfigurer = new PropertySourcesPlaceholderConfigurer(); propertyConfigurer.setLocation(new ClassPathResource("application.properties")); /*propertyConfigurer.setLocation(new ClassPathResource("file:${catalina.home}/conf/application.properties")); propertyConfigurer.setLocation(new ClassPathResource("/var/lib/openshift/517874b8e0b8cd218e000391/app-root/data/apache-tomcat-7.0.39/conf/application.properties"));*/ propertyConfigurer.setIgnoreUnresolvablePlaceholders(false); propertyConfigurer.setIgnoreResourceNotFound(true); return propertyConfigurer; } }
DbConfig类别:
package nl.somesite.teamshot.config; import org.apache.commons.dbcp.BasicDataSource; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @Import({PropertySourcesPlaceholderConfigurerConfig.class}) public class DbConfig { private @Value("jdbc:mysql://${OPENSHIFT_MYSQL_DB_HOST}:${OPENSHIFT_MYSQL_DB_PORT}/${OPENSHIFT_MYSQL_DATABASE}") String url; private @Value("${OPENSHIFT_MYSQL_DB_USERNAME}") String username; private @Value("${OPENSHIFT_MYSQL_DB_PASSWORD}") String password; static Logger log = LogManager.getLogger(DbConfig.class.getName()); @Bean public BasicDataSource dataSource() { log.debug("Creating dataSource bean, url = "+ url); BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDefaultAutoCommit(false); log.debug("dataSource bean url = "+ dataSource.getUrl()); return dataSource; } }
MyBatisConfig类:
package nl.somesite.teamshot.config; import java.io.IOException; import org.apache.commons.dbcp.BasicDataSource; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; @Configuration @Import({DbConfig.class}) public class MyBatisConfig { @Autowired private BasicDataSource dataSource; static Logger log = LogManager.getLogger(MyBatisConfig.class.getName()); @Bean public SqlSessionFactoryBean sqlSessionFactory() { log.debug("Creating SqlSessionFactoryBean, dataSource = " + dataSource); SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean(); sqlSessionFactory.setDataSource(dataSource); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); org.springframework.core.io.Resource[] classPathResources; try { classPathResources = resolver.getResources("classpath*:/mappers/*.xml"); sqlSessionFactory.setMapperLocations(classPathResources); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return sqlSessionFactory; } @Bean public MapperScannerConfigurer mapperScannerConfigurer() { log.debug("Creating MapperScannerConfigurer"); MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("nl.somesite.teamshot.data"); return mapperScannerConfigurer; } }
AppConfig类:
package nl.somesite.teamshot.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = {"nl.somesite.teamshot.config"}) public class AppConfig { }
AppInitializer类:
package nl.somesite.teamshot.initializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; public class AppInitializer implements WebApplicationInitializer { private static final String CONFIG_LOCATION = "nl.somesite.teamshot.config"; private static final String MAPPING_URL = "/"; static Logger log = LogManager.getLogger(AppInitializer.class.getName()); public void onStartup(ServletContext servletContext) throws ServletException { WebApplicationContext context = getContext(); servletContext.addListener(new ContextLoaderListener(context)); ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context)); dispatcher.setLoadOnStartup(1); dispatcher.addMapping(MAPPING_URL); } private AnnotationConfigWebApplicationContext getContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setConfigLocation(CONFIG_LOCATION); return context; } }
WebMvcConfig类:
package nl.somesite.teamshot.config; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { static Logger log = LogManager.getLogger(WebMvcConfig.class.getName()); @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Bean public ViewResolver internalResourceViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); return resolver; } }
日志的最后一部分:
[DEBUG:] 2014-07-09 01:25:20 [DefaultSingletonBeanRegistry line:220] Creating shared instance of singleton bean 'dataSource' [DEBUG:] 2014-07-09 01:25:20 [AbstractAutowireCapableBeanFactory line:449] Creating instance of bean 'dataSource' [DEBUG:] 2014-07-09 01:25:20 [AbstractBeanFactory line:249] Returning cached instance of singleton bean 'dbConfig' [DEBUG:] 2014-07-09 01:25:20 [DbConfig line:25] Creating dataSource bean, url = jdbc:mysql://localhost:3306/teamshot?useUnicode=true&characterEncoding=UTF-8 [DEBUG:] 2014-07-09 01:25:20 [DbConfig line:34] dataSource bean url = jdbc:mysql://localhost:3306/teamshot?useUnicode=true&characterEncoding=UTF-8 [DEBUG:] 2014-07-09 01:25:20 [AbstractAutowireCapableBeanFactory line:523] Eagerly caching bean 'dataSource' to allow for resolving potential circular references [DEBUG:] 2014-07-09 01:25:20 [AbstractAutowireCapableBeanFactory line:477] Finished creating instance of bean 'dataSource' [DEBUG:] 2014-07-09 01:25:20 [DefaultSingletonBeanRegistry line:220] Creating shared instance of singleton bean 'sqlSessionFactory' [DEBUG:] 2014-07-09 01:25:20 [AbstractAutowireCapableBeanFactory line:449] Creating instance of bean 'sqlSessionFactory' [DEBUG:] 2014-07-09 01:25:20 [AbstractBeanFactory line:249] Returning cached instance of singleton bean 'myBatisConfig' [DEBUG:] 2014-07-09 01:25:20 [MyBatisConfig line:27] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 01:25:20 [PathMatchingResourcePatternResolver line:553] Looking for matching resources in directory tree [D:\Eclipse workspaces\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\teamshot\WEB-INF\classes\mappers] [DEBUG:] 2014-07-09 01:25:20 [PathMatchingResourcePatternResolver line:615] Searching directory [D:\Eclipse workspaces\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\teamshot\WEB-INF\classes\mappers] for files matching pattern [D:/Eclipse workspaces/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/teamshot/WEB-INF/classes/mappers/*.xml] [DEBUG:] 2014-07-09 01:25:20 [PathMatchingResourcePatternResolver line:354] Resolved location pattern [classpath*:/mappers/*.xml] to resources [file [D:\Eclipse workspaces\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\teamshot\WEB-INF\classes\mappers\newsitems.xml], file [D:\Eclipse workspaces\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\teamshot\WEB-INF\classes\mappers\teams.xml]] [DEBUG:] 2014-07-09 01:25:20 [AbstractAutowireCapableBeanFactory line:523] Eagerly caching bean 'sqlSessionFactory' to allow for resolving potential circular references [DEBUG:] 2014-07-09 01:25:20 [AbstractAutowireCapableBeanFactory line:1595] Invoking afterPropertiesSet() on bean with name 'sqlSessionFactory' [DEBUG:] 2014-07-09 01:25:20 [DefaultSingletonBeanRegistry line:474] Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3278a91: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,appConfig,dbConfig,myBatisConfig,propertySourcesPlaceholderConfigurerConfig,webMvcConfig,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor,dataSource,sqlSessionFactory,mapperScannerConfigurer,propertyConfigurer,org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration,requestMappingHandlerMapping,mvcContentNegotiationManager,viewControllerHandlerMapping,beanNameHandlerMapping,resourceHandlerMapping,defaultServletHandlerMapping,requestMappingHandlerAdapter,mvcConversionService,mvcValidator,mvcUriComponentsContributor,httpRequestHandlerAdapter,simpleControllerHandlerAdapter,handlerExceptionResolver,internalResourceViewResolver,newsItemMapper,teamMapper]; root of factory hierarchy [DEBUG:] 2014-07-09 01:25:20 [DisposableBeanAdapter line:322] Invoking destroy method 'close' on bean with name 'dataSource' [DEBUG:] 2014-07-09 01:25:20 [DisposableBeanAdapter line:244] Invoking destroy() on bean with name 'webMvcConfig' [DEBUG:] 2014-07-09 01:25:20 [DisposableBeanAdapter line:244] Invoking destroy() on bean with name 'dbConfig' [DEBUG:] 2014-07-09 01:25:21 [DisposableBeanAdapter line:244] Invoking destroy() on bean with name 'appConfig' [DEBUG:] 2014-07-09 01:25:21 [DisposableBeanAdapter line:244] Invoking destroy() on bean with name 'propertySourcesPlaceholderConfigurerConfig' [ERROR:] 2014-07-09 01:25:21 [ContextLoader line:331] Context initialization failed Error creating bean with name 'sqlSessionFactory' defined in class path resource [nl/somesite/teamshot/config/MyBatisConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required jul 09, 2014 1:25:21 AM org.apache.catalina.core.StandardContext listenerStart SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [nl/somesite/teamshot/config/MyBatisConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1553) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:681) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482) at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403) at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306) at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106) at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4887) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5381) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334) at java.util.concurrent.FutureTask.run(FutureTask.java:166) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:724) Caused by: java.lang.IllegalArgumentException: Property 'dataSource' is required at org.springframework.util.Assert.notNull(Assert.java:112) at org.mybatis.spring.SqlSessionFactoryBean.afterPropertiesSet(SqlSessionFactoryBean.java:337) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549) ... 22 more
如果禁用Spring调试日志记录,则会看到以下内容:
[DEBUG:] 2014-07-09 11:02:19 [MyBatisConfig line:50] Creating MapperScannerConfigurer [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [DbConfig line:24] Creating dataSource bean, url = jdbc:mysql://localhost:3306/teamshot?useUnicode=true&characterEncoding=UTF-8 [DEBUG:] 2014-07-09 11:02:20 [DbConfig line:33] dataSource bean url = jdbc:mysql://localhost:3306/teamshot?useUnicode=true&characterEncoding=UTF-8 [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [DEBUG:] 2014-07-09 11:02:20 [MyBatisConfig line:30] Creating SqlSessionFactoryBean, dataSource = null [ERROR:] 2014-07-09 11:02:20 [ContextLoader line:331] Context initialization failed Error creating bean with name 'sqlSessionFactory' defined in class path resource [nl/somesite/teamshot/config/MyBatisConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required
阅读了很多与Spring相关的内容之后,我发现MapperScannerConfigurer是BeanFactoryPostProcessor的实现。在@Configuration类中使用BeanFactoryPostProcessor会破坏该@Configuration类的默认后处理。
在使用@ Autowired,@ PostConstruct,@ Value等的@Configuration类中处理BeanFactoryPostProcessor @Bean方法时,存在根本的生命周期冲突。因为BFPP必须在生命周期的早期实例化,所以它们会导致实例化声明@Configuration类的早期实例- 现在还为时尚早,无法接受AutowiredAnnotationBeanPostProcessor和朋友的常规后处理。参见https://jira.spring.io/browse/SPR-8269
将MapperScannerConfigurer Bean方法从MyBatisConfig分离到它自己的类中可以解决此问题。