ContextLoaderListener自动装配配置信息(转)
原文地址:https://blog.csdn.net/qq_35029061/article/details/82936895
Spring配置过程中要考虑两个监听器:ContextLoaderListener与RequestContextListener。
ContextLoaderListener extends ContextLoader implements ServletContextListener。
ServletContextListener extends EventListener。
ServletContextListener只负责监听Web容器的启动和关闭的事件。
ContextLoaderListener(或ContextLoaderServlet)将Web容器与spring容器进行整合。
spring初始化
1 import org.springframework.web.context.ContextLoader; 2 import org.springframework.web.context.ContextLoaderListener 3 import org.springframework.web.context.WebApplicationContext; 4 public class ContextLoaderListener extends ContextLoader implements ServletContextListener
实现spring管理bean
1 import javax.naming.Context; 2 import javax.naming.InitialContext; 3 import javax.sql.DataSource; 4 5 import org.springframework.web.context.ContextLoader; 6 import org.springframework.web.context.WebApplicationContext; 7 8 private DataSource lookDS(String nameStr) { 9 Context env1 = null; 10 try { 11 WebApplicationContext wc = ContextLoader.getCurrentWebApplicationContext(); 12 DataSource dSource = (DataSource)wc.getBean(nameStr); 13 if(dSource != null){ 14 return dSource; 15 } 16 17 env1 = (Context) new InitialContext(); 18 return (DataSource) env1.lookup(nameStr); 19 } catch (Exception e) { 20 try { 21 Context env2 = (Context) env1.lookup("java:comp/env"); 22 return (DataSource) env2.lookup(nameStr); 23 } catch (Exception e2) { 24 String msg = "查找数据源:" + nameStr + "失败:" + 25 CoreException.getStackTraceMsg(e); 26 throw new CoreException(msg); 27 } 28 } 29 }
一、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中配置
1 <!-- Spring配置文件开始 --> 2 <context-param> 3 <param-name>contextConfigLocation</param-name> 4 <param-value> 5 classpath:spring-config.xml 6 </param-value> 7 </context-param> 8 <listener> 9 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 10 </listener> 11 <!-- Spring配置文件结束 -->
在一个团队使用Spring的实际项目中,应该需要多个Spring的配置文件,如何使用和交叉引用的问题:
1 <context-param> 2 <param-name>contextConfigLocation</param-name> 3 <param-value>/WEB-INF/spring-base.xml</param-value> 4 </context-param> 5 <servlet> 6 <servlet-name>springmvc</servlet-name> 7 <servlet-class>org.springframework.web.servlet.DispatcherServlet 8 </servlet-class> 9 <init-param> 10 <param-name>contextConfigLocation</param-name> 11 <param-value>/WEB-INF/springmvc.xml</param-value> 12 </init-param> 13 <load-on-startup>1</load-on-startup> 14 </servlet> 15 <servlet-mapping> 16 <servlet-name>springmvc</servlet-name> 17 <url-pattern>/</url-pattern> 18 </servlet-mapping> 19 <listener> 20 <listener-class>org.springframework.web.context.ContextLoaderListener 21 </listener-class> 22 </listener>
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对象。
1 protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent) 2 throws BeansException 3 { 4 Class contextClass = determineContextClass(servletContext);//通过servletContext确定WebApplicationContext的具体类型 5 if(!(org.springframework.web.context.ConfigurableWebApplicationContext.class).isAssignableFrom(contextClass)) 6 { 7 throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + (org.springframework.web.context.ConfigurableWebApplicationContext.class).getName() + "]"); 8 } else 9 { 10 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); 11 wac.setParent(parent); 12 wac.setServletContext(servletContext); 13 wac.setConfigLocation(servletContext.getInitParameter("contextConfigLocation"));//设置配置文件的路径名 14 customizeContext(servletContext, wac); 15 wac.refresh(); 16 return wac; 17 } 18 }
因此可以通过WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)获取WebApplicationContext。内部实现是通过servletContext对象查找该对象
属性名为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
4.WebApplicationContext如何完成创建
监听器一直对事件监听,如果接收到了变化,就会做出预先设计好的相应动作”。而监听器的响应动作就是在服务器启动时contextInitialized会被调用,关闭的时候contextDestroyed被调用。
把创建好的springcontext,交给application内置对象,提供给监听器/过滤器/拦截器使用。
1 @Service 2 public class CommonListener implements ServletContextListener{ 3 4 @Autowired 5 private UserService userService; 6 7 public void contextInitialized(ServletContextEvent servletContextEvent) { 8 //Exception sending context initialized event to listener instance of class com.walidake.listener.CommonListener java.lang.NullPointerException 9 System.out.println(userService.findUser()); 10 } 11 12 public void contextDestroyed(ServletContextEvent servletContextEvent) { 13 // TODO Auto-generated method stub 14 15 } 16 17 }
spring是管理逻辑层和数据访问层的依赖。而listener是web组件,那么必然不能放在spring里面。真正实例化它的应该是tomcat,在启动加载web.xml实例化的。上层的组件不可能被下层实例化得到。
因此,即使交给Spring实例化,它也没能力去帮你实例化。真正实现实例化的还是web容器。
然而NullPointerException并不是来自这个原因,“ContextLoader来完成实际的WebApplicationContext,也就是Ioc容器的初始化工作”。我们并没有继承ContextLoader,没有Ioc容器的初始化,是无法实现依赖注入的。
因此,我们想到另一种解决方案,能不能通过new ClassPathXmlApplicationContext的方式,像测试用例那样取得Ioc容器中的bean对象。
1 ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); 2 userService = context.getBean(UserService.class); 3 System.out.println(userService.findUser());
发现可以正常打印出结果。然而观察日志后发现,原本的单例被创建了多次(譬如userServiceImpl等)。因此该方法并不可取。
那么,由于被创建了多次,是不是可以说明项目中已存在了WebApplicationContext?
是的。“在初始化ContextLoaderListener成功后,spring context会存放在servletContext中”,意味着我们完全可以从servletContext取出WebApplicationContext,然后getBean取得需要的bean对象。
1 ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContextEvent.getServletContext()); 2 userService = context.getBean(UserService.class); 3 datas = userService.findUser(); 4 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更多一些,这一点大家应该有所体会。
1 <context-param> 2 <param-name>contextConfigLocation</param-name> 3 <param-value>/WEB-INF/applicationContext.xml</param-value> 4 </context-param> 5 <listener> 6 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 7 </listener>
通过上图可以发现ContextLoaderListener继承了ContextLoader类以及实现了ServletContextListener接口,ServletContextListener又实现了EvenListener接口,所以这个监听器具有事件监听的功能,并且是监听了web容器,web容器启动马上加载执行。ContextLoader感觉更像是执行加载web容器的一个小小的core组件,负责执行加载web容器的逻辑。下面重点来说一说这个。
1 public void contextInitialized(ServletContextEvent event) { 2 //首先先获取ContextLoader对象 3 this.contextLoader = createContextLoader(); 4 if (this.contextLoader == null) { 5 this.contextLoader = this; 6 } 7 //通过servletContext对象去加载WebApplicationContext 8 this.contextLoader.initWebApplicationContext(event.getServletContext()); 9 }
initWebApplicationContext的主要代码:
1 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 2 //去servlet容器中去寻找org.springframework.web.context.WebApplicationContext.root作为key的value,也就是webApplicationContext对象 3 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { 4 throw new IllegalStateException( 5 "Cannot initialize context because there is already a root application context present - " + 6 "check whether you have multiple ContextLoader* definitions in your web.xml!"); 7 } 8 9 Log logger = LogFactory.getLog(ContextLoader.class); 10 servletContext.log("Initializing Spring root WebApplicationContext"); 11 if (logger.isInfoEnabled()) { 12 logger.info("Root WebApplicationContext: initialization started"); 13 } 14 long startTime = System.currentTimeMillis(); 15 16 try { 17 // Store context in local instance variable, to guarantee that 18 // it is available on ServletContext shutdown. 19 if (this.context == null) { 20 this.context = createWebApplicationContext(servletContext); 21 } 22 if (this.context instanceof ConfigurableWebApplicationContext) { 23 configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext); 24 } 25 //将ApplicationContext放入ServletContext中,其key为<WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 26 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 27 28 //将ApplicationContext放入ContextLoader的全局静态常量Map中,其中key为:Thread.currentThread().getContextClassLoader()即当前线程类加载器 29 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 30 if (ccl == ContextLoader.class.getClassLoader()) { 31 currentContext = this.context; 32 } 33 else if (ccl != null) { 34 currentContextPerThread.put(ccl, this.context); 35 } 36 37 if (logger.isDebugEnabled()) { 38 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + 39 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); 40 } 41 if (logger.isInfoEnabled()) { 42 long elapsedTime = System.currentTimeMillis() - startTime; 43 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); 44 } 45 46 return this.context; 47 } 48 catch (RuntimeException ex) { 49 logger.error("Context initialization failed", ex); 50 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); 51 throw ex; 52 } 53 catch (Error err) { 54 logger.error("Context initialization failed", err); 55 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); 56 throw err; 57 } 58 }
Spring初始化之后,将ApplicationContext存到在了两个地方(servletContext中和currentContextPerThread中),那么是不是意味着我们可以通过两种方式取得ApplicationContext?
第一种获取方式:
1 request.getSession().getServletContext().getAttribute("org.springframework.web.context.WebApplicationContext.ROOT")
在WebApplicationContextUtils类中有一个静态方法:
1 public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) 2 throws IllegalStateException { 3 4 WebApplicationContext wac = getWebApplicationContext(sc); 5 if (wac == null) { 6 throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); 7 } 8 return wac; 9 } 10 11 public static WebApplicationContext getWebApplicationContext(ServletContext sc) { 12 return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 13 }
通过执行上面的方法可以获取WebApplicationContext对象。
第二种方法:
借用ApplicationContextAware,ApplicationContext的帮助类能够自动装载ApplicationContext,只要你将某个类实现这个接口,并将这个实现类在Spring配置文件中进行配置,Spring会自动帮你进行注入 ApplicationContext.ApplicationContextAware的代码结构如下:
1 public interface ApplicationContextAware { 2 void setApplicationContext(ApplicationContext applicationContext) throws BeansException; 3 }
就这一个接口。可以这样简单的实现一个ApplicationContextHelper类:
1 public class ApplicationHelper implements ApplicationContextAware { 2 3 4 private ApplicationContext applicationContext; 5 public void setApplicationContext(ApplicationContext applicationContext) 6 throws BeansException { 7 this.applicationContext = applicationContext; 8 } 9 public ApplicationContext getApplicationContext(){ 10 return this.applicationContext; 11 } 12 }
通过ApplicationHelper我们就可以获得咱们想要的AppilcationContext类了
浙公网安备 33010602011771号