Spring配置过程中要考虑两个监听器:ContextLoaderListener与RequestContextListener。
ContextLoaderListener extends ContextLoader implements ServletContextListener。
ServletContextListener extends EventListener。
ServletContextListener只负责监听Web容器的启动和关闭的事件。
ContextLoaderListener(或ContextLoaderServlet)将Web容器与spring容器进行整合。
spring初始化
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.ContextLoaderListener
import org.springframework.web.context.WebApplicationContext;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
实现spring管理bean
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
private DataSource lookDS(String nameStr) {
Context env1 = null;
try {
WebApplicationContext wc = ContextLoader.getCurrentWebApplicationContext();
DataSource dSource = (DataSource)wc.getBean(nameStr);
if(dSource != null){
return dSource;
}
env1 = (Context) new InitialContext();
return (DataSource) env1.lookup(nameStr);
} catch (Exception e) {
try {
Context env2 = (Context) env1.lookup("java:comp/env");
return (DataSource) env2.lookup(nameStr);
} catch (Exception e2) {
String msg = "查找数据源:" + nameStr + "失败:" +
CoreException.getStackTraceMsg(e);
throw new CoreException(msg);
}
}
}
一、ContextLoaderListener
1.ContextLoaderListener的作用
ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext.xml的配置信息。
ContextLoaderListener继承自ContextLoader,实现的是ServletContextListener接口。在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。
ContextLoaderListener可以指定在Web应用程序启动时载入Ioc容器,正是通过ContextLoader来实现的,ContextLoader来完成实际的WebApplicationContext,也就是Ioc容器的初始化工作。
如果没有显式声明,则 系统默认 在WEB-INF/applicationContext.xml。
在web.xml中配置
contextConfigLocation
classpath:spring-config.xml
org.springframework.web.context.ContextLoaderListener
在一个团队使用Spring的实际项目中,应该需要多个Spring的配置文件,如何使用和交叉引用的问题:
contextConfigLocation
/WEB-INF/spring-base.xml
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
/WEB-INF/springmvc.xml
1
springmvc
/
org.springframework.web.context.ContextLoaderListener
2.ServletContextListener接口
ContextLoaderListener类实现了javax.servlet.ServletContextListener接口。ServletContextListener接口能够监听ServletContext对象的生命周期,因为每个web应用仅有一个ServletContext对象,故实际上该接口监听的是整个web应用。
ServletContextListener接口里的函数会结合Web容器的生命周期被调用。因为ServletContextListener是ServletContext的监听者,如果ServletContext发生变化,会触发相应的事件,而监听器一直对事件监听,如果接收到了变化,就会做出预先设计好的相应动作。由于ServletContext变化而触发的监听器的响应具体包括:在服务器启动时,ServletContext被创建的时候,服务器关闭时,ServletContext将被销毁的时候等。
实现该接口的类在web.xml中作为监听器配置后,当web应用启动后,会触发ServletContextEvent事件,调用ContextLoaderListener的contextInitialized(ServletContextEvent sce)方法。
3.继承ContextLoader的作用
ContextLoaderListener的作用就是启动Web容器时,读取在contextConfigLocation中定义的xml文件,自动装配ApplicationContext的配置信息,并产生WebApplicationContext对象,然后将这个对象放置在ServletContext的属性里,这样我们只要得到Servlet就可以得到WebApplicationContext对象,并利用这个对象访问spring容器管理的bean。
简单来说,就是上面这段配置为项目提供了spring支持,初始化了Ioc容器。
ContextLoaderListener通过一个ContextLoader对象来初始化Spring容器。在contextInitialized方法中调用contextLoader.initWebApplicationContext(event.getServletContext())。
ContextLoader类的initWebApplicationContext方法即可返回一个WebApplicationContext对象context。并通过 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context)将WebApplicationContext对象放置在ServletContext对象中。initWebApplicationContext方法通过调用以下方法实例化并设置WebApplicationContext对象。
protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent)
throws BeansException
{
Class contextClass = determineContextClass(servletContext);//通过servletContext确定WebApplicationContext的具体类型
if(!(org.springframework.web.context.ConfigurableWebApplicationContext.class).isAssignableFrom(contextClass))
{
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + (org.springframework.web.context.ConfigurableWebApplicationContext.class).getName() + "]");
} else
{
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
wac.setParent(parent);
wac.setServletContext(servletContext);
wac.setConfigLocation(servletContext.getInitParameter("contextConfigLocation"));//设置配置文件的路径名
customizeContext(servletContext, wac);
wac.refresh();
return wac;
}
}
因此可以通过WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)获取WebApplicationContext。内部实现是通过servletContext对象查找该对象
属性名为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
4.WebApplicationContext如何完成创建
监听器一直对事件监听,如果接收到了变化,就会做出预先设计好的相应动作”。而监听器的响应动作就是在服务器启动时contextInitialized会被调用,关闭的时候contextDestroyed被调用。
把创建好的springcontext,交给application内置对象,提供给监听器/过滤器/拦截器使用。
@Service
public class CommonListener implements ServletContextListener{
@Autowired
private UserService userService;
public void contextInitialized(ServletContextEvent servletContextEvent) {
//Exception sending context initialized event to listener instance of class com.walidake.listener.CommonListener java.lang.NullPointerException
System.out.println(userService.findUser());
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
// TODO Auto-generated method stub
}
}
spring是管理逻辑层和数据访问层的依赖。而listener是web组件,那么必然不能放在spring里面。真正实例化它的应该是tomcat,在启动加载web.xml实例化的。上层的组件不可能被下层实例化得到。
因此,即使交给Spring实例化,它也没能力去帮你实例化。真正实现实例化的还是web容器。
然而NullPointerException并不是来自这个原因,“ContextLoader来完成实际的WebApplicationContext,也就是Ioc容器的初始化工作”。我们并没有继承ContextLoader,没有Ioc容器的初始化,是无法实现依赖注入的。
因此,我们想到另一种解决方案,能不能通过new ClassPathXmlApplicationContext的方式,像测试用例那样取得Ioc容器中的bean对象。
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml");
userService = context.getBean(UserService.class);
System.out.println(userService.findUser());
发现可以正常打印出结果。然而观察日志后发现,原本的单例被创建了多次(譬如userServiceImpl等)。因此该方法并不可取。
那么,由于被创建了多次,是不是可以说明项目中已存在了WebApplicationContext?
是的。“在初始化ContextLoaderListener成功后,spring context会存放在servletContext中”,意味着我们完全可以从servletContext取出WebApplicationContext,然后getBean取得需要的bean对象。
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContextEvent.getServletContext());
userService = context.getBean(UserService.class);
datas = userService.findUser();
servletContextEvent.getServletContext().setAttribute("datas", datas);
二、WebApplicationContext
WebApplicationContext是ApplicationContext的子接口,纵观Spring框架的几种容器,BeanFactory作为顶级的接口,是所有IOC容器的最上层接口,顾名思义WebApplicationContext是依赖于Web容器的一个Spring的IOC容器。前提条件是web容器启动后这个容器才能启动。那么如何借助web容器来启动Spring web的上下文?
第一种方式:我们可以通过org.springframework.web.context.ContextLoaderServlet
;
第二种式:org.springframework.web.context.ContextLoaderListener.
这两种方式有什么不同呢?listener完全是观察者的设计,仅仅执行的是监听的任务,而servlet的启动要稍微延迟一些,启动的前后顺序是有影像的。所以我认为listener更好用一些,实际开发中的框架配置中也是listener更多一些,这一点大家应该有所体会。
contextConfigLocation
/WEB-INF/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
通过上图可以发现ContextLoaderListener继承了ContextLoader类以及实现了ServletContextListener接口,ServletContextListener又实现了EvenListener接口,所以这个监听器具有事件监听的功能,并且是监听了web容器,web容器启动马上加载执行。ContextLoader感觉更像是执行加载web容器的一个小小的core组件,负责执行加载web容器的逻辑。下面重点来说一说这个。
public void contextInitialized(ServletContextEvent event) {
//首先先获取ContextLoader对象
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
//通过servletContext对象去加载WebApplicationContext
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
initWebApplicationContext的主要代码:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//去servlet容器中去寻找org.springframework.web.context.WebApplicationContext.root作为key的value,也就是webApplicationContext对象
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
}
//将ApplicationContext放入ServletContext中,其key为
pring初始化之后,将ApplicationContext存到在了两个地方(servletContext中和currentContextPerThread中),那么是不是意味着我们可以通过两种方式取得ApplicationContext?
第一种获取方式:
request.getSession().getServletContext().getAttribute("org.springframework.web.context.WebApplicationContext.ROOT")
在WebApplicationContextUtils类中有一个静态方法:
public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc)
throws IllegalStateException {
WebApplicationContext wac = getWebApplicationContext(sc);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
return wac;
}
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
通过执行上面的方法可以获取WebApplicationContext对象。
第二种方法:
借用ApplicationContextAware,ApplicationContext的帮助类能够自动装载ApplicationContext,只要你将某个类实现这个接口,并将这个实现类在Spring配置文件中进行配置,Spring会自动帮你进行注入 ApplicationContext.ApplicationContextAware的代码结构如下:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
就这一个接口。可以这样简单的实现一个ApplicationContextHelper类:
public class ApplicationHelper implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public ApplicationContext getApplicationContext(){
return this.applicationContext;
}
}
通过ApplicationHelper我们就可以获得咱们想要的AppilcationContext类了