spring的xml配置与annotation注解混合使用无法Autowired的问题
问题:
AppUserServiceImpl.java是通过@Service声明的bean,在xml配置文件中重新给AppUserServiceImpl.java配置一个别名“pap_appuser_service”。
在普通类AppUserAPIImpl.java中,需要调用AppUserServiceImpl.java,但是运行的时候却报NullPointerException,说明没有注入进来。
java代码
public class AppUserAPIImpl implements AppUser { private static ApplicationContext context = ContextLoader.getCurrentWebApplicationContext(); private IAppUserService service; public AppUserAPIImpl(String appCode, String userCode) { this.service = (IAppUserService) context.getBean("pap_appuser_service"); } } @Service public class AppUserServiceImpl implements IAppUserService { @Autowired private IUserService userService; }配置
<bean id="pap_appuser_service" class="net.yhte.web.pap.user.service.impl.AppUserServiceImpl"/>
问题查找:
1. 查找网上资源,未果.
2. 果断debug,跟踪源码.
将断点定位到 org.springframework.context.support.AbstractRefreshableApplicationContext#loadBeanDefinitions ,该方法是加载bean的必经之路.跟踪发现,该方法共执行两次,生成了两个不同的 org.springframework.beans.factory.support.DefaultListableBeanFactory, 并且后者的parentBeanFactory为前者,根据原设计是后者可以调用前者的bean 并完成注入.
现在报NullPointerException,很明显是"父调用子",所以肯定拿不到.在打印的log中进行了佐证.
在debug中发现,两次执行分别来自不同的beans资源文件: spring-servlet.xml 和 pap-service.xml, 按key查找,很容易找到了配置信息如下.
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/config/applicationContext.xml, /WEB-INF/config/application-*.xml, /WEB-INF/config/pap/pap-*.xml </param-value> </context-param> <servlet> <servlet-name>monitor</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-servlet.xml</param-value> </init-param> </servlet>
既然,两次加载,并且加载了不同的beans,虽然有父子的层级关系,但是限制多多. 那么就尝试合二为一.
在test中,发现因为修改了spring默认加载的文件名,所以删除任何一个配置都不能正确运行.那么就全部设置成一样的吧. test success......
解决方案:
方案一. 将配置文件路径合并, 分别指定给不同配置.
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring-servlet.xml, /WEB-INF/config/applicationContext.xml, /WEB-INF/config/application-*.xml, /WEB-INF/config/pap/pap-*.xml </param-value> </context-param> <servlet> <servlet-name>monitor</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring-servlet.xml, /WEB-INF/config/applicationContext.xml, /WEB-INF/config/application-*.xml, /WEB-INF/config/pap/pap-*.xml </param-value> </init-param> </servlet>
方案二. 原有配置不变,合理规划Bean的定义及合理使用.
在方案一中, 使用的简单,粗暴的解决办法. 没有考虑到spring的设计思想. 既然有ioc容器的父子级划分,那么在使用的时候,一定会有用的.
在使用annotation定义bean 的时候,是需要增加如下代码,对使用何种注解的类才管理到ioc容器中.
<context:component-scan base-package="net.yhte.web.pap"> <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, 这样就将职责分开了.所以修改的配置如下:
spring-servlet.xml <context:component-scan base-package="net.yhte.web.pap"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan> pap-service.xml <context:component-scan base-package="net.yhte.web.pap"> <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> <bean id="pap_appuser_service" class="net.yhte.web.pap.user.service.impl.AppUserServiceImpl"/>在开发定义bean的时候, 也需要注意,把bean定义到哪一层级.