zoukankan      html  css  js  c++  java
  • SpringMVC之五:自定义DispatcherServlet配置及配置额外的 servlets 和 filters

    相关文章

    Servlet3.0之四:动态注册和Servlet容器初始化

    SpringBoot中通过SpringBootServletInitializer如何实现组件加载

    SpringMVC之五:自定义DispatcherServlet配置及配置额外的 servlets 和 filters

    一、web容器如何初始化第三方组件(servlet、filters)

    在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet规范中通过ServletContainerInitializer实现此功能。每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。一般伴随着ServletContainerInitializer一起使用的还有HandlesTypes注解,通过HandlesTypes可以将感兴趣的一些类注入到ServletContainerInitializerde的onStartup()方法作为参数传入。

    ServletContainerInitializer的源码:

    package javax.servlet;
    public interface ServletContainerInitializer {
    
        void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
    
    }

    容器启动时会自动扫描当前服务中ServletContainerInitializer的实现类,并调用其onStartup方法,其参数Set<Class<?>> c,可通过在实现类上声明注解进行注入。(例如:可通过在实现类上声明注解javax.servlet.annotation.HandlesTypes(WebApplicationInitializer.class)注解自动注入,@HandlesTypes会自动扫描项目中所有的WebApplicationInitializer.class的实现类,并将其全部注入Set。)

    Tomcat容器的ServletContainerInitializer机制的实现,主要交由Context容器和ContextConfig监听器共同实现,ContextConfig监听器负责在容器启动时读取每个web应用的WEB-INF/lib目录下包含的jar包的META-INF/services/javax.servlet.ServletContainerInitializer,以及web根目录下的META-INF/services/javax.servlet.ServletContainerInitializer,通过反射完成这些ServletContainerInitializer的实例化,然后再设置到Context容器中,最后Context容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法,并将感兴趣的类作为参数传入。

      图-1

    基本的实现机制如图,首先通过ContextConfig监听器遍历每个jar包或web根目录的META-INF/services/javax.servlet.ServletContainerInitializer文件,根据读到的类路径实例化每个ServletContainerInitializer;然后再分别将实例化好的ServletContainerInitializer设置进Context容器中;最后Context容器启动时分别调用所有ServletContainerInitializer对象的onStartup方法。

    假如读出来的内容为com.seaboat.mytomcat.CustomServletContainerInitializer,则通过反射实例化一个CustomServletContainerInitializer对象,这里涉及到一个@HandlesTypes注解的处理,被它标明的类需要作为参数值传入到onStartup方法。如下例子:

    @HandlesTypes({ HttpServlet.class,Filter.class }) 
    public class CustomServletContainerInitializer implements 
        ServletContainerInitializer { 
      public void onStartup(Set<Class<?>> classes, ServletContext servletContext) 
          throws ServletException {
          for(Class c : classes) 
             System.out.println(c.getName());
      } 
    }

    其中@HandlesTypes标明的HttpServlet和Filter两个class被注入到了onStartup方法。所以这个注解也是需要在ContextConfig监听器中处理。前面已经介绍了注解的实现原理,由于有了编译器的协助,我们可以方便地通过ServletContainerInitializer的class对象中获取到HandlesTypes对象,进而再获取到注解声明的类数组,如

    HandlesTypes ht =servletContainerInitializer.getClass().getAnnotation(HandlesTypes.class);
    Class<?>[] types = ht.value();

    即可获取到HttpServlet和Filter的class对象数组,后面Context容器调用CustomServletContainerInitializer对象的onStartup方法时作为参数传入。至此,即完成了servlet规范的ServletContainerInitializer初始化器机制。

    二、Spring中初始化组件(servlet、filters)的过程

    2.1、Spring中是如何初始化组件(servlet、filters)--SpringServletContainerInitializer

    根据一所述在 Servlet 3.0 的环境中,容器会在 classpath 中寻找继承了 javax.servlet.ServletContainerInitializer 接口的类,用它来配置 servlet 容器。 Spring 提供了一个继承这个接口的类 SpringServletContainerInitializer,在这个类中,它会寻找任何继承了 WebApplicationInitializer 接口的类并用其来配置 servlet 容器

    先看看SpringServletContainerInitializer.java的源码:

    package org.springframework.web;
    
    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer { //Spring框架中继承
        @Override
        public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
                throws ServletException {
    
            List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
    
            if (webAppInitializerClasses != null) {
                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) 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);
            for (WebApplicationInitializer initializer : initializers) {
                initializer.onStartup(servletContext);
            }
        }
    
    }

    2.2、Spring中是如何初始化组件(servlet、filters)--WebApplicationInitializer

      Spring将Servlet配置相关的转给了WebApplicationInitializer,Spring 3.2 提供了一个继承了 WebApplicationInitializer 接口的基类 AbstractAnnotationConfigDispatcherServletInitializer。所以,你的 servlet 配置类只需要继承 AbstractAnnotationConfigDispatcherServletInitializer,就会被发现而用于 servlet 容器的配置。

    图-2

    ***DispatcherServlet VS ContextLoaderListener
    
    在 Spring MVC 中存在两种应用上下文:DispatcherServlet 创建的和拦截器 ContextLoaderListener 创建的上下文:
    1、DispatcherServlet:加载包含 web 组件的 bean,比如 controllers,view resolvers 和 hanlder mappings。
    2、ContextLoaderListener:加载其他 bean,通常是一些中间层和数据层的组件(比如数据库配置 bean 等)。

    回到AbstractAnnotationConfigDispatcherServletInitializer ,在 AbstractAnnotationConfigDispatcherServletInitializer 中 DispatcherServlet 和 ContextLoaderListener 都会被创建,而基类中的方法就可用来创建不同的应用上下文:

    • getServletConfigClasses():定义 DispatcherServlet 应用上下文中的 beans
    • getRootConfigClasses():定义拦截器 ContextLoaderListener 应用上下文中的 beans

    Note:为了使用 AbstractAnnotationConfigDispatcherServletInitializer 必须保证 web 服务器支持 Servlet 3.0 标准(如 tomcat 7 或更高版本) 。

    图-3

    三、自定义 DispatcherServlet 配置

    因为我们使用 AbstractAnnotationConfigDispatcherServletInitializer 来配置 DispatcherServlet,同时可以通过 customizeRegistration() 方法来对DispatcherServlet进行额外的配置

    通过 ServletRegistration.Dynamic 参数配置 DispatcherServlet 的 load-on-startup 优先级 setLoadOnStartup(int loadOnStartup),设置初始化参数 setInitParameters() 等。具体查看文档 ServletRegistration.Dynamic

    在AbstractAnnotationConfigDispatcherServletInitializer将DispatcherServlet注册到Servlet容 器中之后,就会调用customizeRegistration(),并将Servlet注册后得到的 Registration.Dynamic传递进来。通过重载customizeRegistration()方法,我们可 以对DispatcherServlet进行额外的配置。

    看AbstractDispatcherServletInitializer的部分源码

        protected void registerDispatcherServlet(ServletContext servletContext) {
            String servletName = getServletName();
            Assert.hasLength(servletName, "getServletName() must not return empty or null");
    
            WebApplicationContext servletAppContext = createServletApplicationContext();
            Assert.notNull(servletAppContext,
                    "createServletApplicationContext() did not return an application " +
                    "context for servlet [" + servletName + "]");
    
            FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
            dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
    
            ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
            Assert.notNull(registration,
                    "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);
        }

    例如,在本章稍后的内容中(7.2节),我们将会看到如何在Spring MVC中处理multipart请求和文 件上传。如果计划使用Servlet 3.0对multipart配置的支持,那么需要使 用DispatcherServlet的registration来启用multipart请求。我们可以重 载customizeRegistration()方法来设置MultipartConfigElement,如下所示:

        @Override
        protected void customizeRegistration(Dynamic registration) {
            registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
            super.customizeRegistration(registration);
        }

    借助customizeRegistration()方法中的ServletRegistration.Dynamic,我们能 够完成多项任务,包括通过调用setLoadOnStartup()设置load-on-startup优先级,通过 setInitParameter()设置初始化参数,通过调用setMultipartConfig()配置Servlet 3.0对multipart的支持。在前面的样例中,我们设置了对multipart的支持,将上传文件的临时存 储目录设置在“/tmp/spittr/uploads”中。

    四、配置额外的 servlets 和 filters

    按照AbstractAnnotationConfigDispatcherServletInitializer的定义,它会创 建DispatcherServlet和ContextLoaderListener。如上图-3所示。但是,如果你想注册其他的 Servlet、Filter或Listener的话,那该怎么办呢?

    基于Java的初始化器(initializer)的一个好处就在于我们可以定义任意数量的初始化器类。因 此,如果我们想往Web容器中注册其他组件的话,只需创建一个新的初始化器就可以了。最简 单的方式就是实现Spring的WebApplicationInitializer接口。 例如,如下的程序清单展现了如何创建WebApplicationInitializer实现并注册一个 Servlet。

    使用 java 配置 servlet 的一个好处(不同于 web.xml)就是:可以定义任意数量的初始化类。所以,如果需要定义额外的 servlets 或 filters,只需要创建额外的初始化类。在 Spring MVC 中可以通过继承 WebApplicationInitializer 接口来实现。

    接下来,我们定义一个新的 servlet:

    package com.dxz.mvcdemo1.config.servlet;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRegistration.Dynamic;
    
    import org.springframework.web.WebApplicationInitializer;
    
    public class MyServletInitializer implements WebApplicationInitializer {
    
        public void onStartup(ServletContext servletContext) throws ServletException {
            //定义额外的    servlet
            Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
            myServlet.addMapping("/custom/**");
            
            //定义filter
            javax.servlet.FilterRegistration.Dynamic filter =
                    servletContext.addFilter("myFilter", MyFilter.class);
            filter.addMappingForUrlPatterns(null, false, "/custom/*");
        }
    
    }

    当然,你也可以用来定义 filters 和 listeners:

    如上面的红底部分。

    AbstractAnnotationConfigDispatcherServletInitializer 中还有一种快捷方式:

    如果你需要为 DispatcherServlet 添加 filter 的话,就不用这么麻烦了,你只要重写 AbstractAnnotationConfigDispatcherServletInitializer 类的 getServletFilters() 方法就行了:

        @Override
        protected Filter[] getServletFilters() {
            return new Filter[] { new MyFilter() };
        }

    不需要 mapping,因为会自动 mapping 到 DispatcherServlet 上,通过返回多个 filter,可以添加多个 filter。

  • 相关阅读:
    接口--类似于抽象类但不是抽象类
    final
    抽象类
    static示例
    深入理解static关键字
    IDEA 出现错误:找不到或无法加载主类
    IDEA的java源码文件左边有一个红色的J
    this关键字
    构造方法、方法的重载
    访问控制符
  • 原文地址:https://www.cnblogs.com/duanxz/p/3688968.html
Copyright © 2011-2022 走看看