Servlet3.0——模块化与动态Servlet

今天看Tomcat源码的时候,发现了SCI这个词,好奇ing...,就去看了一下,servlet3.0的新特性。先来看看我是在哪发现他的,大概是ContextConfig的webConfig()中,关于这个函数的作用,请查看Servlet工作原理

  • Anything and everything can override the global and host defaults.
    * This is implemented in two parts
    * - Handle as a web fragment that gets added after everything else so
    * everything else takes priority
    * - Mark Servlets as overridable so SCI configuration can replace
    * configuration from the defaults
    大概这么个意思,就是说global和host level 的默认web.xml可以被任何东西覆盖,主要实现方式有两部分,一个是使用@HandlesTypes注解声明一个servlet作为web fragment ,另一个呢就是SCI。

说到这里,我们要了解一下Servlet3.0的一些新特性了,

  • 使用 @WebServlet、WebFilter、WebListner 三个注解来代替 web.xml 文件中的 Servlet、Filter、Listner 的配置;
    Servlet 3.0 的部署描述文件 web.xml 的顶层标签 有一个 metadata-complete 属性,该属性指定当前的部署描述文件是否是完全的。如果设置为 true,则容器在部署时将只依赖部署描述文件,忽略所有的注解(同时也会跳过 web-fragment.xml 的扫描,亦即禁用可插性支持;如果不配置该属性,或者将其设置为 false,则表示启用注解支持(和可插性支持)
  • web Servlet 3.0 模块化
    原本一个web应用的任何配置都需要在web.xml中进行,因此会使得web.xml变得很混乱,而且灵活性差,因此Servlet 3.0可以将每个Servlet、Filter、Listener打成jar包,然后放在WEB-INF\lib中;注意各自的模块都有各自的配置文件,这个配置文件的名称为 web-fragment.xml ;(注意:配置文件的名必须是这个)
    • 使用方法
      编写Servlet,编译打jar包,将此jar包中的META-INF中的manifest删除并添加 web-fragment.xml;

由于一个 Web 应用中可以出现多个 web-fragment.xml 声明文件,加上一个 web.xml 文件,加载顺序问题便成了不得不面对的问题。
web-fragment.xml 包含了两个可选的顶层标签,,如果希望为当前的文件指定明确的加载顺序,通常需要使用这两个标签, 主要用于标识当前的文件,而 则用于指定先后顺序。一个简单的示例如下:


    FragmentA
    
        
            FragmentB
            FragmentC
        
    
        
    
    


如上所示, 标签的取值通常是被其它 web-fragment.xml 文件在定义先后顺序时引用的,在当前文件中一般用不着,它起着标识当前文件的作用。
了解完上面的特性,现在我们知道

  • 有三种方式来为Web应用增加一个Servlet(Listener、Filter)配置
    1. 使用web.xml
    2. 使用web-fragment.xml
    3. 使用注解
  • 动态部署 Servlet、过滤器、监听器,以及为 Servlet 和过滤器增加 URL 映射等
    ServletContext 为动态配置 Servlet 增加了如下方法:
ServletRegistration.Dynamic addServlet(String servletName,Class servletClass)
ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
ServletRegistration.Dynamic addServlet(String servletName, String className)
     T createServlet(Class clazz)
ServletRegistration getServletRegistration(String servletName)
Map getServletRegistrations()

其中前三个方法的作用是相同的,只是参数类型不同而已;通过 createServlet() 方法创建的 Servlet,通常需要做一些自定义的配置,然后使用 addServlet() 方法来将其动态注册为一个可以用于服务的 Servlet。两个 getServletRegistration() 方法主要用于动态为 Servlet 增加映射信息,这等价于在 web.xml( 或 web-fragment.xml) 中使用 标签为存在的 Servlet 增加映射信息。

以上 ServletContext 新增的方法要么是在 ServletContextListener 的 contexInitialized 方法中调用,要么是在 ServletContainerInitializer 的 onStartup() 方法中调用。(由具体实现决定)
ServletContainerInitializer 是 Servlet 3.0 新增的一个接口(就是开局的SCI),容器在启动时使用 SPI来发现 ServletContainerInitializer 的实现。
下面我给出示例代码

  • 基于ServletContextListener
    public void contextInitialized(ServletContextEvent sce) {

        ServletContext sc = sce.getServletContext();

        // Register Servlet
        ServletRegistration sr = sc.addServlet("DynamicServlet",
            "web.servlet.dynamicregistration_war.TestServlet");
        sr.setInitParameter("servletInitName", "servletInitValue");
        sr.addMapping("/*");

        // Register Filter
        FilterRegistration fr = sc.addFilter("DynamicFilter",
            "web.servlet.dynamicregistration_war.TestFilter");
        fr.setInitParameter("filterInitName", "filterInitValue");
        fr.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST),
                                     true, "DynamicServlet");

        // Register ServletRequestListener
        sc.addListener("web.servlet.dynamicregistration_war.TestServletRequestListener");
    }
  • 基于ServletContainerInitializer
    需要在jar包含META-INF/services/javax.servlet.ServletContainerInitializer文件,文件内容为已经实现ServletContainerInitializer接口的类:例如springWeb中的实现
    org.springframework.web.SpringServletContainerInitialzer
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List initializers = new LinkedList<>();

        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)
                                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);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

@HandlesTypes注解表示SpringServletContainerInitializer 处理的类,被它标明的类需要作为参数值传入到onStartup方法,在onStartup 方法中,可以通过Set> c 获取得到。
ContextConfig监听器负责在容器启动时读取每个web应用的WEB-INF/lib目录下包含的jar包的META-INF/services/javax.servlet.ServletContainerInitializer,以及web根目录下的META-INF/services/javax.servlet.ServletContainerInitializer,通过反射完成这些ServletContainerInitializer的实例化,然后再设置到Context容器中,最后Context容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法

你可能感兴趣的:(Servlet3.0——模块化与动态Servlet)