spring启动入口
spring启动入口
(44条消息) spring启动入口_lieyanhaipo的专栏-CSDN博客_spring 入口类
一、Spring与WEB容器整合
web项目中,Spring启动是在web.xml配置监听器,如下所示:
- <!-- 配置Spring上下文监听器 -->
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
可以看看ContextLoaderListener类,它实现了Tomcat容器的ServletContextListener接口,所以它与普通的Servlet监听是一样的。同样是重写到两个方法:contextInitialized()方法在web容器初始化时执行,contextDestroyed()方法在容器销毁时执行。
WEB容器启动时会触发初始化事件,ContextLoaderListener监听到这个事件,其contextInitialized()方法会被调用,在这个方法中Spring会初始化一个根上下文,即WebApplicationContext。这是一个接口,其实际默认实现类是XmlWebApplicationContext。这个就是Spring IOC的容器,其对应bean定义的配置信息由web.xml中的context-param来指定
- <!-- 配置Spring配置文件路径 -->
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath*:applicationContext.xmlclasspath*:applicationContext-shiro.xml
- </param-value>
- </context-param>
在Spring IOC 容器启动初始化完毕之后,会将其储存到ServletContext中。形式如下:servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, WebApplicationContext context);
在ContextLoaderListener类中,只是实现了ServletContextListener提供的到两个方法,Spring启动主要的逻辑在父类ContextLoader的方法initWebApplicationContext实现。ContextLoaderListener的作用就是启动web容器时自动装配ApplicationContext的配置信息。更细化一点讲,Spring的启动过程其实就是Spring IOC容器的启动过程。
- public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
- /**
- * Initialize the root web application context.
- */
- @Override
- public void contextInitialized(ServletContextEvent event) {
- initWebApplicationContext(event.getServletContext());
- }
- /**
- * Close the root web application context.
- */
- @Override
- public void contextDestroyed(ServletContextEvent event) {
- closeWebApplicationContext(event.getServletContext());
- ContextCleanupListener.cleanupAttributes(event.getServletContext());
- }
- }
二、ContextLoader剖析
从上一部分ContextLoaderListener类可以得知,ContextLoader实际执行Spring容器的初始化,Spring整个的配置工作都是在ContextLoader完成的,这里参数ServletContextEvent由web容器提供,不做说明。ContextLoaderListener很好理解,所以我们主要看ContextLoader类。用Maven引入Spring的源码,打开ContextLoader类,类注释的第一行就是
- /**
- * Performs the actual initialization work for the root application context.
- ......
- **/
用google翻译:实际执行根应用上下文的初始化工作。这里的根应用上下文就是上文所写的WebApplicationContext。我们先看看ContextLoader的时序图
ContextLoader类initWebApplicationContext()方法
- /**
- * Initialize Spring's web application context for the given servlet context,
- * using the application context provided at construction time, or creating a new one
- * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
- * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
- * @param servletContext current servlet context
- * @return the new WebApplicationContext
- * @see #ContextLoader(WebApplicationContext)
- * @see #CONTEXT_CLASS_PARAM
- * @see #CONFIG_LOCATION_PARAM
- */
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
- //判断ServletContext是否已经存在WebApplication,如果存在则抛出异常
- 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) {
- //创建WebApplicationContext(下文有说明)
- this.context = createWebApplicationContext(servletContext);
- }
- if (this.context instanceof ConfigurableWebApplicationContext) {
- ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
- if (!cwac.isActive()) {
- // The context has not yet been refreshed -> provide services such as
- // setting the parent context, setting the application context id, etc
- if (cwac.getParent() == null) {
- // 得到根上下文的父上下文,然后设置到根上下文 。一般的web项目parent为空
- ApplicationContext parent = loadParentContext(servletContext);
- cwac.setParent(parent);
- }
- //从web.xml加载参数,初始化跟上下文WebApplicationContext,创建bean工厂和bean对象。
- //这个过程比较麻烦,下一篇文章专门分析
- configureAndRefreshWebApplicationContext(cwac, servletContext);
- }
- }
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
- ClassLoader ccl = Thread.currentThread().getContextClassLoader();
- if (ccl == ContextLoader.class.getClassLoader()) {
- currentContext = this.context;
- }
- else if (ccl != null) {
- currentContextPerThread.put(ccl, this.context);
- }
- if (logger.isDebugEnabled()) {
- logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
- WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
- }
- if (logger.isInfoEnabled()) {
- long elapsedTime = System.currentTimeMillis() - startTime;
- logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
- }
- return this.context;
- }
- catch (RuntimeException ex) {
- logger.error("Context initialization failed", ex);
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
- throw ex;
- }
- catch (Error err) {
- logger.error("Context initialization failed", err);
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
- throw err;
- }
- }
在这个方法中ServletContext是由web容器监听器(ContextLoaderListener)提供。首先判断servlectContext中是否已经存在根上下文,如果存在,则抛出异常;否则通过createWebApplicationContext方法创建新的根上下文。然后通过loadParentContext()方法为其设置父上下文。再通过configureAndRefreshWebApplicationContext为根上下文构建bean工厂和bean对象。 最后把上下文存入servletContext,并且存入currentContextPerThread。至此初始化过程完毕,接下来可以获取WebApplicationContext,进而用getBean("bean name")得到bean。
createWebApplicationContext()方法用来创建根上下文:
- protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
- //从web.xml配置的contextClass参数中获取上下文类名,如果contextClass为空,则使用默认的。
- //下文有说明
- Class<?> contextClass = determineContextClass(sc);
- //根上下文必须是ConfigurableWebApplicationContext的子类,否则抛出异常
- if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
- throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
- "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
- }
- //BeanUtils.instantiateClass工具方法,根据类名创建类
- return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
- }
determineContextClass()方法返回根上下文的类名
- protected Class<?> determineContextClass(ServletContext servletContext) {
- //从web.xml获得参数contextClass,在一般的web项目中,此参数为null
- String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
- if (contextClassName != null) {
- try {
- return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
- }
- catch (ClassNotFoundException ex) {
- throw new ApplicationContextException(
- "Failed to load custom context class [" + contextClassName + "]", ex);
- }
- }
- else {
- //获得根上下文WebApplicationContext的默认实现类的类名,defaultStrategies是Properties类型,
- //在CotnextLoader类开头static语句块中初始化
- contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
- try {
- return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
- }
- catch (ClassNotFoundException ex) {
- throw new ApplicationContextException(
- "Failed to load default context class [" + contextClassName + "]", ex);
- }
- }
- }
WebApplicationContext默认实现类的类名获取
- static {
- try {
- //获取当前包下面的ContextLoader.properties文件,文件内容是:
- //org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
- ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
- defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
- }
- catch (IOException ex) {
- throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
- }
- }