Servlet 3.1實踐

Servlet 3.1 新特性詳解

  • 參考:
  • 關鍵特性
    • Asynchronization(異步支持): 經過AsyncContext另起異步線程處理業務邏輯, 再也不阻塞Filter或Servlet線程. 能夠實現Reactor模式.
    • Pluggability(插撥支持): 經過Annotation(@WebServlet, @WebFilter, @WebListener, @WebInitParam, @MultipartConfig, @ServletSecurity, @HttpConstraint, @HttpMethodConstraint)或/META-INF/web-fragment.xml充分解耦組件,實現插撥機制.
      注意: 在web.xml設置metadata-complete="true"能夠關閉Pluggability特性. 但不影響@HandlesTypes+ServletContainerInitializer的動態註冊機制.
    • Fileupload(文件上傳): 經過@MultipartConfig+Part實現文件上傳. 再也不須要apache-fileupload.
    • Dynamic Registration(動態註冊):
      • 經過ServletContext.addXXX()+Registration實現.
      • 相關API只能在ServletContextListener.contextInitialized()或者ServletContainerInitializer.onStartup()中調用. 後者是基於java服務API機制實現, 須要在(/META-INF/services/javax.servlet.ServletContainerInitializer)配置實現類.
      • 動態註冊不受web.xml中metadata-complete="true"影響,但優先級低於web.xml, 相同設置會被覆蓋.
    以上4點是Servlet 3.0/3.1的關鍵特性, 其中
    • Pluggabilityr的目標是"充分解耦,實現插撥"
    • Dynamic Registration的目標是"動態註冊,編程"
    • Asynchronization的目標是提升併發性能
    • FileUpload, Annotation是增長編程方便

Servlet 3.1 新特性練習

Jetty9對於Servlet 3.1的支持很不友善, 配置起來使人不舒服! 在Tomcat 7/8/9都支持Embedded. 後面使用Tomcat8(要求JDK7, 支持Servlet3.1/JSP2.3)練習.html

準備工具: 配置嵌入式Tomcat

Maven依賴:java

<properties>
    <spring.version>4.2.7.RELEASE</spring.version>
    <tomcat.version>8.5.5</tomcat.version>
</properties>

<dependencies>
    <!-- ================================ -->
    <!-- spring test frameworks -->
    <!-- ================================ -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <!-- ================================ -->
    <!-- junit frameworks -->
    <!-- ================================ -->
    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.github.stefanbirkner/system-rules -->
    <dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-rules</artifactId>
    <version>1.16.1</version>
    </dependency>


    <!-- ================================ -->
    <!-- tomcat frameworks -->
    <!-- ================================ -->
    <!-- https://devcenter.heroku.com/articles/create-a-java-web-application-using-embedded-tomcat#add-a-launcher-class -->
    <dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>${tomcat.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>${tomcat.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-websocket</artifactId>
    <version>${tomcat.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jasper</artifactId>
    <version>${tomcat.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jasper-el</artifactId>
    <version>${tomcat.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jsp-api</artifactId>
    <version>${tomcat.version}</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.3</version>
            <configuration>
                <source>1.7</source>
                <target>1.7</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.4</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <goals>
                    <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Java實現:git

public final class EmbedTomcat extends RiseJUnitTester {

    public static final String WEBAPP_DIRECTORY = "src/main/webapp/";
    public static final String ROOT_CONTEXT = "";
    public static final int HTTP_PORT = 80;
    public static final int HTTPS_PORT = 443;
    public static final int OFF = -1;

    public static void start() {
        start(ROOT_CONTEXT, HTTP_PORT, OFF, null, null, null);
    }

    public static void start(int httpPort) {
        start(ROOT_CONTEXT, httpPort, OFF, null, null, null);
    }

    public static void start(String keyAlias, String password, String keystorePath) {
        start(ROOT_CONTEXT, OFF, HTTPS_PORT, keyAlias, password, keystorePath);
    }

    public static void start(int httpsPort, String keyAlias, String password, String keystorePath) {
        start(ROOT_CONTEXT, OFF, httpsPort, keyAlias, password, keystorePath);
    }

    public static void start(int httpPort, int httpsPort, String keyAlias, String password, String keystorePath) {
        start(ROOT_CONTEXT, httpPort, httpsPort, keyAlias, password, keystorePath);
    }

    public static void start(String contextPath, int httpPort, int httpsPort, String keyAlias, String password, String keystorePath) {

        // FIX: A context path must either be an empty string or start with a '/' and do not end with a '/'.
        if (contextPath == null || contextPath.equals("/")) {
            contextPath = ROOT_CONTEXT;
        }
        try {
            // initial
            processSystemEnvironment();

            Tomcat tomcat = new Tomcat();

            if (httpsPort > 0) {
                tomcat.setPort(httpsPort);
                Connector httpsConnector = tomcat.getConnector();
                httpsConnector.setSecure(true);
                httpsConnector.setScheme("https");
                httpsConnector.setAttribute("keyAlias", keyAlias);
                httpsConnector.setAttribute("keystorePass", password);
                httpsConnector.setAttribute("keystoreFile", keystorePath);
                httpsConnector.setAttribute("clientAuth", "false");
                httpsConnector.setAttribute("sslProtocol", "TLS");
                httpsConnector.setAttribute("SSLEnabled", true);

                if (httpPort > 0) {
                    Connector httpConnector = new Connector();
                    httpConnector.setPort(httpPort);
                    httpConnector.setRedirectPort(httpsPort);
                    tomcat.getService().addConnector(httpConnector);
                }
            } else if (httpPort > 0) {
                tomcat.setPort(httpPort);
            }

            StandardContext ctx = (StandardContext) tomcat.addWebapp(contextPath, new File(WEBAPP_DIRECTORY).getAbsolutePath());
            // Declare an alternative location for your "WEB-INF/classes" dir Servlet 3.0 annotation will work
            WebResourceRoot resources = new StandardRoot(ctx);
            resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes", new File("target/classes").getAbsolutePath(), "/"));
            ctx.setResources(resources);
            ctx.setJarScanner(new WebappStandardJarScanner());
            ctx.setDefaultWebXml(new File("src/main/webapp/WEB-INF/web.xml").getAbsolutePath());
            // FixBug: no global web.xml found
            for (LifecycleListener ll : ctx.findLifecycleListeners()) {
                if (ll instanceof ContextConfig) {
                    ((ContextConfig) ll).setDefaultWebXml(ctx.getDefaultWebXml());
                }
            }

            tomcat.start();
            tomcat.getServer().await();
        } catch (Exception e) {
            throw new RuntimeException("tomcat launch failed", e);
        }
    }

}

代碼功能:github

  1. 支持http啓動
  2. 支持https啓動, 需用keytool建立keystore.
  3. 支持http + https啓動, 其中http轉發https.
    以上用於測試

另外Tomcat自帶的StandardJarScanner的processManifest()在jar的META-INF/manif.mf含有"Class-Path"項時處理不大正確. 複製其源碼保存爲WebappStandardJarScanner.java(package相同),並註釋下述語句:web

private static final Set<ClassLoader> CLASSLOADER_HIERARCHY;

    static {
        Set<ClassLoader> cls = new HashSet<>();
        // FixBug: fail to scan
        // ClassLoader cl = WebappStandardJarScanner.class.getClassLoader();
        // while (cl != null) {
        // cls.add(cl);
        // cl = cl.getParent();
        // }

        CLASSLOADER_HIERARCHY = Collections.unmodifiableSet(cls);
    }

準備工做: 建立war項目

Maven配置spring

<dependencies>
        <!-- https://mvnrepository.com/artifact/org.eclipse.jetty.aggregate/jetty-all -->

        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.1</version>
        </dependency>
        
        <!-- 準備工做: 嵌入式Tomcat -->
        <dependency>
            <groupId>com.yy.risedev</groupId>
            <artifactId>risedev-test</artifactId>
            <version>2.0.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>

        <plugins>
            <!-- compiler plugin -->
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <!-- source plugin -->
            <plugin>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <!-- 無web.xml時maven檢測錯誤 -->
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>

    </build>

注意failOnMissingWebXml須要配置爲false, 不然maven會顯示錶示錯誤的紅叉叉...apache

練習1: web.xml 與 ServletContainerIntializer同時存在

  • 在web.xml配置/test的Servlet,其類型爲TestServlet2
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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-app_3_0.xsd"
    version="3.0" metadata-complete="true">

    <servlet>
        <servlet-name>test</servlet-name>
        <servlet-class>servlet.TestServlet2</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>test</servlet-name>
        <url-pattern>/test</url-pattern>
    </servlet-mapping>

</web-app>
  • ServletContainerIntializer實現
    • 在/META-INF/services/javax.servlet.ServletContainerInitializer設置實現類. 嗯! 就是一句話
    servlet.MyServletContainerInitializer
    • MyServletContainerInitializer代碼:
    package servlet;
    
    import java.util.Set;
    
    import javax.servlet.ServletContainerInitializer;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.HandlesTypes;
    
    @HandlesTypes(WebAppIntializer.class)
    public class MyServletContainerInitializer implements ServletContainerInitializer {
    
        @Override
        public void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {
            for (Class<?> c : arg0) {
                if (WebAppIntializer.class.isAssignableFrom(c)) {
                    try {
                        ((TestWebAppIntializer) (c.newInstance())).onStartup(arg1);
                    } catch (InstantiationException | IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }
    • WebAppIntializer代碼:
    package servlet;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    
    public interface WebAppIntializer {
        void onStartup(ServletContext ctx) throws ServletException;
    }
    • TestWebAppIntializer代碼:
    package servlet;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRegistration;
    
    public class TestWebAppIntializer implements WebAppIntializer {
    
        public void onStartup(ServletContext ctx) throws ServletException {
            ServletRegistration.Dynamic dyna = ctx.addServlet("testServlet", "servlet.TestServlet");
            dyna.addMapping("/test");
        }
    
    }

    經過java服務API,會掃描@HandlesTypes的類型,而後傳遞給MyServletContainerInitializer.onStartup()執行. 假意相識的感受? 這是Spring IOC的DI特性. 看來Spring的影響不小哦!編程

  • 執行結果: 在訪問http://localhost/test時,執行的是TestServlet的邏輯, 而非TestServlet2. 即web.xml配置覆蓋了動態註冊. 若改爲/test2, 則能訪問到動態註冊的TestServlet2了. 同時, 也可看到metadata-complete="true"不影響動態註冊.api

練習2: web.xml與web-fragment.xml同時存在

  • /META-INF/web-fragment.xml
<?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" metadata-complete="true">

    <servlet>
        <servlet-name>test</servlet-name>
        <servlet-class>servlet.TestServlet2</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>test</servlet-name>
        <url-pattern>/test2</url-pattern>
    </servlet-mapping>

</web-fragment>
  • /WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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-app_3_0.xsd"
    version="3.0" metadata-complete="false">

</web-app>

注意: metadata-complete="false", 不然pluggability特性會被關閉.tomcat

  • 執行結果: 訪問http://localhost/test,調用TestServlet2的業務邏輯,證實web-fragment.xml也會覆蓋動態註冊的

後續練習...

隨時有想法均可以快速嘗試

相關文章
相關標籤/搜索