springmvc dao怎么可以不写实现类_SpringMVC(一)细聊ContextLoaderListener 是怎么被加载的...
点击上方“蓝字”关注我们,每天点亮一个技能点。
本文作者: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 是怎么被加载的...相关推荐
- java加载自己写的类_java 自定义类加载器从磁盘或网络加载类
一.编写自定义类加载器类 package com.mybatis.entity; import java.io.ByteArrayOutputStream; import java.io.File; ...
- Mybatis中DAO层接口没有写实现类,Mapper中的方法和DAO接口方法是怎么绑定到一起的...
参考mybatis入门基础(二)----原始dao的开发和mapper代理开发 其实也就是通过接口名与mapper的id绑定在一起,通过SQL去写实现类,返回数据. 转载于:https://www.c ...
- java无法加载主类_java运行显示“找不到或无法加载主类”!
java运行显示"找不到或无法加载主类"!一般都自然想到是环境没配置好.但是 也可能是,绝对路径的问题. 今天一个不会java的朋友也不知道从哪整了一个java文件让我帮忙运行一下 ...
- java打印已经被加载的类_使用URLClassLoader加载类,不会报错,但被加载类中的内容也没有打印出来...
被加载类和加载类的程序在一个文件夹下: 运行没有任何结果: D:java_exercise>javac URLTest.java D:java_exercise>java URLTest ...
- 找不到或无法加载主类java idea_IDEA——错误: 找不到或无法加载主类 com.Main
在使用IDEA创建了一个普通Java项目,执行main()方法时,程序报错如下: "错误: 找不到或无法加载主类 com.Main Process finished with exit co ...
- java错误 找不到或无法加载主类_java错误:找不到或无法加载主类解决方法
使用java命令运行.class文件时出现:"错误: 找不到或无法加载主类":(推荐:java视频教程) 解决方法: 使用 java 包名+类名执行,如下:在src下使用 java ...
- 写过Mybatis插件?那说说自定义插件是如何加载的吧?
大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外. 我们从插件配置.插件编写.插件运行原理.插件注册与执行拦截的时机.初始化插件.分页插件的原理等六个方面展开阐述. 1 ...
- js如何在当前页面加载springmvc返回的页面_手写SpringMVC学习
前面我们学习了spring框架源码,做了一些自己手写的学习,最近,我们开始学习springMVC框架的学习 ,springMVC框架,相信大家不陌生了,所以这里不做过多的介绍了. SpringMVC以 ...
- 能不做自己写个类,也叫java.lang.String
http://bbs.itheima.com/thread-51369-1-1.html 可以,但是即使你写了这个类,也没有用. 这个问题涉及到加载器的委托机制,在类加载器的结构图(在下面)中, Bo ...
最新文章
- 多激光雷达与相机的外参快速精准标定(arxiv 2021)
- 计算机计组成原理课后,计算机组成原理课后习题课(0001).doc
- Java 多线程之 synchronized 和 volatile 的比较
- Groovy里使用Runnable实现多线程
- codeforces1167 E. Range Deleting(双指针)
- Spring Tool Suite 4 自动提示功能
- 图像处理Pillow详解
- Hive 元数据库表信息
- 谋求“同股不同权”,旷视赴港上市渐近
- php学语法,PHP入门学习——PHP语法
- 程序员使用未经批准的软件时会怎么样?
- 2019年8月19日矩阵
- 11.盛水最多的容器
- 32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式
- 2022电工(初级)操作证考试题库及模拟考试
- 目标管理体系:OKR
- 英语句子组织的5个步骤
- css实现方框内打勾
- 微信小程序真机调试步骤
- Installing vipm-17.0.2018-linux
热门文章
- 77 行代码实现 ABAP ALV 中的双击事件处理
- 介绍一个好用的静态图片合成为 gif 动画的在线网站
- TypeScript reflect-metadata 结合方法装饰器实现的一个自定义语法检查的例子
- 在SAP UI5应用里使用jQuery.ajax读取数据并显示在页面上
- Angular应用ng build的一些边界情况boundary condition
- 通过 Apache Ant 来运行 Tomcat
- 基于CDS view生成的OData服务的metadata是如何加载的
- SAP CRM, C4C和Hybris Commerce的数据迁移策略
- SAP Hybris Commerce product读取的调试截图
- attachment old API read - DB debug