Tomcat源码分析(四):ServletContext应用启动之核心组件初始化

本文主要是介绍Tomcat源码分析(四):ServletContext应用启动之核心组件初始化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述

  • 由我的上篇文章Tomcat源码分析(三):ServletContext应用启动之配置解析可知,在Tomcat启动应用StandardContext时,是先通过listener事件机制的方式,交给ContextConfig,解析web.xml类来获取应用的配置信息,包含过滤器filter,监听器listener,如spring的ContextLoaderListener,servlet,如spring的DispatchServlet,以及session配置等。但是配置解析这边只是创建一个包装类,如过滤器的FilterDef,servlet的StandardWrapper,获取这些组件对应的类的全限定名以及init params等数据保存到包装类中,而不进行实际类对象的创建。

  • 其次在Servlet3.0之后,提供了WebApplicationInitializer接口来支持以编程方式配置web.xml的内容,故在ContextConfig中也会查找应用中的WebApplicationInitializer的实例类,然后保存到StandardContext的initializers中。注意在ContextConfig并不会对WebApplicationInitializer进行解析,即调用其onStartup方法,而是留在StardardContext中执行。

    /*** The ordered set of ServletContainerInitializers for this web application.*/// map的value为WebApplicationInitializer的集合
    private Map<ServletContainerInitializer,Set<Class<?>>> initializers =new LinkedHashMap<>();
    
  • 具体以上对象实例的创建,如servlet, filter,listener等,是之后在StandardContext中继续完成。即在StandardContext的startInternal方法中,以如下顺序继续完成启动和相关类对象实例的创建。

1. context初始化参数加载

  • context初始化参数主要包括两种,一种是在web.xml(或者WebApplicationInitializer,以下提到web.xml的类似)配置context-param设置,如下:

    <context-param>  <param-name>contextConfigLocation</param-name>  <param-value>classpath:spring/applicationContext.xml</param-value>  
    </context-param>
    
  • 另外一种则是在Server.xml的Context节点下面的Parameter节点配置,或者在Context.xml中配置,这些是提供某个param默认值的方式,可以被web.xml中的覆盖。然后在Digester解析规则会添加Parameter解析器,具体为在ContextRuleSet中定义,如下:

    digester.addObjectCreate(prefix + "Context/Parameter","org.apache.tomcat.util.descriptor.web.ApplicationParameter");
    digester.addSetProperties(prefix + "Context/Parameter");
    digester.addSetNext(prefix + "Context/Parameter","addApplicationParameter","org.apache.tomcat.util.descriptor.web.ApplicationParameter");
    
  • 在ContextConfig中已经解析好了在web.xml中配置的param,然后接着在StandardContext中,将以上两种方式获取的param填充到servletContext中:

    /*** Merge the context initialization parameters specified in the application* deployment descriptor with the application parameters described in the* server configuration, respecting the <code>override</code> property of* the application parameters appropriately.*/
    private void mergeParameters() {Map<String,String> mergedParams = new HashMap<>();// web.xmlString names[] = findParameters();for (int i = 0; i < names.length; i++) {mergedParams.put(names[i], findParameter(names[i]));}// Parameter节点ApplicationParameter params[] = findApplicationParameters();for (int i = 0; i < params.length; i++) {if (params[i].getOverride()) {if (mergedParams.get(params[i].getName()) == null) {mergedParams.put(params[i].getName(),params[i].getValue());}} else {mergedParams.put(params[i].getName(), params[i].getValue());}}// 填充到ServletContext中ServletContext sc = getServletContext();for (Map.Entry<String,String> entry : mergedParams.entrySet()) {sc.setInitParameter(entry.getKey(), entry.getValue());}}
    

2. SCI(ServletContainerInitializers)启动onStartup

  • 在ContextConfig查找到应用中的WebApplicationInitializer实现类中,填充保存在StandardContext的initializers集合中,然后在这步调用WebApplicationInitializer的onStartup方法,获取编程方式的应用配置信息。
    // Call ServletContainerInitializers
    for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :initializers.entrySet()) {try {// 在spring中,则是遍历WebApplicationInitializer集合,// 即entry.getValue(),// 调用WebApplicationInitializer的onStartup方法;// getServletContext()为传递当前应用的servletContextentry.getKey().onStartup(entry.getValue(),getServletContext());} catch (ServletException e) {log.error(sm.getString("standardContext.sciFail"), e);ok = false;break;}
    }
    

3. Listener监听器配置与调用

  • 这些一般是应用在web.xml和WebApplicationInitializer中配置的监听器,而不是Tomcat自身添加的监听器,如ContextConfig是Tomcat自身使用Digester解析server.xml时添加的监听器,用于解析配置文件。而这里说的监听器是应用在web.xml或者WebApplicationInitializer中,即配置文件中添加的,类型包括ServletContextListener,HttpSessionListener,ServletRequestListener等,如spring的ContextLoaderListener就是一个ServletContextListener接口的实现类。

  • 实现在StandardContext的listenerStart方法,核心实现如下:即包括:

    1. EventListener和LifeCycleListener的创建
    2. 生命周期监听器LifeCycleListener的调用
    /*** 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() {...// 1. 创建listener对象实例// Instantiate the required listenersString listeners[] = findApplicationListeners();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. 分类:分为EventListener和LifeCycleListener,// 并保存到StandardContext中// EventListener是在应用运行过程中调用的// LifetCycleListener是应用启动,关闭等中调用的// Sort listeners in two arraysArrayList<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]);}}// Listener instances may have been added directly to this Context by// ServletContextInitializers and other code via the pluggability APIs.// Put them these listeners after the ones defined in web.xml and/or// annotations then overwrite the list of instances with the new, full// list.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());...// 3. 调用LifetCycleListener,处理应用启动初始化事件// 这里只调用LifetCycleListener,// 不调用EventListener// Send application start events// Ensure context is not nullgetServletContext();context.setNewServletContextListenerAllowed(false);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++) {// 只调用ServletContextListener// spring的ContextLoaderListener就是// ServletContextListener的实现类if (!(instances[i] instanceof ServletContextListener))continue;ServletContextListener listener =(ServletContextListener) instances[i];try {fireContainerEvent("beforeContextInitialized", listener);if (noPluggabilityListeners.contains(listener)) {listener.contextInitialized(tldEvent);} else {// 调用contextInitialized方法// 可以通过event获取ServletContext// spring的ContextLoaderListener// 是在这里加载spring的root WebApplicationContet// 并作为一个attribute填充到ServletContext中listener.contextInitialized(event);}fireContainerEvent("afterContextInitialized", listener);}...}return (ok);
    }
    

4. 过滤器Filter的实例化

  • 遍历StandardContext的filterDefs集合,创建ApplicationFilterConfig,然后填充到filterConfigs中。其中在创建ApplicationFilterConfig时,会创建filter实例并进行初始化。
    /*** Configure and initialize the set of filters for this Context.* @return <code>true</code> if all filter initialization completed* successfully, or <code>false</code> otherwise.*/
    public boolean filterStart() {if (getLogger().isDebugEnabled()) {getLogger().debug("Starting filters");}// Instantiate and record a FilterConfig for each defined filterboolean ok = true;synchronized (filterConfigs) {filterConfigs.clear();for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {String name = entry.getKey();if (getLogger().isDebugEnabled()) {getLogger().debug(" Starting filter '" + name + "'");}try {// 创建ApplicationFilterConfig实例,// 并放到StandardContext的filterConfigs中ApplicationFilterConfig filterConfig =new ApplicationFilterConfig(this, entry.getValue());filterConfigs.put(name, filterConfig);} catch (Throwable t) {t = ExceptionUtils.unwrapInvocationTargetException(t);ExceptionUtils.handleThrowable(t);getLogger().error(sm.getString("standardContext.filterStart", name), t);ok = false;}}}return ok;
    }/*** Construct a new ApplicationFilterConfig for the specified filter* definition.** @param context The context with which we are associated* @param filterDef Filter definition for which a FilterConfig is to be*  constructed** @exception ClassCastException if the specified class does not implement*  the <code>javax.servlet.Filter</code> interface* @exception ClassNotFoundException if the filter class cannot be found* @exception IllegalAccessException if the filter class cannot be*  publicly instantiated* @exception InstantiationException if an exception occurs while*  instantiating the filter object* @exception ServletException if thrown by the filter's init() method* @throws NamingException* @throws InvocationTargetException* @throws SecurityException* @throws NoSuchMethodException* @throws IllegalArgumentException*/
    ApplicationFilterConfig(Context context, FilterDef filterDef)throws ClassCastException, ClassNotFoundException, IllegalAccessException,InstantiationException, ServletException, InvocationTargetException, NamingException,IllegalArgumentException, NoSuchMethodException, SecurityException {super();this.context = context;this.filterDef = filterDef;// Allocate a new filter instance if necessaryif (filterDef.getFilter() == null) {getFilter();} else {this.filter = filterDef.getFilter();// 创建filter实例getInstanceManager().newInstance(filter);// 初始化filter,如添加filter配置的param等initFilter();}
    }
    

5. Servlet的实例化

  • 主要为查找配置的Servlet中onStartup > 0的serlvet,然后加载Servlet对应的类,创建servlet实例并初始化。具体为通过StandardWrapper的load方法来完成。
    /*** Load and initialize all servlets marked "load on startup" in the* web application deployment descriptor.** @param children Array of wrappers for all currently defined*  servlets (including those not declared load on startup)* @return <code>true</code> if load on startup was considered successful*/
    public boolean loadOnStartup(Container children[]) {// 筛选loadOnStartup > 0的servlet// Collect "load on startup" servlets that need to be initializedTreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();for (int i = 0; i < children.length; i++) {Wrapper wrapper = (Wrapper) children[i];int loadOnStartup = wrapper.getLoadOnStartup();if (loadOnStartup < 0)continue;Integer key = Integer.valueOf(loadOnStartup);ArrayList<Wrapper> list = map.get(key);if (list == null) {list = new ArrayList<>();map.put(key, list);}list.add(wrapper);}// Load the collected "load on startup" servletsfor (ArrayList<Wrapper> list : map.values()) {for (Wrapper wrapper : list) {try {// 通过StandardWrapper的load方法来完成// 类加载,实例创建,实例初始化wrapper.load();} catch (ServletException e) {getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",getName(), wrapper.getName()), StandardWrapper.getRootCause(e));// NOTE: load errors (including a servlet that throws// UnavailableException from the init() method) are NOT// fatal to application startup// unless failCtxIfServletStartFails="true" is specifiedif(getComputedFailCtxIfServletStartFails()) {return false;}}}}return true;}/*** Load and initialize an instance of this servlet, if there is not already* at least one initialized instance.  This can be used, for example, to* load servlets that are marked in the deployment descriptor to be loaded* at server startup time.* <p>* <b>IMPLEMENTATION NOTE</b>:  Servlets whose classnames begin with* <code>org.apache.catalina.</code> (so-called "container" servlets)* are loaded by the same classloader that loaded this class, rather than* the classloader for the current web application.* This gives such classes access to Catalina internals, which are* prevented for classes loaded for web applications.** @exception ServletException if the servlet init() method threw*  an exception* @exception ServletException if some other loading problem occurs*/
    @Override
    public synchronized void load() throws ServletException {instance = loadServlet();if (!instanceInitialized) {initServlet(instance);}if (isJspServlet) {StringBuilder oname = new StringBuilder(getDomain());oname.append(":type=JspMonitor");oname.append(getWebModuleKeyProperties());oname.append(",name=");oname.append(getName());oname.append(getJ2EEKeyProperties());try {jspMonitorON = new ObjectName(oname.toString());Registry.getRegistry(null, null).registerComponent(instance, jspMonitorON, null);} catch( Exception ex ) {log.info("Error registering JSP monitoring with jmx " +instance);}}
    }
    

这篇关于Tomcat源码分析(四):ServletContext应用启动之核心组件初始化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/957119

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听