美文网首页
SpringMVC是怎么添加DispatcherServlet到

SpringMVC是怎么添加DispatcherServlet到

作者: 雅阁驸马 | 来源:发表于2023-08-02 17:29 被阅读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);
          }
       }
    }
    
    • @HandlesTypes(WebApplicationInitializer.class) // Servlet容器会根据@HandlesTypes找到所有注解中的类的实现,也就是所有WebApplicationInitializer.class的实现
    • 所有WebApplicationInitializer.class的实现都会被放在这个Set中
    • 调用所有WebApplicationInitializer实现类的onStartUp()函数,这样Spring框架就可以在WebApplicationInitializer实现类里做一些初始化等操作(如创建DispatcherServlet,添加Filter)

    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);    
            }
        ......
    }
    
    • 看下构造函数中是否传进来了 webApplicationContext,用web.xml的情况下,webApplicationContext是null
    • 看下Servlet容器里有没有现成的 webApplicationContext
    • 前面几步都没拿到,自己创建一个webApplicationContext,使用web.xml的情况走的就是这里
    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);
    }
    

    相关文章

      网友评论

          本文标题:SpringMVC是怎么添加DispatcherServlet到

          本文链接:https://www.haomeiwen.com/subject/jqeppdtx.html