问题:
在LVMMCrawlerSuit.java是xml配置的bean, 需要注入用@Component声明的bean. 但是运行的时候却报NullPointerException. 说明没有注入进来.
代码:
1. java
- 1. LVMMCrawlerSuit.java
- public class LVMMCrawlerSuit extends AbstractCrawlerSuit{
- @Resource
- private LVMMURLBuilder lvmmurlBuilder;
- public LVMMCrawlerSuit() {
- }
- }
- 2. LVMMURLBuilder.java
- @Component
- public class LVMMURLBuilder extends AbstractResourceURLBuilder {
- public LVMMURLBuilder() { }
- }
2. 配置.
- <bean id="LVMMCrawlerSuit" class = 'com.qunar.b2c.crawlersuit.impl.LVMMCrawlerSuit'/>
问题查找:
1. 查找网上资源,未果.
2. 果断debug,跟踪源码.
将断点定位到 org.springframework.context.support.AbstractRefreshableApplicationContext#loadBeanDefinitions ,该方法是加载bean的必经之路.跟踪发现,该方法共执行两次,生成了两个不同的 org.springframework.beans.factory.support.DefaultListableBeanFactory, 并且后者的parentBeanFactory为前者,根据原设计是后者可以调用前者的bean 并完成注入.
现在报NullPointerException,很明显是"父调用子",所以肯定拿不到.在打印的log中进行了佐证.
在debug中发现,两次执行分别来自不同的beans资源文件: app-web.xml 和 applicationContext-*.xml, 按key查找,很容易找到了配置信息如下.
- <servlet>
- <servlet-name>dispatcherServlet</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- classpath:classpath:app-web.xml
- </param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:applicationContext-*.xml</param-value>
- </context-param>
既然,两次加载,并且加载了不同的beans,虽然有父子的层级关系,但是限制多多. 那么就尝试合二为一.
在test中,发现因为修改了spring默认加载的文件名,所以删除任何一个配置都不能正确运行.那么就全部设置成一样的吧. test success......
解决方案:
方案一. 将配置文件路径合并, 分别指定给不同配置.
- <servlet>
- <servlet-name>dispatcherServlet</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- classpath:applicationContext-*.xml,classpath:app-web.xml
- </param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:applicationContext-*.xml,classpath:app-web.xml</param-value>
- </context-param>
方案二. 原有配置不变,合理规划Bean的定义及合理使用.
在方案一中, 使用的简单,粗暴的解决办法. 没有考虑到spring的设计思想. 既然有ioc容器的父子级划分,那么在使用的时候,一定会有用的.
在使用annotation定义bean 的时候,是需要增加如下代码,对使用何种注解的类才管理到ioc容器中.
- <context:component-scan base-package="com.qunar.b2c">
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Component" />
- </context:component-scan>
上述提到, 在 spring web的使用中, 会加载两个ioc容器,
1. 一个是contextConfigLocation定义,用来启动spring核心框架的. 所以在该步骤中,应加载应用中的基础服务信息的bean,如 dao,Service 等等.
2. 另外一个ioc容器是web加载的容器, 那么只需加载Controller相关的bean.
因为在spring ioc的 DefaultListableBeanFactory类是支持父子关系,
1. 子容器是可以访问到父容器中的bean,
2. 然而父容器访问不了子容器的bean,
这就保证了, Controller可以访问 Service等, 但是Service 访问不了web层的bean, 这样就将职责分开了.所以修改的配置如下:
- applicationContext-beans.xml:
- <context:component-scan base-package="com.qunar.b2c">
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Component" />
- </context:component-scan>
- app-web.xml
- <context:component-scan base-package="com.qunar.b2c">
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
- </context:component-scan>
在开发定义bean的时候, 也需要注意,把bean定义到哪一层级.