接上一篇末尾讨论到,解决问题是在filter里面注入bean,这个方法有多种,下面简单探讨一下.
先介绍下web服务加载顺序和bean的初始化步骤,具体如下:
web服务器加载配置顺序:
web.xml加载过程(步骤):
1.启动WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点:
2.容器创建一个ServletContext(上下文),这个WEB项目所有部分都将共享这个上下文.
3.容器将转化为键值对,并交给ServletContext.
4.容器创建中的类实例,即创建监听.
5.在监听中会有contextInitialized(ServletContextEvent args)初始化方法,在这个方法中获得:
ServletContext = ServletContextEvent.getServletContext();
context-param的值 = ServletContext.getInitParameter(“context-param的键”);
6.得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比 所 有的Servlet都要早.
换句话说,这个时候,你对中的键值做的操作,将在你的WEB项目完全启动之前被执行.
7.这个监听是自己写的一个类,除了初始化方法,它还有销毁方法.用于关闭应用前释放资源.比如说数据库连接的关闭.
web.xml节点加载顺序:
节点的加载顺序与它们在 web.xml 文件中的先后顺序无关。即不会因为 filter 写在 listener 的前面而会先加载 filter。最终得出的web服务启动加载结论是:context-param -> listener -> filter -> servlet -> spring.
Bean的初始化顺序应该是:
1.构造函数。
2.初始化属性。
3.如果实现了BeanFactoryAware 接口执行setBeanFactory方法。
4..如果实现了InitializingBean 接口执行afterPropertiesSet方法。
5.如果在配置文件中指定了init-method,那么执行该方法。
6..如果实现了BeanFactoryPostProcessor 接口在 “new”其他类之前执行 postProcessBeanFactory 方法(通过这个方法可以改变配置文件里面的属性值的配置)。
7.如果实现了BeanFactoryPostProcessor 接口,那么会在其他bean初始化方法之前执行postProcessBeforeInitialization 方法,之后执行postProcessAfterInitialization方法。
注意: 对于filter配置而言,与它们出现的顺序是有关的。web.xml 中当然可以定义多个 filter,web 容器启动时初始化每个 filter 时,是按照 filter 配置节出现的顺序来初始化的,当请求资源匹配多个 filter-mapping 时,filter 拦截资源是按照 filter-mapping 配置节出现的顺序来依次调用 doFilter() 方法的。与 filter 相关的一个配置节是 filter-mapping,他个filter相对应,特别要注意,对于拥有相同 filter-name 的 filter 和 filter-mapping 配置节而言,filter-mapping 必须出现在 filter 之后,否则当解析到 filter-mapping 时,它所对应的 filter-name 还未定义。
回到我们的问题,需要在filter中注入bean, 因为加载顺序是: 先加载filter, 后加载spring. 所以在filter中直接用@Autowired和 @Resource注入获取的实例是空的,都会出现NullPointerException.
解决办法就只能提前初始化需要注入的bean, 可以提前到listener或者filter中就可以了.
第一种:在filter中初始化
在filter中初始化,即在init中对bean进行初始化.当然也需要在spring配置这个bean的
初始化,在filter的init初始化方式如下:
ApplicationContext ct=new FileSystemXmlApplicationContext("classpath*:applicationContext.xml");
dataStatisticService = (DataStatisticService)ct.getBean("dataStatistMonitor");
有兴趣的话,可以看看上一篇的问题解决过程.
第二种:在listener中初始化
(1)Listener的生命周期是由servlet容器(例如tomcat)管理的,项目启动时上例中的ConfigListener是由servlet容器实例化并调用其contextInitialized方法,而servlet容器并不认得@Autowired注解,因此导致ConfigService实例注入失败。
(2)而spring容器中的bean的生命周期是由spring容器管理的。
那么该如何在spring容器外面获取到spring容器bean实例的引用呢?这就需要用到spring为我们提供的WebApplicationContextUtils工具类,该工具类的作用是获取到spring容器的引用,进而获取到我们需要的bean实例.
具体如下:
1.建一个监听类,重写 ServletContextListener的 contextInitialized和contextDestroyed 方法
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext servletContext = filterConfig.getServletContext();
WebApplicationContext webAC = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
dataStatistic = webAC.getBean(DataStatistic.class);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
2.在web.xml配置 ,启动的时候就会加载到这个listener.
这样就可以在spring之前可以获取到这个bean的实例进行使用了.
其实这种方法,在前面加载后台线程的那篇已经提到了,有兴趣的可以去看看 .