Servlet 3.0 新特性

LZC發表於2020-02-12

Servlet3.0 提供了 @WebServlet@WebFilter 等註解,這樣便有了拋棄 web.xml 的第一個途徑,憑藉註解宣告 Servlet 和 Filter 來做到這一點。

除了這種方式,Servlet3.0 規範還提供了更強大的功能,可以在執行時動態註冊 Servlet ,Filter,listener。以 Servlet 為例,過濾器與監聽器與之類似。ServletContext 為動態配置 Servlet 增加了如下方法:

  • ServletRegistration.Dynamic addServlet(String servletName,Class<? extends Servlet> servletClass)

  • ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)

  • ServletRegistration.Dynamic addServlet(String servletName, String className)

  • T createServlet(Class clazz)

  • ServletRegistration getServletRegistration(String servletName)

  • Map<String,? extends ServletRegistration> getServletRegistrations()

  • Map<String,? extends ServletRegistration> getServletRegistrations()

其中前三個方法的作用是相同的,只是引數型別不同而已;
透過 #createServlet(Class clazz) 方法建立的 Servlet,通常需要做一些自定義的配置,然後使用 #addServlet(...) 方法來將其動態註冊為一個可以用於服務的 Servlet。
兩個 #getServletRegistration() 方法主要用於動態為 Servlet 增加對映資訊,這等價於在 web.xml 中使用 標籤為存在的 Servlet 增加對映資訊。

以上新增的方法一般在如下情況呼叫:

  1. javax.servlet.ServletContextListener#contextInitialized(ServletContextEvent sce) 方法中呼叫。
  2. javax.servlet.ServletContainerInitializer#onStartup(Set<Class<?>> c, ServletContext ctx) 方法中呼叫。

ServletContainerInitializer 也是 Servlet 3.0 新增的一個介面,容器在啟動時使用 JAR 服務 API(JAR Service API) 來發現 ServletContainerInitializer 的實現類,並且容器將 WEB-INF/lib 目錄下 JAR 包中的類都交給該類的 #onStartup(...) 方法處理,我們通常需要在該實現類上使用 @HandlesTypes 註解來指定希望被處理的類,過濾掉不希望給 #onStartup(...) 處理的類。
Servlet-HelloWorld配置的web.xml去掉,編寫如下類

package com.lzc.servlet;
import javax.servlet.*;
import java.util.Set;
/**
 * Created by lzc
 * 2020/2/12 21:39
 */
public class CustomServletContainerInitializer implements ServletContainerInitializer {
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("CustomServletContainerInitializer create ServletHelloWorld ...");
        ServletRegistration.Dynamic helloServlet = servletContext.addServlet("HelloWorld", ServletHelloWorld.class);
        helloServlet.addMapping("/HelloWorld");
    }
}

ServletContext 我們稱之為 servlet 上下文,它維護了整個 web 容器中註冊的 servlet,filter,listener,以 servlet 為例,可以使用 servletContext.addServlet 等方法來新增 servlet。

這麼宣告一個 ServletContainerInitializer 的實現類,web 容器並不會識別它,所以,需要藉助 SPI 機制來指定該初始化類,這一步驟是透過在專案的 resources 路徑下建立 META-INF/services/javax.servlet.ServletContainerInitializer 來做到的,它只包含一行內容:

com.lzc.servlet.CustomServletContainerInitializer

使用 ServletContainerInitializer 和 SPI 機制,我們的 web 應用便可以徹底擺脫 web.xml 了。

方法入參中 Set<Class<?>> set@HandlesTypes 註解的使用
首先編寫一個介面和該介面的實現類

public interface Hello {
    void hello();
}

public class HelloWorld implements Hello {
    @Override
    public void hello() {
        System.out.println("HelloWorld...");
    }
}

改造 CustomServletContainerInitializer

@HandlesTypes({Hello.class})
public class CustomServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        for (Class<?> cls : set) {
            try {
                Hello hello = (Hello)cls.newInstance();
                hello.hello();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        System.out.println("CustomServletContainerInitializer create ServletHelloWorld ...");
        ServletRegistration.Dynamic demoServlet = servletContext.addServlet("HelloWorld", ServletHelloWorld.class);
        demoServlet.addMapping("/HelloWorld");
    }
}

執行上面程式碼即可看到效果
@HandlesTypes指定需要處理的類,Set<Class<?>> set攜帶了所有@HandlesTypes指定的類

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章