SpringMVC(一)-细聊ContextLoaderListener 是怎么被加载的

前言

距离上一篇博客已经一个多月了 感觉都好久了!今天决定还是写篇博客 让自己坚持下,不然再这么下去就荒废了 哈哈!
为什么要写博客呢?

其实还是源于我在知乎上面的 看到的一个回答。

问题是这样的:生活中最浪费时间的事情有哪些?我看到下面的一个贴子回答是:
学而不思犹豫不决,看到这个 我想到 哇偶 总结的好到位!

好了 废话不多说,为什么要写Spring MVC 呢 当然是因为我博主我最近在看Spring MVC 的源码了! 为什么开篇想讲 2个容器呢?随着Spring Boot 的越来越流行 我们之前配置XML 的方式也逐渐 被舍弃。我们刚使用Spring MVC的时候 一定也知道怎么去标准的配置 比如ContextLoaderListener DispatcherServlet等等 那为什么要有这些配置,这些配置又去怎么运行的呢?

今天 我就从自己的理解 去分析下 , 理解不当的地方 请各位见谅,留言指出 多谢!

PS: 这篇文章 有点跑题 重点讲了一个程序 tomcat 是怎么去加载web配置 又是怎么样将我们的ContextLoaderListener监听类加入到监听列表中的 最后串联了下整个Servlet 容器怎么去启动一个应用程序的~

下面一片文章 我会重点去看下ContextLoaderListener 里面怎么去创建WebApplication容器的,这里面我也走了一点弯路 一开始以为 我们的ContextLoaderListene 是通过ApplicationContext中的addListener添加的 最终 我一步步发现 我想错了!

标准配置

来 首先回顾下 我们的标准配置

    <!--Spring 配置-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--Spring Root 根容器的配置-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/configs/spring/applicationContext.xml</param-value>
    </context-param>
  
    
    <!--Spring MVC 配置-->
    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--Servlet 容器的配置-->
        <!--这边可以不写  默认读取/WEB-INF/{servletName}-dispatcher-servlet.xml -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    

从上面的我的注释中 也可以看看出 一个是Spring 的 一个是Spring MVC 的

Spring Root容器

Spring Root容器 一般都是配置了非Controller的Bean 比如 Dao,Service 等等Bean都会在这个容器里面。

我们看到 这个配置contextparam 中的节点 设置了ApplicationContext的config路径 这个后面在源码中 我会讲到,先过~~

ContextLoaderListener 是被怎么执行的

listener

我们看到listener 这个节点配置了一个ContextLoaderListener类,那我们来详细聊下 这个类 是做什么的

首先 我们知道 服务在启动的时候 会给我们应用提供了一个容器的上下文环境ServletContext 这个上下文环境 就是我们应用程序的宿主环境

这边简答的说下ServletContenxt: 当我们的Servlet容器 也就是我们经常用的Tomcat或者Jetty 等 在web应用启动的时候 会创建一个Servlet对象,这个对象可以被 web应用下面的所有的Servlet所访问。

也就是说一个web应用只有一个ServletContent.记得这点很重要,到后面的代码中 我们就能知道 我们的WebApplicationContext 其实就是ServletContent的一个属性Attribute。
我们可以从Spring 项目的的一个ContextLoaderTests 单元测试类中 可以看出

	@Test
   public void testContextLoaderListenerWithDefaultContext() {
       MockServletContext sc = new MockServletContext("");
   	sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
   			"/org/springframework/web/context/WEB-INF/applicationContext.xml " +
   			"/org/springframework/web/context/WEB-INF/context-addition.xml");
   	ServletContextListener listener = new ContextLoaderListener();
   	ServletContextEvent event = new ServletContextEvent(sc);
   	listener.contextInitialized(event);
   	WebApplicationContext context = (WebApplicationContext) sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
   	...忽略
   }

仔细看下 WebApplicationContext的获取方式 当然这个我后面也会在ContextLoaderListener的源码中讲到

这边配置的节点 就是把ContextLoaderListener 加入到 启动的监听列表里面 当程序启动后 会用ServletContextEvent作为参数 去初始化listener 上面的单元测试代码 也写很清楚了

ContextLoaderListener
<!--此类位于Spring-mvc项目中  org/springframework/web/context/ContextLoaderListener.java-->
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
   
    public ContextLoaderListener() {
   
	}
    
    public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}
	
     /**
	 * 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());
	}
} 

ContextLoaderListener 继承了ContextLoader类 实现了ServletContextListener接口

其中contextInitialized,contextDestroyed 方法都是ServletContextListener接口里面的方法

ServletContextListener

那我们看下ServletContextListener 这个类

public interface ServletContextListener extends EventListener {

    public void contextInitialized(ServletContextEvent sce);

    public void contextDestroyed(ServletContextEvent sce);
}

这个接口也是很简单 2个方法 而且接口继承了 一个空的EventListener接口 这样的写法 是为了标识某一类的接口,程序有的地方 判断的时候 用到这个。这样的写法 源码中很多地方用到过。

这边这样写是为了 程序在启动的时候 从xml 读取到Listener节点的配置的类 只有实现了ServletContextListener接口的类 才能加入到监听列表中

那又是怎么加入 怎么去调用的呢?

带着 这个问题 我们一步步去看看

我在上面 提到过 Servlet容器会在每个web程序启动的时候会分配一个ServletContext的上下文,那下面我们看下这个ServletContext 是什么

ServletContext

首先我们看下 这个类 位于什么地方 javax.servlet-api-3.0.1-sources.jar!\javax\servlet\ServletContext.java

这个类是位于javax.servlet-api 这个包里面的 我们都知道 这个包 定义了servlet的标准 web容器都是根据这些标准去实现的

public interface ServletContext {

    <!--Adds the listener with the given class name to this ServletContext.-->
    public void addListener(String className);
    
    <!--adds the given listener to this ServletContext.-->     
    public void addListener(Class <? extends EventListener> listenerClass);
      
    <!--Adds a listener of the given class type to this ServletContext.-->  
    public <T extends EventListener> void addListener(T t);  
}

这个是我截取了 我关心的方法

看到这个方法的时候 我们就能明白 为什么 上面的ServletContextListener 要继承了一个空的EventListener接口了吧!
容器在启动的时候 就是把web.xml 中配置的监听类 加入到 分配到上下文环境ServletContext中的

那我怀着好奇心 继续寻找ServletContext的实现类

ApplicationContext

此类位于tomcat-embed-core-8.5.45-sources.jar!\org\apache\catalina\core\ApplicationContext.java
这个就是在tomcat中的了 tomcat本质就是一个Servlet容器 所以一定是要按照javax.servlet-api里面的定义的标准接口去实现的,话不多说 我们去看下代码是怎么写的,我截取了部分有关我讲的代码 有兴趣的对照着源码 看看

  private final StandardContext context;
  
  public ApplicationContext(StandardContext context) {
        super();
        this.context = context;
        this.service = ((Engine) context.getParent().getParent()).getService();
        this.sessionCookieConfig = new ApplicationSessionCookieConfig(context);

        // Populate session tracking modes
        populateSessionTrackingModes();
    }
  

  public <T extends EventListener> void addListener(T t) {
        if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
            throw new IllegalStateException(
                    sm.getString("applicationContext.addListener.ise",
                            getContextPath()));
        }

        boolean match = false;
        if (t instanceof ServletContextAttributeListener ||
                t instanceof ServletRequestListener ||
                t instanceof ServletRequestAttributeListener ||
                t instanceof HttpSessionIdListener ||
                t instanceof HttpSessionAttributeListener) {
            context.addApplicationEventListener(t);
            match = true;
        }

        if (t instanceof HttpSessionListener ||
                (t instanceof ServletContextListener && newServletContextListenerAllowed)) {
            // Add listener directly to the list of instances rather than to
            // the list of class names.
            context.addApplicationLifecycleListener(t);
            match = true;
        }

        if (match) return;
    }

我们看到 ApplicationContext 初始化的是传入了一个StandardContext对象 而且最终addListener 也是调用的StandardContext类中的addApplicationEventListener方法 看了下上面的代码 监听事件 还做了区分 放入了2个集合中

StandardContext

那我们就来看下StandardContext 是怎么做的

    public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {
        
        //这边是一个对象锁 后面添加applicationListeners的时候会用到
        private final Object applicationListenersLock = new Object();
        
        <!--
        这个是一个存放监听启动类的名称的数组 这边其实才是 启动的时候 我们存放xmL 里面的监听类的数组  一开始 我搞错了  后面我会仔细描述下
        -->
        private String applicationListeners[] = new String[0];
        
        <!--这边2个数组 就是 添加监听对象的时候存放的数组  但是为什么要一个用集合 一个用数组  好奇怪 哈哈 -->
        private List<Object> applicationEventListenersList = new CopyOnWriteArrayList<>(); 
        public void addApplicationEventListener(Object listener) {
            applicationEventListenersList.add(listener);
        }
        
         private Object applicationLifecycleListenersObjects[] = new Object[0]; 
        public void addApplicationLifecycleListener(Object listener) {
            int len = applicationLifecycleListenersObjects.length;
            Object[] newListeners = Arrays.copyOf(
                    applicationLifecycleListenersObjects, len + 1);
            newListeners[len] = listener;
            applicationLifecycleListenersObjects = newListeners;
        } 
         
        <!--
        仔细看下 这个方法时一个重写方法  也就是看到这个方法 我才知道 我一开始 想的错了 
        我以为容器是调用了 上面的2个方法 也就是我们一路跟随的addListener方法 
        将我们XML里面的监听类 加入监听列表的 
        然而并不是这样的-->
        @Override
        public void addApplicationListener(String listener) {
            synchronized (applicationListenersLock) {
                String results[] = new String[applicationListeners.length + 1];
                for (int i = 0; i < applicationListeners.length; i++) {
                    if (listener.equals(applicationListeners[i])) {
                        log.info(sm.getString("standardContext.duplicateListener",listener));
                        return;
                    }
                    results[i] = applicationListeners[i];
                }
                results[applicationListeners.length] = listener;
                applicationListeners = results;
            }
            fireContainerEvent("addApplicationListener", listener);
        }
        
         /**
         * Configure the set of instantiated application event listeners
         * for this Context.
         * @return <code>true</code> if all listeners wre
         * initialized successfully, or <code>false</code> otherwise.
         */
        public boolean listenerStart() {
    
            // Instantiate the required listeners
            String listeners[] = findApplicationListeners();//就是返回 applicationListeners[]数组
            Object results[] = new Object[listeners.length];//这边是存储 我们监听类的是实例化后的对象
            boolean ok = true;
            for (int i = 0; i < results.length; i++) {
                String listener = listeners[i];
                results[i] = getInstanceManager().newInstance(listener);//实例化 我们的监听类
            }
      
            // 这个是吧  实例化的类 拆分成了2个监听数组对象  不知道为啥  ServletContextListener 是单独的一个
            // 这个也对应了  applicationContext中的方法 也是按照这样的类型 拆分了存储的
            ArrayList<Object> eventListeners = new ArrayList<>();
            ArrayList<Object> lifecycleListeners = new ArrayList<>();
            for (int i = 0; i < results.length; i++) {
                if ((results[i] instanceof ServletContextAttributeListener)
                    || (results[i] instanceof ServletRequestAttributeListener)
                    || (results[i] instanceof ServletRequestListener)
                    || (results[i] instanceof HttpSessionIdListener)
                    || (results[i] instanceof HttpSessionAttributeListener)) {
                    eventListeners.add(results[i]);
                }
                if ((results[i] instanceof ServletContextListener)
                    || (results[i] instanceof HttpSessionListener)) {
                    lifecycleListeners.add(results[i]);
                }
            }
    
            for (Object eventListener: getApplicationEventListeners()) {
                eventListeners.add(eventListener);
            }
            setApplicationEventListeners(eventListeners.toArray());
            for (Object lifecycleListener: getApplicationLifecycleListeners()) {
                lifecycleListeners.add(lifecycleListener);
                <!--这边  通过applicationContext 添加的ServletContextListener 这个会被添加的2个监听集合中-->
                if (lifecycleListener instanceof ServletContextListener) {
                    noPluggabilityListeners.add(lifecycleListener);
                }
            }
            setApplicationLifecycleListeners(lifecycleListeners.toArray());
            
            Object instances[] = getApplicationLifecycleListeners();
            if (instances == null || instances.length == 0) {
                return ok;
            }
    
            ServletContextEvent event = new ServletContextEvent(getServletContext());
            ServletContextEvent tldEvent = null;
            <!--这个就是 刚才上面说的  noPluggabilityListeners 的意思是不可插拔的监听列表  这个就是非WebXML 中配置的 ServletContextListener监听类-->
            if (noPluggabilityListeners.size() > 0) {
                noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
                tldEvent = new ServletContextEvent(noPluggabilityServletContext);
            }
            for (int i = 0; i < instances.length; i++) {
                if (!(instances[i] instanceof ServletContextListener))
                    continue;
                ServletContextListener listener = (ServletContextListener) instances[i];
                fireContainerEvent("beforeContextInitialized", listener);
                if (noPluggabilityListeners.contains(listener)) {
                    listener.contextInitialized(tldEvent);
                } else {
                <!--这边就是 调用我们的ContextLoaderListener#contextInitialized的地方-->
                    listener.contextInitialized(event); 
                }
                fireContainerEvent("afterContextInitialized", listener);
            }
            return ok;
        }

看到这边 我们才知道 ContextLoaderListener#contextInitialized 是怎么被调用起来的 listenerStart的方法 就是处理监听类的地方

最后 关注下 addApplicationListener 这个类方法 我的注释 注释上也说了 看到了这个方法 我才明白 我前面想的是错的,
存放我们的XML的监听类的地方 其实是applicationListeners[] 数组

那既然addApplicationListener是重写的方法 他的父类是Context类

最后我有查找了下addApplicationListener方法时 是在哪些地方使用的 这样我们就知道我们的XML中配置的监听类是什么加进入 刚才上面的数组的

最后我在 tomcat-embed-core-8.5.45-sources.jar!\org\apache\catalina\startup\ContextConfig.java 类中找到了这个方法的使用 看名字要也能知道 这个是一个Context的配置类

这也能符合我们的想法 监听类就是在配置Context的是加入的 那我们看下是否 值这样的

ContextConfig
configureContext

    public class ContextConfig implements LifecycleListener{
        
        protected Context context = null;
        
        <!--这个方法在LifecycleBase.java#fireLifecycleEvent被调用-->
        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            context = (Context) event.getLifecycle();
            if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
                configureStart();//开始配置
            } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
                beforeStart();
            } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
                // Restore docBase for management tools
                if (originalDocBase != null) {
                    context.setDocBase(originalDocBase);
                }
            } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
                configureStop();
            } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
                init();
            } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
                destroy();
            }
        }
        
        /**
        * Process a "contextConfig" event for this Context.
        */
        protected synchronized void configureStart() {
            webConfig();//开始webConfig配置
            if (!context.getIgnoreAnnotations()) {
                applicationAnnotationsConfig();
            }
            if (ok) {
                validateSecurityRoles();
            }
            // Configure an authenticator if we need one
            if (ok) {
                authenticatorConfig();
            }
        }
        
        <!--根据webxml的信息 去配置Context-->
        protected void webConfig() {
            WebXml webXml = createWebXml();//创建WebXml对象
            // Parse context level web.xml
            InputSource contextWebXml = getContextWebXmlSource();//获取webXML的地址
            if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
                ok = false;
            }
            
            ServletContext sContext = context.getServletContext();
            // Step 9. Apply merged web.xml to Context
            if (ok) {
                configureContext(webXml);//这边是拿到了webXml 开始配置
            }
        }
        
         private void configureContext(WebXml webxml) {
            for (FilterDef filter : webxml.getFilters().values()) {
                if (filter.getAsyncSupported() == null) {
                    filter.setAsyncSupported("false");
                }
                context.addFilterDef(filter);
            }
            for (FilterMap filterMap : webxml.getFilterMappings()) {
                context.addFilterMap(filterMap);
            }
            
            for (String listener : webxml.getListeners()) {
               context.addApplicationListener(listener);//这边就是 添加我们监听类的地方
            }}

仔细看下这个ContextConfig这个类 从中我们可以看到 Context的创建也是从这个类中完成的 这个记住下

在继续深入下

到了这边 其实 已经讲的差不多了 但是 小伙伴们 一定想知道Tomcat 究竟是怎么去调用到 上面的执行方法的

我们知道listenerStart 中处理了监听事件 那这个又是怎么去运行的呢 和Tomcat 又有什么关系呢,由于篇幅问题 我就不一一列出代码了 大概的描述下

  1. tomcat-embed-core-8.5.45-sources.jar!\org\apache\catalina\startup\Tomcat.java 中有一个Start()方法
  protected Server server;
  public void start() throws LifecycleException {
       getServer();
       getConnector();
       server.start();
   }
   
  public interface Server extends Lifecycle
  

这其中 server.start();方法是Lifecycle接口中
2. tomcat-embed-core-8.5.45-sources.jar!\org\apache\catalina\util\LifecycleBase.java

<!--LifecycleBase这个类实现了Lifecycle接口 -->
public abstract class LifecycleBase implements Lifecycle
{
     public final synchronized void start() throws LifecycleException{
         startInternal();//这边执行startInternal方法
     }
     protected abstract void startInternal() throws LifecycleException;
}


<!--LifecycleMBeanBase类 又继承了LifecycleBase类-->
public abstract class LifecycleMBeanBase extends LifecycleBase

<!--ContainerBase 又继承了LifecycleMBeanBase法-->
public abstract class ContainerBase extends LifecycleMBeanBase implements Container
  1. StandardContext 类 我们应该很熟悉了吧
<!--StandardContext 又继承了ContainerBase类 并且重写startInternal方法 -->
public class StandardContext extends ContainerBase implements Context, NotificationEmitter
{
     protected synchronized void startInternal() throws LifecycleException {
     
     <!--这个方法真的很长  要好好去找listenerStart-->
     
          // Configure and call application event listeners
            if (ok) {
                if (!listenerStart()) {
                    log.error(sm.getString("standardContext.listenerFail"));
                    ok = false;
                }
            }
     }
}

看到这个listenerStart 应该清楚了吧 ~结合我上面讲的 就能串联上了~
到此 我们应该知道了 从Tomcat 到ContextLoaderListener.java 是怎么运行起来的吧

最后还是给大家画个小图吧 简单明了 方便记忆 画图工具用的markdown 自带的 有点low 见谅哈

ContextConfig
StandardContex
Tomcat
调用start
继承
实现
继承
继承
继承
调用
执行
执行
调用
执行
执行
执行
执行
执行
添加
StandardContext 实现类重写startInternal
LifecycleBase#fireLifecycleEvent
LifecycleEvent
ConfigureStart
WebConfig
ConfigureContext
addApplicationListener->applicationListeners
applicationListeners#contextInitialized
StandardContext#startInternal
startInternal->listenerStart
Tomcat
Server
Lifecycle接口start->startInternal
LifecycleBase抽象类
LifecycleMBeanBase抽象类
ContainerBase 抽象类

妈呀 这个图 花的好费劲呀 就这样吧~

总结一下

通过以上的分析 我们知道 ContextLoaderListener 是怎么被加入监听列表 contextInitialized 方法时怎么执行的

写在这边 我感觉我要分篇文章去写了 发现写的有的太细了~

但是我相信 通过这篇文章 你能知道 Servlet容器是怎么处理一个web应用的

最后还是 希望大家多多点赞 后面感觉还要写几篇

下面一篇文章 我就去分析下 ContextLoaderListener 中webApplication的创建

哇偶 断断续续 写了一天~ 真费劲呀 下次要快点了

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值