天天看點

Servlet詳解(四): Servlet3.0新特性詳解基于注解的配置檔案上傳API異步處理元件可插性動态注冊元件ServletContainerInitializer

Servlet2.5是JavaEE5.0規範,最低運作環境為JDK5.0以及Tomcat5.0。而Servlet3.0是JavaEE6.0規範,最低環境為JDK6.0以及Tomcat7.0。Servlet3.0的新特性主要分為以下幾個點:

  1. 基于注解配置三大元件
  2. 檔案上傳API的優化
  3. 異步處理
  4. 元件可插性
  5. 動态注冊三大元件(ServletContext)
  6. SPI - ServletContainerInitializer 機制
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.0.1</version>
    <scope>provided</scope>
</dependency>
           

基于注解的配置

基于web.xml的配置與基于注解的配置,在Servlet3.0中,可以混合使用

@WebServlet

用來注冊servlet元件,在servlet2.x中,需要在web.xml中配置servlet标簽:

<servlet>
    <!--servlet描述-->
    <description>Servlet描述</description>
    <!--servlet展示名稱-->
    <display-name>TestServlet</display-name>
    <!--servlet名稱-->
    <servlet-name>TestServlet</servlet-name>
    <!--servlet class-->
    <servlet-class>com.yangsx95.demo.TestServlet</servlet-class>
    <!--servlet 初始化參數-->
    <init-param>
      <param-name>param1</param-name>
      <param-value>value1</param-value>
    </init-param>
    <init-param>
      <param-name>param2</param-name>
      <param-value>value2</param-value>
    </init-param>
    <!--servlet加載順序-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <!--servlet url 映射-->
  <servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/test</url-pattern>
  </servlet-mapping>
           

在3.0 可以使用如下方式注冊一個Servlet:

//@WebServlet("test") // value 就是 urlPatterns,是預設屬性,二者不能同時使用
@WebServlet(
        description = "Servlet描述",
        displayName = "TestServlet",
        name = "TestServlet",
        initParams = {@WebInitParam(name="param1", value = "value1"), @WebInitParam(name="param2", value = "value2")},
        loadOnStartup = 1,
        urlPatterns = {"test", "aaa"}
)
public class TestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 擷取servlet名稱
        this.getServletName();
        // 擷取servlet初始化參數
        Enumeration<String> initParameterNames = this.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            System.out.println(initParameterNames.nextElement());
        }
        resp.getWriter().append("servlet test");
    }
}
           

@WebFilter

xml配置:

<filter>
    <description>TestFilter描述</description>
    <display-name>TestFilter</display-name>
    <!--Filter名稱-->
    <filter-name>TestFilter</filter-name>
    <!--Filter class-->
    <filter-class>com.yangsx95.demo.TestFilter</filter-class>
    <!--初始化參數-->
    <init-param>
        <param-name>param1</param-name>
        <param-value>value1</param-value>
    </init-param>
    <init-param>
        <param-name>param2</param-name>
        <param-value>value2</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>TestFilter</filter-name>
    <!--filter 過濾規則-->
    <!--過濾路徑-->
    <!--<url-pattern>/*</url-pattern>-->
    <!--過濾指定的servlet-->
    <servlet-name>TestServlet</servlet-name>
</filter-mapping>
           

對應的Servlet3.0 注解配置:

@WebFilter(
        displayName = "TestFilter",
        filterName = "TestFilter",
        initParams = {@WebInitParam(name="param1", value = "value1"), @WebInitParam(name="param2", value = "value2")},
//        urlPatterns = {"/*"},  // 過濾路徑
        servletNames = {"TestServlet"} // 過濾指定的servlet
)
public class TestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 擷取初始化參數
        // filterConfig.getInitParameter();
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("-------------start-----------");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("--------------end------------");
    }

    @Override
    public void destroy() {

    }
}
           

@WebListener

<listener>
    <listener-class>com.yangsx95.demo.TestListener</listener-class>
  </listener>
           

對應的Servlet3.0注解方式也很簡單:

@WebListener
public class TestListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("應用程式啟動");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("應用程式關閉");
    }
}

           

檔案上傳API

@WebServlet("/upload")
@MultipartConfig   // 代表該servlet可以處理multipart請求
public class UploadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 擷取伺服器本地路徑
        String txtPath = this.getServletContext().getRealPath("/txts");

        Part file = req.getPart("file");// 檔案表單對應的input name
        // 擷取指定的頭部屬性
//        file.getHeader("Content");
        file.write(txtPath + File.separator + "111.txt"); // 将檔案寫入到磁盤
    }
}
           

異步處理

部分servlet需要做一些很長的耗時操作,這時,就需要servlet提前響應前端使用者,自己背景繼續執行邏輯,比如郵件發送、短信通知、複雜耗時任務提前傳回進行中。這就是servlet的異步處理。

下面是代碼展示:

@WebServlet(value = "/register", asyncSupported = true) // 開啟異步支援
public class RegisterServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 擷取異步上下文對象,包含request資訊,response資訊
        AsyncContext ac = req.getAsyncContext();
        // 建立異步Runnable對象
        ComputeRunnable computeRunnable = new ComputeRunnable(ac);
        // 異步處理逾時時間
        ac.setTimeout(30 * 1000);
        // 設定異步監聽器
        ac.addListener(new AsyncListener() {
            @Override
            public void onComplete(AsyncEvent asyncEvent) throws IOException {
                System.out.println("異步任務完成");
            }

            @Override
            public void onTimeout(AsyncEvent asyncEvent) throws IOException {
                System.out.println("異步任務逾時");
            }

            @Override
            public void onError(AsyncEvent asyncEvent) throws IOException {
                System.out.println("異步任務出錯");
            }

            @Override
            public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
                System.out.println("異步任務開始執行");
            }
        });
        // 異步執行runnable對象
        ac.start(computeRunnable);

        // 立即響應
        resp.getWriter().print("成功,稍後會給您發送郵件");
    }
}

public class ComputeRunnable implements Runnable {

    private AsyncContext asyncContext;

    public ComputeRunnable(AsyncContext context) {
        this.asyncContext = context;
    }

    @Override
    public void run() {
        System.out.println(asyncContext.getRequest());
        System.out.println(asyncContext.getResponse());
    }
}

           

元件可插性

JavaEE6.0項目支援将打為Jar包的Servlet、Filter、Listener直接插入到運作的web項目中,這些jar中包含相應的配置檔案,這些jar也稱為web片斷項目。

定義一個web-fragment項目很容易,隻需要在web-inf下建立檔案web-fragment即可:

<?xml version="1.0" encoding="UTF-8"?> 
<web-fragment xmlns="http://java.sun.com/xml/ns/javaee" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee  
    http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd"  
     version="3.0"> 
    <name>WebFragment1</name> 
    <servlet> 
        <servlet-name>hi</servlet-name> 
        <servlet-class>com.yangsx95.HiServlet</servlet-class> 
    </servlet> 
    <servlet-mapping> 
        <servlet-name>hi</servlet-name> 
        <url-pattern>/hi.view</url-pattern> 
    </servlet-mapping> 
</web-fragment> 
           

将此項目打包為jar,然後放入web項目的lib目錄下,就可以通路web片段項目中的servlet了。

動态注冊元件

也就是在代碼中,通過一些api,比如addServlet動态添加元件,處于安全考慮,元件的動态注冊,隻能在應用啟動時執行。

@WebListener
public class MyServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        // 擷取ServletContext
        ServletContext servletContext = servletContextEvent.getServletContext();
        ServletRegistration.Dynamic servletName = servletContext.addServlet("servletName", TestServlet.class);
        servletName.addMapping("/test");

        FilterRegistration.Dynamic filterName = servletContext.addFilter("filterName", TestFilter.class);
        filterName.setInitParameter("param2", "value2");
        filterName.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        servletContext.addListener( TestListener.class);
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

           

ServletContainerInitializer

Serlvet容器在啟動容器的時候,會掃描目前應用中的每個jar中的

META-INF/services/javax.servlet.ServletContainerInitializer

中指定的實作類,啟動并運作這個實作類的方法。這是一種SPI機制。

示例代碼:

@HandlesTypes({Person.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {

    // 參數1是 HandlesTypes 指定的類型集合。 将系統中所有HandlesType定義的類型的類,也就是子類/實作類(這裡是Person類的子類/實作類),都傳遞給這個接口;
    // 參數2是ServletContext對象,用于在啟動時向應用程式動态注冊元件
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        servletContext.addListener(MyServletContextListener.class);
    }

}

           

配置

META-INF/services/javax.servlet.ServletContainerInitializer

com.yangsx95.demo.MyServletContainerInitializer