点击上方“蓝字”关注我们,每天点亮一个技能点。

本文作者:burgxun

云析学院VIP学员

前言

为什么要写博客呢?其实还是源于我在知乎上面的 看到的一个回答。问题是这样的:生活中最浪费时间的事情有哪些?我看到下面的一个贴子回答是:学而不思,犹豫不决,看到这个 我想到 哇偶 总结的好到位!好了 废话不多说,为什么要写Spring MVC 呢 当然是因为我博主我最近在看Spring MVC 的源码了!为什么开篇想讲 2个容器呢?随着Spring Boot 的越来越流行 我们之前配置XML 的方式也逐渐 被舍弃。我们刚使用Spring MVC的时候 一定也知道怎么去标准的配置 比如ContextLoaderListener DispatcherServlet等等 那为什么要有这些配置,这些配置又去怎么运行的呢?今天 我就从自己的理解 去分析下 , 理解不当的地方 请各位见谅,留言指出 多谢!PS: 这篇文章 有点跑题 重点讲了一个程序 tomcat 是怎么去加载web配置 又是怎么样将我们的ContextLoaderListener监听类加入到监听列表中的 最后串联了下整个Servlet 容器怎么去启动一个应用程序的~下面一片文章 我会重点去看下ContextLoaderListener 里面怎么去创建WebApplication容器的,这里面我也走了一点弯路 一开始以为 我们的ContextLoaderListene 是通过ApplicationContext中的addListener添加的 最终 我一步步发现 我想错了!

标准配置

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

                org.springframework.web.context.ContextLoaderListener                    contextConfigLocation        WEB-INF/configs/spring/applicationContext.xml                    mvc-dispatcher        org.springframework.web.servlet.DispatcherServlet                                    contextConfigLocation            /WEB-INF/configs/spring/mvc-dispatcher-servlet.xml                1                mvc-dispatcher        /        

从上面的我的注释中 也可以看看出 一个是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

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 {        public void addListener(String className);             public void addListener(Class  extends EventListener> listenerClass);          public  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  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();                private String applicationListeners[] = new String[0];                private List 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;        }                 @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 true if all listeners wre         * initialized successfully, or false 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 eventListeners = new ArrayList<>();            ArrayList 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);                                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;                        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 {                                    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;                @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();            }        }                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

public abstract class LifecycleBase implements Lifecycle{     public final synchronized void start() throws LifecycleException{         startInternal();//这边执行startInternal方法     }     protected abstract void startInternal() throws LifecycleException;}public abstract class LifecycleMBeanBase extends LifecycleBasepublic abstract class ContainerBase extends LifecycleMBeanBase implements Container

3.StandardContext 类 我们应该很熟悉了吧

public class StandardContext extends ContainerBase implements Context, NotificationEmitter{     protected synchronized void startInternal() throws LifecycleException {               // Configure and call application event listeners            if (ok) {                if (!listenerStart()) {                    log.error(sm.getString("standardContext.listenerFail"));                    ok = false;                }            }     }}

看到这个listenerStart 应该清楚了吧 ~结合我上面讲的 就能串联上了~到此 我们应该知道了 从Tomcat 到ContextLoaderListener.java 是怎么运行起来的吧最后还是给大家画个小图吧 简单明了 方便记忆 画图工具用的markdown 自带的 有点low 见谅哈

总结一下

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

●带你领略史上最全—编译部署EasyDarwin源码【二次开发】【Linux】

●Thread类源码(2)

●Thread类源码分析(1)

●线程池(1)——线程池的使用

Java技术直播

点击图片直达课堂

觉得有帮助的话,点个“在看”吧!

springmvc dao怎么可以不写实现类_SpringMVC(一)细聊ContextLoaderListener 是怎么被加载的...相关推荐

  1. java加载自己写的类_java 自定义类加载器从磁盘或网络加载类

    一.编写自定义类加载器类 package com.mybatis.entity; import java.io.ByteArrayOutputStream; import java.io.File; ...

  2. Mybatis中DAO层接口没有写实现类,Mapper中的方法和DAO接口方法是怎么绑定到一起的...

    参考mybatis入门基础(二)----原始dao的开发和mapper代理开发 其实也就是通过接口名与mapper的id绑定在一起,通过SQL去写实现类,返回数据. 转载于:https://www.c ...

  3. java无法加载主类_java运行显示“找不到或无法加载主类”!

    java运行显示"找不到或无法加载主类"!一般都自然想到是环境没配置好.但是 也可能是,绝对路径的问题. 今天一个不会java的朋友也不知道从哪整了一个java文件让我帮忙运行一下 ...

  4. java打印已经被加载的类_使用URLClassLoader加载类,不会报错,但被加载类中的内容也没有打印出来...

    被加载类和加载类的程序在一个文件夹下: 运行没有任何结果: D:java_exercise>javac URLTest.java D:java_exercise>java URLTest ...

  5. 找不到或无法加载主类java idea_IDEA——错误: 找不到或无法加载主类 com.Main

    在使用IDEA创建了一个普通Java项目,执行main()方法时,程序报错如下: "错误: 找不到或无法加载主类 com.Main Process finished with exit co ...

  6. java错误 找不到或无法加载主类_java错误:找不到或无法加载主类解决方法

    使用java命令运行.class文件时出现:"错误: 找不到或无法加载主类":(推荐:java视频教程) 解决方法: 使用 java 包名+类名执行,如下:在src下使用 java ...

  7. 写过Mybatis插件?那说说自定义插件是如何加载的吧?

    大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外. 我们从插件配置.插件编写.插件运行原理.插件注册与执行拦截的时机.初始化插件.分页插件的原理等六个方面展开阐述. 1 ...

  8. js如何在当前页面加载springmvc返回的页面_手写SpringMVC学习

    前面我们学习了spring框架源码,做了一些自己手写的学习,最近,我们开始学习springMVC框架的学习 ,springMVC框架,相信大家不陌生了,所以这里不做过多的介绍了. SpringMVC以 ...

  9. 能不做自己写个类,也叫java.lang.String

    http://bbs.itheima.com/thread-51369-1-1.html 可以,但是即使你写了这个类,也没有用. 这个问题涉及到加载器的委托机制,在类加载器的结构图(在下面)中, Bo ...

最新文章

  1. 多激光雷达与相机的外参快速精准标定(arxiv 2021)
  2. 计算机计组成原理课后,计算机组成原理课后习题课(0001).doc
  3. Java 多线程之 synchronized 和 volatile 的比较
  4. Groovy里使用Runnable实现多线程
  5. codeforces1167 E. Range Deleting(双指针)
  6. Spring Tool Suite 4 自动提示功能
  7. 图像处理Pillow详解
  8. Hive 元数据库表信息
  9. 谋求“同股不同权”,旷视赴港上市渐近
  10. php学语法,PHP入门学习——PHP语法
  11. 程序员使用未经批准的软件时会怎么样?
  12. 2019年8月19日矩阵
  13. 11.盛水最多的容器
  14. 32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式
  15. 2022电工(初级)操作证考试题库及模拟考试
  16. 目标管理体系:OKR
  17. 英语句子组织的5个步骤
  18. css实现方框内打勾
  19. 微信小程序真机调试步骤
  20. Installing vipm-17.0.2018-linux

热门文章

  1. 77 行代码实现 ABAP ALV 中的双击事件处理
  2. 介绍一个好用的静态图片合成为 gif 动画的在线网站
  3. TypeScript reflect-metadata 结合方法装饰器实现的一个自定义语法检查的例子
  4. 在SAP UI5应用里使用jQuery.ajax读取数据并显示在页面上
  5. Angular应用ng build的一些边界情况boundary condition
  6. 通过 Apache Ant 来运行 Tomcat
  7. 基于CDS view生成的OData服务的metadata是如何加载的
  8. SAP CRM, C4C和Hybris Commerce的数据迁移策略
  9. SAP Hybris Commerce product读取的调试截图
  10. attachment old API read - DB debug