Tomcat原理系列之四:tomcat与spring容器的关系

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 在springboot盛行的今天,你是否还记得那,在xml文件中配置各种servlet, filter的日子。是否还记得那Tomat+spring+springmvc配置的组合。还有那熟悉的web.xml文件。不知你当时是否有过为何如此配置的疑惑?你又是否已经解惑。不要带着疑惑让他们远去。我们一起回顾

熟悉的web.xml


ContextLoaderListener

为了使用spring我们常见在web.xml中做这样的配置. 配置一个ContextLoaderListener监听器。这个监听器是如何把Tomcat与spring关联的呢?

<?xml version="1.0" encoding="UTF-8"?>  
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">  
    <filter>  
        <filter-name>characterEncodingFilter</filter-name>  
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
        <init-param>  
            <param-name>encoding</param-name>  
            <param-value>UTF-8</param-value>  
        </init-param>  
        <init-param>  
            <param-name>forceEncoding</param-name>  
            <param-value>true</param-value>  
        </init-param>  
    </filter>  
    <filter-mapping>  
        <filter-name>characterEncodingFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
    <!--启用spring-->
    <listener>  
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
    </listener>  
    <!--2、部署applicationContext的xml文件--> 
    <context-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>classpath:spring/applicationContext.xml</param-value>  
    </context-param>  
    <!--3、启用springmvc--> 
    <servlet>  
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <init-param>  
            <param-name>contextConfigLocation</param-name>  
            <param-value>classpath:spring/dispatcher-servlet.xml</param-value>  
        </init-param>  
        <load-on-startup>1</load-on-startup><!--是启动顺序,让这个Servlet随Servletp容器一起启动。-->  
    </servlet>  
    <servlet-mapping>  
        <servlet-name>DispatcherServlet</servlet-name>  
        <url-pattern>/</url-pattern> <!--会拦截URL中带“/”的请求。-->  
    </servlet-mapping>  
    <welcome-file-list><!--指定欢迎页面-->  
        <welcome-file>login.html</welcome-file>  
    </welcome-file-list>  
    <error-page> <!--当系统出现404错误,跳转到页面nopage.html-->  
        <error-code>404</error-code>  
        <location>/nopage.html</location>  
    </error-page>  
    <error-page> <!--当系统出现java.lang.NullPointerException,跳转到页面error.html-->  
        <exception-type>java.lang.NullPointerException</exception-type>  
        <location>/error.html</location>  
    </error-page>  
    <session-config><!--会话超时配置,单位分钟-->  
        <session-timeout>360</session-timeout>  
    </session-config>  
</web-app>


Tomcat的初始化StandardContext.startInternal()


1.Tomcat对web.xml的加载.

要解开Tomcat与spring的关系,我们首先应该先搞懂,Tomcat是在什么位置,如何加载的web.xml文件。

说到这个问题,我们不得不提一下Tomcat启动的三大主线中的start()主线。

Tomcat 层级调用组件的start()方法,执行到StandardContext.startInternal() 时, 在startInternal()方法中调用fireLifecycleEvent()发布一个"configure_start" 事件.

public static final String CONFIGURE_START_EVENT = "configure_start";
  // Notify our interested LifecycleListeners
  fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
  //响应configure_start事件
  protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        for (LifecycleListener listener : lifecycleListeners) {
            listener.lifecycleEvent(event);
        }
    }

web.xml之旅就此开始。

在众多监听器中,有一个ContextConfig监听器,在监听到"configure_start" 事件后, 会执行configureStart()方法. 在configureStart()方法中执行webConfig()开始web.xml解析.

lifecycleEvent()==》
public void lifecycleEvent(LifecycleEvent event) {
    //如果事件类型是configure_start,执行 configureStart()
    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)) {
        ....
    }
}
configureStart()==》
 protected synchronized void configureStart() {
      webConfig();
 }
 //解析web.xml
 protected void webConfig() {
     ....
 }

webConfig:就是Tomcat加载解析web.xml的地方 方法中注释标注了1,2...步骤,详细讲解web.xml加载,解析的过程.讲的很详细,我这里不一一讲了,强烈建议大家去看看源码。 这里主要提一个两个重要的点

parseWebXml(contextWebXml, webXml, false)方法: 这个方法中有一个Digester工具,在Tomcat加载server.xml配置文件的时候就是使用了此工具,解析原理异曲同工。 此处使用WebRuleSet规则,将web.xml文件中的配置读取出来设置到webXml对象中去.configureContext(StandardContext context)方法: 将web.xml文件解析出来的各个组件设置到标准servlet上下文StandardContext中去。 其中就包括我们的filter ,servlet, listener

parseWebXml()===>
public boolean parseWebXml(InputSource source, WebXml dest,
            boolean fragment) {
  Digester digester;
        WebRuleSet ruleSet;
        if (fragment) {
            digester = webFragmentDigester;
            ruleSet = webFragmentRuleSet;
        } else {
            digester = webDigester;
            ruleSet = webRuleSet;
        }
        ....
}
 configure()===>
 private void configureContext(WebXml webxml) {
        //把配置文件中filter设置到context中
  for (FilterDef filter : webxml.getFilters().values()) {
            if (filter.getAsyncSupported() == null) {
                filter.setAsyncSupported("false");
            }
            context.addFilterDef(filter);
        }
        //把配置文件中filterMap设置到context中
        for (FilterMap filterMap : webxml.getFilterMappings()) {
            context.addFilterMap(filterMap);
        }
        ....
        //把配置文件中sevrlet设置到context中
        for (ServletDef servlet : webxml.getServlets().values()) {
            Wrapper wrapper = context.createWrapper();
        }
        ...
        //把配置文件中listener设置到context中
        for (String listener : webxml.getListeners()) {
            context.addApplicationListener(listener);
        }
 }

至此StandardContext已经有了我们配置的listener,fitler,servlet


2.Tomcat 执行了listener.

在加载并将filter,servlet,listener设置到contxt中后,接下来就是执行了。

还是StandardContext.startInternal()方法, 在方法的下半部分按顺序有如下操作:listenerStart() 启动所有的listener.filterStart() 启动所有的filterloadOnStartup(findChildren()) 启动所持有的servletlistenerStart() 启动众多listener,其中就包括我们配置的ContextLoaderListener监听器。

// Configure and call application event listeners
            if (ok) {
                if (!listenerStart()) {
                    log.error(sm.getString("standardContext.listenerFail"));
                    ok = false;
                }
            }
            ...
            // Configure and call application filters
            if (ok) {
                if (!filterStart()) {
                    log.error(sm.getString("standardContext.filterFail"));
                    ok = false;
                }
            } 
            ...
            // Load and initialize all "load on startup" servlets
            if (ok) {
                if (!loadOnStartup(findChildren())){
                    log.error(sm.getString("standardContext.servletFail"));
                    ok = false;
                }
            }                       


3.ContextLoaderListener.contextInitialized(event) spring的初始化.

listenerStart() 方法其实就是调用Listener的contextInitialized()方法

public boolean listenerStart() {
 ...
 for (int i = 0; i < instances.length; i++) {
            if (!(instances[i] instanceof ServletContextListener))
                continue;
            ServletContextListener listener =
                (ServletContextListener) instances[i];
            try {
                fireContainerEvent("beforeContextInitialized", listener);
                if (noPluggabilityListeners.contains(listener)) {
                    listener.contextInitialized(tldEvent);
                } else {
                  //执行listener的初始。传递ServletContextEvent参数
                    listener.contextInitialized(event);//
                }
                fireContainerEvent("afterContextInitialized", listener);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                fireContainerEvent("afterContextInitialized", listener);
                getLogger().error
                    (sm.getString("standardContext.listenerStart",
                                  instances[i].getClass().getName()), t);
                ok = false;
            }
        }
 ...
 }

来到ContextLoaderListener.contextInitialized(ServletContextEvent event)方法中,开始初始化web应用下的IO容器。

public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
 }

initWebApplicationContext方法中,调用了createWebApplicationContext方法来构建一个上下文类,createWebApplicationContext方法中首先调用determineContextClass()来判断上下文类型决定创建哪种上下文, 通常会使用默认策略,根据ContextLoader.properties文件中配置的WebApplicationContext值创建上下文XmlWebApplicationContext对象 至此web容器实例就创建出来了

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

紧接着调用configureAndRefreshWebApplicationContext()方法来初始化bean.就开始了spring的著名的初始化方法refresh()

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);
            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }
        }
    }

总结:ContextLoaderListener通过实现ServletContextListener接口,继承ContextLoader加载器。 把Tomcat与spring连接到了一起。

看懂了Tomcat与spring的关系, 想想配置的DispatcherServlet。其中原理是否有门道了

Spring分别提供了用于启动WebApplicationContext的Servlet和Web容器监听器: org.springframework.web.context.ContextLoaderServlet; org.springframework.web.context.ContextLoaderListener. ContextLoaderListener,ContextLoaderServlet 其实区别不大。流程是一样的


相关文章
|
26天前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
|
24天前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
17天前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
212 4
|
2月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
6月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
2777 114
|
3月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
119 0
|
3月前
|
监控 架构师 NoSQL
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
|
5月前
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
174 32
|
5月前
|
Java 开发者 Spring
Spring框架 - 深度揭秘Spring框架的基础架构与工作原理
所以,当你进入这个Spring的世界,看似一片混乱,但细看之下,你会发现这里有个牢固的结构支撑,一切皆有可能。不论你要建设的是一座宏大的城堡,还是个小巧的花园,只要你的工具箱里有Spring,你就能轻松搞定。
210 9
|
6月前
|
安全 前端开发 Java
Spring Boot 项目中触发 Circular View Path 错误的原理与解决方案
在Spring Boot开发中,**Circular View Path**错误常因视图解析与Controller路径重名引发。当视图名称(如`login`)与请求路径相同,Spring MVC无法区分,导致无限循环调用。解决方法包括:1) 明确指定视图路径,避免重名;2) 将视图文件移至子目录;3) 确保Spring Security配置与Controller路径一致。通过合理设定视图和路径,可有效避免该问题,确保系统稳定运行。
376 0