搜尋
首頁Javajava教程web.xml SpringBoot打包可執行Jar運行SpringMVC的方法是什麼

部署到webapps目錄啟動

本文使用的Spring版本為Spring6,SpringBoot版本為3,JDK為17,可能會和之前有細微不同,但整體流程差不太大。

如果部署應用到tomcat webapps目錄下面啟動,則需要在專案中配置web.xml檔案

web.xml檔案

配置Spring應用上下文

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/application-context.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

context-param

在Web應用程式的上下文範圍內,可以使用context-param標籤來設定初始化參數。這些參數可以在整個Web應用程式中使用,並且可以透過ServletContext物件的getInitParameter()方法來取得。

ContextLoaderListener

ContextLoaderListener實作了ServletContextListener接口,這個介面是tomcat留給應用程式初始化上下文環境的接口,用於在Web應用程式啟動時載入ApplicationContext。

ServletContextListener有兩個預設方法

// 在所有的servlet和filter初始化之前被调用
default public void contextInitialized(ServletContextEvent sce) {
}
// 在所有的servlet和filter销毁之后被调用
default public void contextDestroyed(ServletContextEvent sce) {
}

ContextLoaderListener也繼承了ContextLoader類,所有的context作業都在此類進行。

ContextLoaderListener實作contextInitialized方法,然後呼叫父類別ContextLoader的initWebApplicationContext方法,把ServletContext傳進去。

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

初始化Spring Context。

initWebApplicationContext方法關鍵程式碼

...
if (this.context == null) {
    // 创建ApplicationContext
    this.context = createWebApplicationContext(servletContext);
}
...
// 刷新ApplicationContext
configureAndRefreshWebApplicationContext(cwac, servletContext);
...
// 将当前ApplicationContext添加到ServletContext的属性中,后面有用再说
// String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
...

建立ApplicationContext

在createWebApplicationContext方法中,先呼叫determineContextClass方法決定使用哪個ApplicationContext,找到之後,實例化。

determineContextClass這個方法,主要是確定使用的ApplicationContext,首先從web.xml中加載,如果使用者有定義,直接使用使用者自訂的。

String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);

web.xml中配置如下,

<context-param>
    <param-name>contextClass</param-name>
    <param-value>com.xxx.XxxContext</param-value>
</context-param>

如果沒有配置,則使用Spring預設的XmlWebApplicationContext類別。

這個類別在ContextLoader同路徑包下面的ContextLoader.properties檔案中定義。

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

配置與刷新ApplicationContext

configureAndRefreshWebApplicationContext關鍵程式碼

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,ServletContext sc) {
    // ...
    // 获取web.xml中配置的contextConfigLocation参数
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }
    // ...
    // 刷新上下文
    wac.refresh();
}

至此Tomcat已經啟動Spring環境了,後續就是Spring的初始化流程,這裡不再敘述。

初始化DispatcherServlet

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>/WEB-INF/spring/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

此處的contextConfigLocation屬於DispatcherServlet的父類別FrameworkServlet,主要用來載入SpringMVC相關的配置,範例如下:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
    <!-- 扫描控制器和其他组件 -->
    <context:component-scan base-package="com.example.controller" />
    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>
    <!-- 启用Spring MVC注解支持 -->
    <mvc:annotation-driven />
</beans>

DispatcherServlet類別圖

web.xml SpringBoot打包可執行Jar運行SpringMVC的方法是什麼

##可以看到DispatcherServlet實作了Servlet接口,Servlet接口中有init方法,SpringMVC的配置就是在初始化的時候被載入的。

關鍵程式碼在HttpServletBean.init()和FrameworkServlet.initServletBean()方法中。

HttpServletBean.init()

public final void init() throws ServletException {
   // Set bean properties from init parameters.
   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
         initBeanWrapper(bw);
         bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet &#39;" + getServletName() + "&#39;", ex);
         }
         throw ex;
      }
   }
   // Let subclasses do whatever initialization they like.
   initServletBean();
}

FrameworkServlet.initServletBean()

protected final void initServletBean() throws ServletException {
    ...
    // 在这里初始化ApplicationContext 
    this.webApplicationContext = initWebApplicationContext();
    // 初始化servlet
    initFrameworkServlet();
}

FrameworkServlet.initWebApplicationContext()

protected WebApplicationContext initWebApplicationContext() {
    // 此处获取根容器,就是Spring初始化的XmlWebApplicationContext,
    // 在上面把它添加到了ServletContext的属性中,标记根容器,这里把它获取出来
    // String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    // servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;
   // 此时webApplicationContext还是null,因为DispatchServlet是被tomcat创建的,需要无参构造器
   // 构造器中没有设置webApplicationContext的代码,所以此时webApplicationContext还是null
   // 注意:在SpringBoot使用嵌入式Tomcat时,这个webApplicationContext不为null,因为FrameworkServlet还
   // 实现了ApplicationContextAware接口,所以当SpringBoot的上下文准备好之后,会回调setApplicationContext方法
   // 注入ApplicationContext,后面在细说 
   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
         // The context has not yet been refreshed -> provide services such as
         // setting the parent context, setting the application context id, etc
         if (cwac.getParent() == null) {
            // The context instance was injected without an explicit parent -> set
            // the root application context (if any; may be null) as the parent
            cwac.setParent(rootContext);
         }
         configureAndRefreshWebApplicationContext(cwac);
      }
   }
   if (wac == null) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      // 此处主要是获取web.xml配置的WebApplicationContext
      // 可以通过设置参数contextAttribute来设置加载SpringMVC的ApplicationContext
      // 比如下面这样。除非项目中有多个WebApplicationContext,需要使用其他WebApplicationContext才会用到
      // 一般都是null
      // <context-param>
      //    <param-name>contextAttribute</param-name>
      //    <param-value>myWebApplicationContext</param-value>
      // </context-param>
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // 现在进入到创建SpringMVC的ApplicationContext流程
      // 也就是加载contextConfigLocation定义的xml文件 
      // No context instance is defined for this servlet -> create a local one
      wac = createWebApplicationContext(rootContext);
   }
   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
         // 初始化策略对象
         // 比如:HandlerMapping,HandlerAdapter,ViewResolver等等 
         onRefresh(wac);
      }
   }
   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }
   return wac;
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
   // SpringMVC所使用的contextClass,可以在<servlet>标签下设置
   // <init-param>
   //    <param-name>contextClass</param-name>
   //    <param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>
   // </init-param>
   // 默认为XmlWebApplicationContext
   Class<?> contextClass = getContextClass();
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException(
            "Fatal initialization error in servlet with name &#39;" + getServletName() +
            "&#39;: custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
   }
   // 实例化ApplicationContext
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
   // 设置环境参数
   wac.setEnvironment(getEnvironment());
   // 设置父容器为Spring的ApplicationContext
   wac.setParent(parent);
   // 获取SpringMVC的contextConfigLocation文件
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
      wac.setConfigLocation(configLocation);
   }
   // 配置并刷新ApplicationContext
   configureAndRefreshWebApplicationContext(wac);
   return wac;
}
##DispatchServlet初始化完成完成完成。

#為什麼需要父子容器

父子容器的作用主要是分割框架邊界和實作bean的複用。

在J2EE三層架構中,在service層我們一般使用Spring框架,而在web層則有多種選擇,如Spring MVC、Struts等。為了讓web層能夠使用service層的bean,我們需要將service層的容器當作web層容器的父容器,這樣就可以實現框架的整合。

  • 父子容器的作用在於,當我們嘗試從子容器(Servlet WebApplicationContext)中取得一個bean時,如果找不到,則會委派給父容器(Root WebApplicationContext)進行查找。重複定義相同的bean在各個子容器中被避免,從而提高了程式碼的複用性和可維護性。

  • 接收請求

  • 請求先進入doService,然後呼叫doDispatch進行處理。

doDispatch關鍵程式碼

...
// 首先根据当前请求HttpServletRequest,遍历所有的HandlerMapping执行handle方法,返回可用的HandlerExecutionChain对象。
mappedHandler = getHandler(processedRequest);
// 然后根据handler获取支持的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 执行HandlerInterceptor.preHandle,在controller的方法被调用前执行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}
// 执行controller方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 执行HandlerInterceptor.postHandle,在controller的方法被调用后执行
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 渲染结果到视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

HandlerMapping是request與handler object之間的映射,它能根據request找到對應的handler。 handler object可以是任意類型,例如@Controller註解的類,或是實作了Controller介面的類,或是實作了HttpRequestHandler介面的類等。

  • HandlerExecutionChain是handler執行鏈,它包裝了handler object和一組HandlerInterceptor。 HandlerInterceptor是攔截器,它可以在handler執行前後進行一些額外的操作,例如權限檢查,日誌記錄等。

  • HandlerAdapter是handler的適配器,它能處理不同類型的handler object,並呼叫其對應的方法,傳回ModelAndView物件。 HandlerAdapter可以根據handler object的類型,進行參數綁定,傳回值處理等操作。

  • HandlerInterceptor使用

#定義一個攔截器類,實作HandlerInterceptor介面或繼承HandlerInterceptorAdapter類,重寫preHandle, postHandle和afterCompletion三個方法。

  • 在preHandle方法中,可以获取请求和响应对象,进行预处理,比如检查请求头中的token,或者判断请求的url是否有权限访问等。如果返回true,则继续执行后续的拦截器或者处理器;如果返回false,则中断请求,不再执行后续的拦截器或者处理器。

  • 在postHandle方法中,可以获取请求和响应对象,以及处理器返回的ModelAndView对象,进行后处理,比如修改模型数据或者视图信息等。只有在preHandle方法返回true并且处理器成功执行后,该方法才会被调用。

  • 在afterCompletion方法中,可以获取请求和响应对象,以及处理器抛出的异常对象(如果有的话),进行清理资源或者异常处理等。只有当preHandle方法返回true时,无论处理器是否成功执行,该方法才会被调用。

  • 在SpringMVC配置文件中,需注册拦截器类并指定拦截的URL模式。可以注册多个拦截器,并指定顺序。拦截器会按照顺序执行preHandle方法,然后按照逆序执行postHandle和afterCompletion方法。

  • HandlerInterceptor和Filter的区别

    • HandlerInterceptor利用Java反射机制实现,而Filter则通过函数回调方式实现。HandlerInterceptor可以利用Spring的AOP技术,实现更灵活的拦截逻辑,而Filter只能在请求前后进行简单的处理。

    • HandlerInterceptor不依赖于Servlet容器,而Filter依赖于Servlet容器。HandlerInterceptor是SpringMVC框架提供的,可以在任何情况下使用,而Filter是Servlet规范的一部分,只能在Web应用中使用。

    • HandlerInterceptor的执行由SpringMVC框架控制,而Filter的执行由Servlet容器控制。HandlerInterceptor可以通过IoC容器来管理,可以注入其他的Bean,而Filter则需要在web.xml中配置,或者使用@WebFilter注解,并且需要@ServletComponentScan扫描。

    • HandlerInterceptor只能拦截DispatcherServlet处理的请求,而Filter可以拦截任何请求。HandlerInterceptor只能对Controller方法进行拦截,而Filter可以对静态资源、JSP页面等进行拦截。

    • HandlerInterceptor有三个方法:preHandle,postHandle和afterCompletion,分别在请求处理前后和视图渲染前后执行,而Filter只有一个方法:doFilter,在请求处理前后执行。

    处理controller返回结果

    对于被controller方法,使用的适配器是RequestMappingHandlerAdapter,在handlerAdapter.handle方法执行时,会去执行对应的controller方法,处理controller方法返回的结果。

    invocableMethod.invokeAndHandle(webRequest, mavContainer);

    ServletInvocableHandlerMethod.invokeAndHandle

    // 执行controller方法
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    ...
    // 处理返回数据,会判断是不是有@ResponseBody注解,如果有,会使用RequestResponseBodyMethodProcessor来处理返回值
    // 然后会解析请求头等等,判断应该返回什么类型的数据,然后使用对应的HttpMessageConverter写入输出流
    this.returnValueHandlers.handleReturnValue(
          returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

    SpringBoot Jar启动

    SpringBoot使用嵌入式Servlet容器启动应用,有Tomcat,Jetty,Undertow。

    选择Servlet容器

    SpringBoot默认使用Tomcat,可以在配置文件中看出。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    web模块自动引入了tomcat

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
    </dependency>

    如果不使用Tomcat可以排除,引入其他服务器。

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <!-- 剔除Tomcat -->
      <exclusions>
        <exclusion>
          <artifactId>spring-boot-starter-tomcat</artifactId>
          <groupId>org.springframework.boot</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <!-- 使用jetty -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>

    如果没有排除Tomcat,直接引入其他服务器,比如下面。

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <!-- 没有排除Tomcat -->
    </dependency>
    <!-- 引入jetty -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>

    如果项目中同时引入了Tomcat和其他服务器的依赖,那么SpringBoot会按照以下顺序来选择启动的服务器。

    Tomcat > Jetty > Undertow

    也就是说,如果有Tomcat,就优先使用Tomcat,如果没有Tomcat,就看有没有Jetty,如果有Jetty,就使用Jetty,以此类推。这个顺序是在SpringBoot的ServletWebServerFactoryConfiguration类中定义的。

    // 只展示必要代码
    class ServletWebServerFactoryConfiguration {
       // 当Servlet、Tomcat、UpgradeProtocol类在类路径存在时
       // 并且ServletWebServerFactory类存在,则会创建tomcatServletWebServerFactory bean。
       @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
       @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
       static class EmbeddedTomcat {
          @Bean
          TomcatServletWebServerFactory tomcatServletWebServerFactory(
             ... 代码省略
          }
       }
       // 当Servlet、Server、WebAppContext类在类路径存在时
       // 并且ServletWebServerFactory类型的Bean不存在时,则会创建JettyServletWebServerFactory bean。
       // ServletWebServerFactory是TomcatServletWebServerFactory、JettyServletWebServerFactory、
       // UndertowServletWebServerFactory的父类
       // 所以如果Tomcat被引入,上面的tomcatServletWebServerFactory就会被创建,这里的条件就不满足,不会被创建。
       @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
       @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
       static class EmbeddedJetty {
          @Bean
          JettyServletWebServerFactory JettyServletWebServerFactory(
             ... 代码省略
          }
       }
       // 分析同上
       @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
       @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
       static class EmbeddedUndertow {
          @Bean
          UndertowServletWebServerFactory undertowServletWebServerFactory(
            ... 代码省略
          }
       }

    下面继续以Tomcat为例

    Tomcat配置、启动

    Tomcat是在Spring容器启动的时候启动的

    SpringApplication.run方法

    首先创建一个ConfigurableApplicationContext对象,并调用其refresh()方法,这个对象一般是AnnotationConfigServletWebServerApplicationContext。

    context = createApplicationContext();
    -> refreshContext(context);
    -> refresh(context);
    -> applicationContext.refresh();

    refresh()方法会调用其父类ServletWebServerApplicationContext的refresh()方法,在父类的refresh()中再次调用父类AbstractApplicationContext的refresh()方法,主要在onRefresh阶段,会进行服务器的配置。

    ... refresh()代码简略
    // 这里会初始化Tomcat配置
    onRefresh();
    // 这里会启动Tomcat
    finishRefresh();
    ...

    回到ServletWebServerApplicationContext类的onRefresh()方法,会调用createWebServer()方法,创建web服务器。

    protected void onRefresh() {
       super.onRefresh();
       try {
          // 创建服务器
          createWebServer();
       }
       catch (Throwable ex) {
          throw new ApplicationContextException("Unable to start web server", ex);
       }
    }
    private void createWebServer() {
        ... 代码简略
        // 获取工厂类,这里获取的就是在配置类中生效的那一个,这里为TomcatServletWebServerFactory
        ServletWebServerFactory factory = getWebServerFactory();
        createWebServer.tag("factory", factory.getClass().toString());
        // 获取服务器 
        this.webServer = factory.getWebServer(getSelfInitializer());  
    }

    TomcatServletWebServerFactory.getWebServer

    public WebServer getWebServer(ServletContextInitializer... initializers) {
       if (this.disableMBeanRegistry) {
          Registry.disableRegistry();
       }
       Tomcat tomcat = new Tomcat();
       File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
       tomcat.setBaseDir(baseDir.getAbsolutePath());
       for (LifecycleListener listener : this.serverLifecycleListeners) {
          tomcat.getServer().addLifecycleListener(listener);
       }
       // 设置Connector,对应与Tomcat Server.xml 中的<Connector></Connector>
       Connector connector = new Connector(this.protocol);
       connector.setThrowOnFailure(true);
       // 对应于Server.xml 中
       // <Service name="Catalina">
       //   <Connector port="8080" protocol="HTTP/1.1"
       //     connectionTimeout="20000"
       //     redirectPort="8443" relaxedQueryChars="[|]"/>
       // </Service>
       tomcat.getService().addConnector(connector);
       customizeConnector(connector);
       tomcat.setConnector(connector);
       tomcat.getHost().setAutoDeploy(false);
       configureEngine(tomcat.getEngine());
       for (Connector additionalConnector : this.additionalTomcatConnectors) {
          tomcat.getService().addConnector(additionalConnector);
       }
       // 准备好Context组件
       prepareContext(tomcat.getHost(), initializers);
       return getTomcatWebServer(tomcat);
    }
    // 创建Tomcat服务器
    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
       return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
    }

    至此,Tomcat配置已经初始化完成,准备启动。

    在finishRefresh()方法中,会启动Tomcat

    getLifecycleProcessor().onRefresh();
    > DefaultLifecycleProcessor.startBeans(true);
    > LifecycleGroup::start
    > doStart(this.lifecycleBeans, member.name, this.autoStartupOnly);
    > bean.start();
    > WebServerStartStopLifecycle.start
    > TomcatWebServer.start();
    private void startBeans(boolean autoStartupOnly) {
       Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
       Map<Integer, LifecycleGroup> phases = new TreeMap<>();
       lifecycleBeans.forEach((beanName, bean) -> {
          if (!autoStartupOnly || (bean instanceof SmartLifecycle smartLifecycle && smartLifecycle.isAutoStartup())) {
             int phase = getPhase(bean);
             phases.computeIfAbsent(
                   phase,
                   p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly)
             ).add(beanName, bean);
          }
       });
       if (!phases.isEmpty()) {
          phases.values().forEach(LifecycleGroup::start);
       }
    }
    public void start() {
       this.webServer.start();
       this.running = true;
       this.applicationContext
          .publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
    }

    DispatchServlet配置

    ServletContextInitializer

    在prepareContext方法中,有一个方法configureContext

    configureContext(context, initializersToUse);

    configureContext方法,在这里面创建了一个TomcatStarter对象,这个类实现了ServletContainerInitializer接口,所以在容器启动过程中会被调用。

    TomcatStarter starter = new TomcatStarter(initializers);
    context.addServletContainerInitializer(starter, NO_CLASSES);

    initializers是Spring自己定义的初始化接口ServletContextInitializer,传入TomcatStarter之后,在onStartup方法中循环调用onStartup方法。

    public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
       try {
          for (ServletContextInitializer initializer : this.initializers) {
             initializer.onStartup(servletContext);
          }
       }
       ...
    }

    需要注意的是,这里的initializers有些传过来的时候是一个函数式接口,在上面的factory.getWebServer(getSelfInitializer());这里传进来的,就是一个函数式接口

    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
       return this::selfInitialize;
    }

    实际调用在下面这个方法

    private void selfInitialize(ServletContext servletContext) throws ServletException {
       prepareWebApplicationContext(servletContext);
       registerApplicationScope(servletContext);
       WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
       for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
          beans.onStartup(servletContext);
       }
    }

    在此处绕过所有ServletContextInitializer,随后执行它们的onStartup方法。

    其中有一个DispatcherServletRegistrationBean,这个类实现了ServletContextInitializer接口,主要是用来添加DispatchServlet。

    DispatcherServletAutoConfiguration配置类中有DispatcherServlet,DispatcherServletRegistrationBean两个Bean。

    protected static class DispatcherServletRegistrationConfiguration {
       @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
       @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
       public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
             WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
          // 创建DispatcherServletRegistrationBean,并把dispatcherServlet传进去
          DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                webMvcProperties.getServlet().getPath());
          registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
          registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
          multipartConfig.ifAvailable(registration::setMultipartConfig);
          return registration;
       }
    }
    protected static class DispatcherServletConfiguration {
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
            // 创建DispatcherServlet
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
            return dispatcherServlet;
        }
    }

    ServletContextInitializer.onStartup方法由子类RegistrationBean实现

    public final void onStartup(ServletContext servletContext) throws ServletException {
        String description = getDescription();
        if (!isEnabled()) {
            logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
            return;
        }
        // register是一个抽象方法,由子类DynamicRegistrationBean实现
        register(description, servletContext);
    }
    protected abstract void register(String description, ServletContext servletContext);

    DynamicRegistrationBean.register

    protected final void register(String description, ServletContext servletContext) {
       // addRegistration是一个抽象方法,由子类ServletRegistrationBean实现
       D registration = addRegistration(description, servletContext);
       if (registration == null) {
          logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
          return;
       }
       // Servlet被添加到Context后,这里对Servlet进行配置,如拦截路径 
       configure(registration);
    }
    protected abstract D addRegistration(String description, ServletContext servletContext);

    ServletRegistrationBean.addRegistration,作用类似下面

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
       String name = getServletName();
       // 添加Servlet到Context中,这里的servlet就是DispatchServlet。 
       return servletContext.addServlet(name, this.servlet);
    }

    ServletRegistrationBean.configure,作用类似下面

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    protected void configure(ServletRegistration.Dynamic registration) {
       super.configure(registration);
       String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
       if (urlMapping.length == 0 && this.alwaysMapUrl) {
          // DEFAULT_MAPPINGS默是“/” 
          urlMapping = DEFAULT_MAPPINGS;
       }
       if (!ObjectUtils.isEmpty(urlMapping)) {
          // 设置mapping 
          registration.addMapping(urlMapping);
       }
       registration.setLoadOnStartup(this.loadOnStartup);
       if (this.multipartConfig != null) {
          registration.setMultipartConfig(this.multipartConfig);
       }
    }

    至此,DispatchServlet已配置好,后续流程和web.xml配置调用流程基本相同。

    FrameworkServlet.initWebApplicationContext()

    protected WebApplicationContext initWebApplicationContext() {
        // 此处获取根容器,就是Spring初始化的XmlWebApplicationContext,
        // 在上面把它添加到了ServletContext的属性中,标记根容器,这里把它获取出来
        // String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
        // servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        // ===========上面为使用web.xml时的分析,下面为SpringBoot嵌入式Tomcat分析============
        // 同样是获取根容器,不过一般为AnnotationConfigServletWebServerApplicationContext
       WebApplicationContext rootContext =
             WebApplicationContextUtils.getWebApplicationContext(getServletContext());
       WebApplicationContext wac = null;
       // 此时webApplicationContext还是null,因为DispatchServlet是被tomcat创建的,需要无参构造器
       // 构造器中没有设置webApplicationContext的代码,所以此时webApplicationContext还是null
       // ===========上面为使用web.xml时的分析,下面为SpringBoot嵌入式Tomcat分析============
       // 注意:在SpringBoot使用嵌入式Tomcat时,这个webApplicationContext不为null,因为FrameworkServlet还
       // 实现了ApplicationContextAware接口,所以当SpringBoot的上下文准备好之后,会回调setApplicationContext方法
       // 注入ApplicationContext,后面在细说 
       if (this.webApplicationContext != null) {
          // A context instance was injected at construction time -> use it
          wac = this.webApplicationContext;
          if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
             // The context has not yet been refreshed -> provide services such as
             // setting the parent context, setting the application context id, etc
             if (cwac.getParent() == null) {
                // The context instance was injected without an explicit parent -> set
                // the root application context (if any; may be null) as the parent
                cwac.setParent(rootContext);
             }
             configureAndRefreshWebApplicationContext(cwac);
          }
       }
       if (wac == null) {
          // No context instance was injected at construction time -> see if one
          // has been registered in the servlet context. If one exists, it is assumed
          // that the parent context (if any) has already been set and that the
          // user has performed any initialization such as setting the context id
          // 此处主要是获取web.xml配置的WebApplicationContext
          // 可以通过设置参数contextAttribute来设置加载SpringMVC的ApplicationContext
          // 比如下面这样。除非项目中有多个WebApplicationContext,需要使用其他WebApplicationContext才会用到
          // 一般都是null
          // <context-param>
          //    <param-name>contextAttribute</param-name>
          //    <param-value>myWebApplicationContext</param-value>
          // </context-param>
          // ===========上面为使用web.xml时的分析,下面为SpringBoot嵌入式Tomcat分析
          // 因为wac此时不为null,这里不会进入
          wac = findWebApplicationContext();
       }
       if (wac == null) {
          // 现在进入到创建SpringMVC的ApplicationContext流程
          // 也就是加载contextConfigLocation定义的xml文件 
          // ===========上面为使用web.xml时的分析,下面为SpringBoot嵌入式Tomcat分析
          // 因为wac此时不为null,这里不会进入,所以没有SpringMVC的容器,也就是没有父子容器之分,SpringBoot项目中只有一个容器
          // No context instance is defined for this servlet -> create a local one
          wac = createWebApplicationContext(rootContext);
       }
       if (!this.refreshEventReceived) {
          // Either the context is not a ConfigurableApplicationContext with refresh
          // support or the context injected at construction time had already been
          // refreshed -> trigger initial onRefresh manually here.
          synchronized (this.onRefreshMonitor) {
             // 初始化策略对象
             // 比如:HandlerMapping,HandlerAdapter,ViewResolver等等
             onRefresh(wac);
          }
       }
       if (this.publishContext) {
          // Publish the context as a servlet context attribute.
          String attrName = getServletContextAttributeName();
          getServletContext().setAttribute(attrName, wac);
       }
       return wac;
    }

    以上是web.xml SpringBoot打包可執行Jar運行SpringMVC的方法是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述
    本文轉載於:亿速云。如有侵權,請聯絡admin@php.cn刪除
    怎么使用SpringBoot+Canal实现数据库实时监控怎么使用SpringBoot+Canal实现数据库实时监控May 10, 2023 pm 06:25 PM

    Canal工作原理Canal模拟MySQLslave的交互协议,伪装自己为MySQLslave,向MySQLmaster发送dump协议MySQLmaster收到dump请求,开始推送binarylog给slave(也就是Canal)Canal解析binarylog对象(原始为byte流)MySQL打开binlog模式在MySQL配置文件my.cnf设置如下信息:[mysqld]#打开binloglog-bin=mysql-bin#选择ROW(行)模式binlog-format=ROW#配置My

    Spring Boot怎么使用SSE方式向前端推送数据Spring Boot怎么使用SSE方式向前端推送数据May 10, 2023 pm 05:31 PM

    前言SSE简单的来说就是服务器主动向前端推送数据的一种技术,它是单向的,也就是说前端是不能向服务器发送数据的。SSE适用于消息推送,监控等只需要服务器推送数据的场景中,下面是使用SpringBoot来实现一个简单的模拟向前端推动进度数据,前端页面接受后展示进度条。服务端在SpringBoot中使用时需要注意,最好使用SpringWeb提供的SseEmitter这个类来进行操作,我在刚开始时使用网上说的将Content-Type设置为text-stream这种方式发现每次前端每次都会重新创建接。最

    SpringBoot怎么实现二维码扫码登录SpringBoot怎么实现二维码扫码登录May 10, 2023 pm 08:25 PM

    一、手机扫二维码登录的原理二维码扫码登录是一种基于OAuth3.0协议的授权登录方式。在这种方式下,应用程序不需要获取用户的用户名和密码,只需要获取用户的授权即可。二维码扫码登录主要有以下几个步骤:应用程序生成一个二维码,并将该二维码展示给用户。用户使用扫码工具扫描该二维码,并在授权页面中授权。用户授权后,应用程序会获取一个授权码。应用程序使用该授权码向授权服务器请求访问令牌。授权服务器返回一个访问令牌给应用程序。应用程序使用该访问令牌访问资源服务器。通过以上步骤,二维码扫码登录可以实现用户的快

    SpringBoot/Spring AOP默认动态代理方式是什么SpringBoot/Spring AOP默认动态代理方式是什么May 10, 2023 pm 03:52 PM

    1.springboot2.x及以上版本在SpringBoot2.xAOP中会默认使用Cglib来实现,但是Spring5中默认还是使用jdk动态代理。SpringAOP默认使用JDK动态代理,如果对象没有实现接口,则使用CGLIB代理。当然,也可以强制使用CGLIB代理。在SpringBoot中,通过AopAutoConfiguration来自动装配AOP.2.Springboot1.xSpringboot1.xAOP默认还是使用JDK动态代理的3.SpringBoot2.x为何默认使用Cgl

    spring boot怎么对敏感信息进行加解密spring boot怎么对敏感信息进行加解密May 10, 2023 pm 02:46 PM

    我们使用jasypt最新版本对敏感信息进行加解密。1.在项目pom文件中加入如下依赖:com.github.ulisesbocchiojasypt-spring-boot-starter3.0.32.创建加解密公用类:packagecom.myproject.common.utils;importorg.jasypt.encryption.pbe.PooledPBEStringEncryptor;importorg.jasypt.encryption.pbe.config.SimpleStrin

    使用Java SpringBoot集成POI实现Word文档导出使用Java SpringBoot集成POI实现Word文档导出Apr 21, 2023 pm 12:19 PM

    知识准备需要理解ApachePOI遵循的标准(OfficeOpenXML(OOXML)标准和微软的OLE2复合文档格式(OLE2)),这将对应着API的依赖包。什么是POIApachePOI是用Java编写的免费开源的跨平台的JavaAPI,ApachePOI提供API给Java程序对MicrosoftOffice格式档案读和写的功能。POI为“PoorObfuscationImplementation”的首字母缩写,意为“简洁版的模糊实现”。ApachePOI是创建和维护操作各种符合Offic

    springboot怎么整合shiro实现多验证登录功能springboot怎么整合shiro实现多验证登录功能May 10, 2023 pm 04:19 PM

    1.首先新建一个shiroConfigshiro的配置类,代码如下:@ConfigurationpublicclassSpringShiroConfig{/***@paramrealms这儿使用接口集合是为了实现多验证登录时使用的*@return*/@BeanpublicSecurityManagersecurityManager(Collectionrealms){DefaultWebSecurityManagersManager=newDefaultWebSecurityManager();

    Springboot如何实现视频上传及压缩功能Springboot如何实现视频上传及压缩功能May 10, 2023 pm 05:16 PM

    一、定义视频上传请求接口publicAjaxResultvideoUploadFile(MultipartFilefile){try{if(null==file||file.isEmpty()){returnAjaxResult.error("文件为空");}StringossFilePrefix=StringUtils.genUUID();StringfileName=ossFilePrefix+"-"+file.getOriginalFilename(

    See all articles

    熱AI工具

    Undresser.AI Undress

    Undresser.AI Undress

    人工智慧驅動的應用程序,用於創建逼真的裸體照片

    AI Clothes Remover

    AI Clothes Remover

    用於從照片中去除衣服的線上人工智慧工具。

    Undress AI Tool

    Undress AI Tool

    免費脫衣圖片

    Clothoff.io

    Clothoff.io

    AI脫衣器

    AI Hentai Generator

    AI Hentai Generator

    免費產生 AI 無盡。

    熱門文章

    R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
    2 週前By尊渡假赌尊渡假赌尊渡假赌
    倉庫:如何復興隊友
    4 週前By尊渡假赌尊渡假赌尊渡假赌
    Hello Kitty Island冒險:如何獲得巨型種子
    4 週前By尊渡假赌尊渡假赌尊渡假赌

    熱工具

    Safe Exam Browser

    Safe Exam Browser

    Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

    DVWA

    DVWA

    Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

    SublimeText3 英文版

    SublimeText3 英文版

    推薦:為Win版本,支援程式碼提示!

    EditPlus 中文破解版

    EditPlus 中文破解版

    體積小,語法高亮,不支援程式碼提示功能

    SublimeText3 Linux新版

    SublimeText3 Linux新版

    SublimeText3 Linux最新版