SpringMVC是怎么添加DispatcherServlet到

2023-08-02  本文已影响0人  雅阁驸马

1. Servlet與SpringMVC之間的關係

Spring的MVC是基於Servlet功能實現的,經過實現Servlet接口的DispatcherServlet來封裝其核心功能實現。

2. ServletContainerInitializer接口

在web容器啓動時會作一些初始化的工做,例如註冊servlet或者filtes等,servlet規範中經過ServletContainerInitializer實現此功能。

每一個框架, 比如Spring,要使用ServletContainerInitializer就必須在對應的jar包的META-INF/services 目錄建立一個名爲javax.servlet.ServletContainerInitializer的文件,文件內容指定具體的ServletContainerInitializer實現類。(JAVA的SPI特性

案例演示:服務器

@HandlesTypes(value = MyHandlesType.class)//該註解聲明的類,會被注入到set中,若是沒有合適的類,set爲null,这个动作也是Servlet 容器做的(如tomcat)
public class MyServletContainerInitializer implements ServletContainerInitializer {

    /**
     * @param set 感興趣類型 也就是MyHandlesType 全部子類型
     * @param servletContext
     * @throws ServletException
     */
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        // 1.打印全部感興趣的類型
        for (Class<?> c : set) {
            System.out.println(c);
        }
       // 2.servletContext 手動註冊過濾器、servlet、監聽器
        ServletRegistration.Dynamic payServlet = servletContext.addServlet("payServlet", new PayServlet());
        payServlet.addMapping("/pay");
    }
}

以前传统的Servlet开发,我也需要提供web.xml到Tomcat中,在web.xml中指定Servlet,以及servlet mapping,如:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
        version="3.1">

    <!-- spring mvc配置开始  -->
    <servlet>
        <servlet-name>let'sGo</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>let'sGo</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- spring mvc配置结束  -->

    <welcome-file-list>
        <welcome-file>index</welcome-file>
    </welcome-file-list>
</web-app>

springmvc是如何實現不需要web.xml配置,靠的就是ServletContainerInitialize。

3. SpringServletContainerInitializer的作用

看下SpringServletContainerInitializer的源码:

@HandlesTypes(WebApplicationInitializer.class)  // Servlet容器会根据@HandlesTypes找到所有注解中的类的实现,也就是所有WebApplicationInitializer.class的实现
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
    //所有WebApplicationInitializer.class的实现都会被放在这个Set中
   @Override
   public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
         throws ServletException {

      List<WebApplicationInitializer> initializers = Collections.emptyList();

      if (webAppInitializerClasses != null) {
         initializers = new ArrayList<>(webAppInitializerClasses.size());
         for (Class<?> waiClass : webAppInitializerClasses) {
            // Be defensive: Some servlet containers provide us with invalid classes,
            // no matter what @HandlesTypes says...
            if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                  WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
               try {
                  initializers.add((WebApplicationInitializer)
                        ReflectionUtils.accessibleConstructor(waiClass).newInstance());
               }
               catch (Throwable ex) {
                  throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
               }
            }
         }
      }

      if (initializers.isEmpty()) {
         servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
         return;
      }

      servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
      AnnotationAwareOrderComparator.sort(initializers);
      // 调用所有WebApplicationInitializer实现类的onStartUp()函数
      for (WebApplicationInitializer initializer : initializers) {
         initializer.onStartup(servletContext);
      }
   }
}

SpringServletContainerInitializer通过实现ServletContainerInitializer将自身并入到Servlet容器的生命周期中, 并通过自身定义的WebApplicationInitializer将依赖于Spring框架的系统初始化需求与Servlet容器解耦. 即依赖于spring的系统可以通过实现WebApplicationInitializer来实现自定义的初始化逻辑. 而不需要去实现ServletContainerInitializer

4. WebApplicationInitializer

SpringServletContainerInitializer会调用所有实现了WebApplicationInitializer接口的实现类。Spring提供了很多实现类用来做初始化操作。

5. AbstractAnnotationConfigDispatcherServletInitializer

SpringMVC提供的AbstractAnnotationConfigDispatcherServletInitializer这个抽象类间接实现了WebApplicationInitializer接口,如下图所示。所以我们只需要自己写一个类,继承AbstractAnnotationConfigDispatcherServletInitializer这个抽象类,servlet容器启动的时候就会调用我们写的实现类的父类里的onStartUp()函数。

image.png

具体其实就是AbstractDispatcherServletInitializer.class的onStartUp()函数:

//org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
public void onStartup(ServletContext servletContext) throws ServletException {
    super.onStartup(servletContext);    
    registerDispatcherServlet(servletContext);
}

protected void registerDispatcherServlet(ServletContext servletContext) {
    String servletName = getServletName();    
    Assert.hasLength(servletName, "getServletName() must not return null or empty");   
    // 创建Spring容器,ServletApplicationContext 
    WebApplicationContext servletAppContext = createServletApplicationContext();    
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");  
    // 创建DispatcherServlet,并把Spring容器当做参数穿进去  
    FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);    
    Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");    
    dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());  
    // 将创建好的DispatcherServlet传进Servlet容器里  
    ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);    
    if (registration == null) {
       throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
             "Check if there is another servlet registered under the same name.");    }

    registration.setLoadOnStartup(1);    
    registration.addMapping(getServletMappings());    
    registration.setAsyncSupported(isAsyncSupported());    
    Filter[] filters = getServletFilters();    
    if (!ObjectUtils.isEmpty(filters)) {
       for (Filter filter : filters) {
          registerServletFilter(servletContext, filter);       }
    }

    customizeRegistration(registration);}

这样的话我们就通过代码的方式做了如下操作:

  1. 创建了Spring容器---ServletApplicationContext
  2. 创建了Servlet---DispatcherServlet,并把Spring容器放进了Servlet中
  3. 把Servlet添加到Servlet 容器中。

6. 上述操作,通过web.xml也能做到,不过Servlet规范3.0以后,支持了代码的方式, 我们看一下通过web.xml执行上述操作的例子:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
        version="3.1">

    <!-- spring mvc配置开始  -->
    <servlet>
        <servlet-name>let'sGo</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>let'sGo</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- spring mvc配置结束  -->

    <welcome-file-list>
        <welcome-file>index</welcome-file>
    </welcome-file-list>
</web-app>
  1. Servlet容器会根据Servlet规范读取web.xml文件
  2. 读到<servlet>标签后就创建<servlet>里标记的Servlet——这里是DispatcherServlet,这里创建调用的是无参构造函数,跟前面通过代码实现的时候创建DispatcherServlet用的是带参数的构造函数不一样,因为这里没有事先创建好的Spring容器(Servlet WebApplicationContext)
  3. 所以在new DispatcherServlet的过程中,判断当前没有现成的Spring容器(Servlet WebApplicationContext),就会自己创建一个Spring容器,代码如下
//org.springframework.web.servlet.FrameworkServlet,它是DispatcherServlet的父类
protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
          WebApplicationContextUtils.getWebApplicationContext(getServletContext());    
          WebApplicationContext wac = null;    
          
          if (this.webApplicationContext != null) {
       // A context instance was injected at construction time -> use it
       // 看下构造函数中是否传进来了  webApplicationContext,用web.xml的情况下,webApplicationContext是null     
       wac = this.webApplicationContext;       
       if (wac instanceof ConfigurableWebApplicationContext) {
          ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;          
          if (!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  
        // 看下Servlet容器里有没有现成的     webApplicationContext
        wac = findWebApplicationContext();    
        }
    // 前面几步都没拿到,自己创建一个webApplicationContext,使用web.xml的情况走的就是这里
    if (wac == null) {
       // No context instance is defined for this servlet -> create a local one      
        wac = createWebApplicationContext(rootContext);    
        }
    ......
}
  1. 然后还是DispatcherServlet,它会把自己添加到Servlet容器中。
// org.springframework.web.servlet.FrameworkServlet
if (this.publishContext) {
    // Publish the context as a servlet context attribute.    
    String attrName = getServletContextAttributeName();    
    getServletContext().setAttribute(attrName, wac);
}
上一篇下一篇

猜你喜欢

热点阅读