• 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)练习.

    准备工具: 配置嵌入式Tomcat

    Maven依赖:

    <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实现:

    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);
    		}
    	}
    
    }
    

    代码功能:

    1. 支持http启动
    2. 支持https启动, 需用keytool创建keystore.
    3. 支持http + https启动, 其中http转发https.
      以上用于测试

    另外Tomcat自带的StandardJarScanner的processManifest()在jar的META-INF/manif.mf含有"Class-Path"项时处理不大正确. 复制其源码保存为WebappStandardJarScanner.java(package相同),并注释下述语句:

        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配置

    <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会显示表示错误的红叉叉...

    练习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"不影响动态注册.

    练习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特性会被关闭.

    • 执行结果: 访问http://localhost/test,调用TestServlet2的业务逻辑,证明web-fragment.xml也会覆盖动态注册的

    后续练习...

    随时有想法都可以快速尝试

  • 相关阅读:
    B 基因改造
    A 密码锁
    Leetcode(884)-索引处的解码字符串
    Leetcode(885)- 救生艇
    Leetcode(23)-合并K个排序链表
    关于优先队列的总结II
    重载运算符问题
    Leetcode(22)-括号生成
    Leetcode(102)-二叉树的层次遍历
    Leetcode(82)-删除排序链表中的重复元素 II
  • 原文地址:https://www.cnblogs.com/zolo/p/5912185.html
Copyright © 2020-2023  润新知