zoukankan      html  css  js  c++  java
  • Tomcat是如何加载Spring和SpringMVC及Servlet相关知识

    概述

    大家是否清楚,Tomcat是如何加载Spring和SpringMVC,今天我们就弄清下这个过程(记录最关键的东西)

    其中会涉及到大大小小的知识,包括加载时候的设计模式,Servlet知识等,看了你肯定有所收获~

    Tomcat

    tomcat是一种Java写的Web应用服务器,也被称为Web容器,专门运行Web程序

    tomcat启动

    tomcat启动了之后会在操作系统中生成一个Jvm(Java虚拟机)的进程,从配置监听端口(默认8080)监听发来的HTTP/1.1协议的消息

    默认配置文件这样

     当Tomcat启动完成后,它就会加载其安装目录下webapps里的项目(放war包会自动解压成项目)

    小提问:webapps里多个项目,是运行在同一个JVM上吗

    是运行在同一个JVM上的(Tomcat启动时创建的那个),多个项目就是多个线程,之所以项目间数据不共享,是因为类加载器不一样的缘故

    加载Web程序(Spring+SpringMVC框架)

    tomcat启动完毕后,最关键的是生成了ServletContext(Tomcat的上下文),然后会根据webapps项目里的web.xml进行加载项目

    下面是一个SpringMVC+Spring项目的部分web.xml

     1 <!--以下为加载Spring需要的配置-->
     2 <!--Spring配置具体参数的地方-->
     3 <context-param>
     4   <param-name>contextConfigLocation</param-name>
     5   <param-value>
     6     classpath:applicationContext.xml
     7   </param-value>
     8 </context-param>
     9 <!--Spring启动的类-->
    10 <listener>
    11   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    12 </listener>
    13 
    14  <!--以下为加载SpringMVC需要的配置-->
    15 <servlet>
    16   <servlet-name>project</servlet-name>
    17   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    18   <load-on-startup>1</load-on-startup>   <!--servlet被加载的顺序,值越小优先级越高(正数)-->
    19 
    20   <servlet-mapping>
    21         <servlet-name>project</servlet-name>
    22         <url-pattern>*.html</url-pattern>
    23   </servlet-mapping>
    24 </servlet>

    初始化Spring

    tomcat首先会加载进ContextLoaderListener,然后将applicationContext.xml里写的参数注入进去,来完成一系列的Spring初始化(如各种bean,数据库资源等等)

    这里就是经常听到的Ioc容器的初始化了,我们搜索这个类发现以下代码

     1 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
     2     //省略其他方法
     3 
     4     /**
     5      * Initialize the root web application context.
     6      */
     7     @Override
     8     public void contextInitialized(ServletContextEvent event) {
     9         initWebApplicationContext(event.getServletContext());
    10     }
    11 
    12     //省略其他方法
    13 }

    这里最重要的是通过ServletContext,初始化属于Spring的上下文WebApplicationContext,并将其存放在ServletContext

    WebApplicationContext多重要老铁们都懂得,我们经常用webApplicationContext.getBean()来获取被Spring管理的类,所以这也是IOC容器的核心

    Spring采用这种监听器来启动自身的方法,也是一种设计模式,叫观察者模式

    整个过程是这样的,Tomcat加载webapps项目时,先通过反射加载在web.xml标明的类(通通放入一个数组)

    到某个时刻,我tomcat(事件源,事件的起源)会发起一个叫ServletContextEvent的事件(里面带着各种参数)

    凡是实现了ServletContextListener接口的类,我都会调用里面的contextInitialized方法,并把这个事件参数传进去

    咳咳,现在我看看这个数组里有没符合条件的(遍历),发现真有实现这个接口的(类 instanceof 接口),就调用contextInitialized方法

    于是Spring就被动态加载进来了~~

    题外话:

    加载一个类,可以用用完整的类名,通过java反射加载,Class.forName(类名)

    也能直接new 一个类 来加载

    初始化SpringMVC

    看配置文件,标签是servlet,我们得首先了解下servlet是什么东东

    Servlet简介

    Servlet是一个接口,为web通信而生(说白了就是一堆sun公司的大佬们开会,拍板造出的类,有固定的几个方法)

    tomcat有一套定义好的程序(其实不只是tomcat,能跑java写的web应用服务器如Jetty等,都有这固定程序)

    1.当tomcat加载进来一个类时,如果它实现了Servlet接口,那么会记载到一个Map里,然后执行一次init()方法进行Servlet初始化

    2.当tomcat收到浏览器的请求后,就会在Map里找对应路径的Servlet处理,路径就是写在<url-pattern>标签里的参数,调用service()这个方法

    3.当Servlet要被销毁了,就调用一次destroy()方法

    各位看到这是不是感觉相识,跟Spring加载差不多嘛,都是实现了一个接口后就被命运(tomcat)安排~~

    当然,我们自己实现Servlet接口太鸡儿麻烦了,于是有HttpServlet(一个抽象类)帮我们实现了大部分方法(包含http头的设置,doXXX方法判断等等)

    所以我们只要继承HttpServlet就实现几个方法就能用啦

    SpringMVC加载

    为什么要讲Servlet,因为SpringMVC的核心就是DispatcherServlet(前置控制器),如图

     DispatcherServlet由SpringMVC的实现,已经实现的很棒棒了,我们不需要再动它

    tomcat从web.xml中加载DispatcherServlet,然后会调用它的init()方法

    Servlet配置文件默认在/WEB-INF/<servlet-name>-servlet.xml,所以现在默认叫project-servlet.xml

    当然,也能自己指定文件

     1  <!--以下为加载SpringMVC需要的配置-->
     2 <servlet>
     3   <servlet-name>project</servlet-name>
     4   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
     5   <load-on-startup>1</load-on-startup>
     6 
     7    <!--指定配置文件-->
     8   <init-param>
     9                    <param-name>contextCOnfigLocation</param-name>
    10                    <param-value>classPath:spring-servlet.xml</param-value>
    11   </init-param>
    12 
    13   <servlet-mapping>
    14         <servlet-name>project</servlet-name>
    15         <url-pattern>*.html</url-pattern>
    16   </servlet-mapping>
    17 </servlet>

    当SpringMVC加载好后,浏览器有请求过来,如果是.html结尾,tomcat就会交给DispatcherServlet处理

    而DispatcherServlet会根据路径找到对应的处理器处理,可以理解为我们写的Controller接收到了(具体SpringMVC处理流程会写一篇博文)

    至此,浏览器发送请求,到执行我们写的代码这个流程就结束了  ~~撒花~~

    Spring和SpringMVC的容器问题

    既然讲到tomcat加载这两个框架,大家发现没有,在web.xml中,Spring加载是写在DispatcherServlet加载前面的

    我们不妨来看看DispatcherServlet的初始化方法,由于DispatcherServlet是通过层层继承而来的,所以初始化的方法也变成了

     1 public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
     2 
     3     //其他方法
     4 
     5     @Override
     6     public final void init() throws ServletException {
     7         
     8         //其他代码
     9 
    10         // Let subclasses do whatever initialization they like.
    11         initServletBean();
    12     }
    13 
    14     protected void initServletBean() throws ServletException {
    15     }
    16 
    17     //其他方法
    18 }
    19 
    20 public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    21 
    22     //其他方法
    23 
    24     @Override
    25     protected final void initServletBean() throws ServletException {
    26         //其他代码
    27 
    28         try {
    29             this.webApplicationContext = initWebApplicationContext();
    30             initFrameworkServlet();
    31         }
    32         catch (ServletException | RuntimeException ex) {
    33             logger.error("Context initialization failed", ex);
    34             throw ex;
    35         }
    36 
    37         //其他代码
    38     }
    39 
    40     protected WebApplicationContext initWebApplicationContext() {
    41 
    42         //获得了Spring创造的webApplicationContext,关键
    43         WebApplicationContext rootContext =
    44                 WebApplicationContextUtils.getWebApplicationContext(getServletContext()); 
    45         WebApplicationContext wac = null;
    46 
    47         if (this.webApplicationContext != null) {
    48             // A context instance was injected at construction time -> use it
    49             wac = this.webApplicationContext;
    50             if (wac instanceof ConfigurableWebApplicationContext) {
    51                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
    52                 if (!cwac.isActive()) {
    53                     // The context has not yet been refreshed -> provide services such as
    54                     // setting the parent context, setting the application context id, etc
    55                     if (cwac.getParent() == null) {
    56                         // The context instance was injected without an explicit parent -> set
    57                         // the root application context (if any; may be null) as the parent
    58                         cwac.setParent(rootContext);      //设置父上下文
    59                     }
    60                     configureAndRefreshWebApplicationContext(cwac);
    61                 }
    62             }
    63         }
    64         
    65         //其他代码
    66     }
    67 
    68     //其他方法
    69 }

    我们可以看到,HttpServlet将init()实现了,留下了initServletBean()抽象方法

    而FrameworkServlet实现了initServletBean()方法并定义成final(不允许重写),在此方法中调用了initWebApplicationContext()

    而initWebApplicationContext()中说明了如果tomcat里存在webapplication就获取它,然后将其设置为SpringMVC的父上下文

    至此DispatcherServlet初始化完成(当然我省略了其他的初始化做的事)

    因此Spring和SpringMVC容器之间是父子关系,由于子容器可以访问父容器的内容,而反过来不行,所以不要想在Service里自动注入Controller这种操作

    因此会有下面情况

    Spring配置进行扫包的时候,如果将Controller也扫进来了会怎样

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3     xmlns:context="http://www.springframework.org/schema/context" 
     4     ...../ 此处省略>
     5 
     6     <!-- 扫描全部包 -->
     7     <context:component-scan base-package="com.shop"></context:component-scan>
     8     
     9   <!--本来正确写法是这样的>
    10   <!--
    11     <!-- 扫描包Service实现类 --> 
    12     <context:component-scan base-package="com.shop.service"></context:component-scan>
    13   >
    14 
    15 </beans>

    那么,Controller将进入Spring的上下文,SpringMVC里就没Controller了,到时候有请求给DispatcherServlet时,就会找不到Controller而404

    小提问:那么SpringMVC扫描所有的包可以吗

    这个是可以的,SpringMVC不需要Spring也能使用,但是加入Spring是为了更好的兼容其他的框架(数据库框架等等,例如SpringMVC扫了service和dao,那是不支持事务的)

    但是如果用了Spring就不能这样做,包括Spring扫Controller,SpringMVC也扫一次Controller这些操作,会出现各种奇怪的问题

     

    小提问:SpringMVC和Spring为什么要两个容器呢,Springboot一个容器就搞定了呀

    其实这和Spring设计理念相关,毕竟一个框架不可能做完一切,总要和第三方框架配合。假设公用一个容器,那么多个第三方框架的Bean冲突概率就很高,由每个第三方框架维护自己的容器,将Spring作为父类容器,则能和平相处。

    Springboot之所以被大家喜爱,是因为它遵循默认大于配置,它帮我们配置好了常用的框架(大部分是写好了配置类,更改了框架的启动注册流程),底层该用SpringMVC还是SpringMVC,该用Spring还是Spring。所以可以在框架启动注册时,就用一个容器把相关内容合理的安排好(这不是全部框架都支持,例如SpringCloud就有自己的独有的容器,并把它设置为Springboot的父容器~)

    以上就讲完啦,希望大家点点赞,或者一键3连不迷路~~

    参考:https://www.cnblogs.com/wyq1995/p/10672457.html

    参考:https://blog.csdn.net/s740556472/article/details/54879954

    学习和点赞一样,可不能下次一定啊! 感谢你的三连~
  • 相关阅读:
    1 绪论
    3.4 向量空间及其子空间的的基与维数
    3.3 极大线性无关组以及&向量的秩
    3.2 线性相关与线性无关的向量组
    3.1 n维向量空间及其子空间
    2.6 拉普拉斯定理
    2.5 克拉默法则
    2.4 行列式按行(列)展开
    2.3 行列式的性质
    2.2 n阶行列式的定义
  • 原文地址:https://www.cnblogs.com/top-housekeeper/p/14105297.html
Copyright © 2011-2022 走看看