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也会覆盖动态注册的

后续练习...

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

Servlet 3.1实践的更多相关文章

  1. 异步Servlet的理解与实践

    AsyncContext理解 Servlet 3.0(JSR315)定义了Servlet/Filter的异步特性规范. 怎么理解"异步Servlet/Filter"及其使用情景? ...

  2. Java Web基础 --- Servlet 综述(理论篇)

    摘要: Web 技术成为当今主流的互联网 Web 应用技术之一,而 Servlet 是 Java Web 技术的核心基础.本文首先从请求/响应架构应用的大背景谈起 Servlet 的由来,明确 Ser ...

  3. JSP的简单介绍

    什么是JSP? JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术. JSP这门技术的最大的特点在于,写jsp就像在写htm ...

  4. paip.spring3 mvc servlet的配置以及使用最佳实践

    paip.spring3 mvc servlet的配置以及使用最佳实践 1. Web.xml 1 2. springMVC.xml 2 1. mvcAction .mvcAction 2 2. Res ...

  5. 云原生实践之 RSocket 从入门到落地:Servlet vs RSocket

    技术实践的作用在于:除了用于构建业务,也是为了验证某项技术或框架是否值得大规模推广. 本期开始,我们推出<RSocket 从入门到落地>系列文章,通过实例和对比来介绍RSocket.主要围 ...

  6. Java Servlet开发的轻量级MVC框架最佳实践

    在Servlet开发的工程实践中,为了减少过多的业务Servlet编写,会采用构建公共Servlet的方式,通过反射来搭建轻量级的MVC框架,从而加快应用开发. 关于Servlet开发的基础知识,请看 ...

  7. 最佳实践: 勿在 Servlet 中实现 SingleThreadModel

    摘要 请不要实现 SingleThreadModel 接口.这种实践将导致 Web 容器创建多个 servlet 实例:即为每个用户创建一个实例.对于任何大小的应用程序,这种实践都将导致严重的性能问题 ...

  8. Java Servlet DAO实践(二)

    Java Servlet DAO实践(二) DAO连接类 package com.seller.servlets.dao; import java.sql.*; public class DataBa ...

  9. Servlet和JSP学习指导与实践(三):JSP助阵

    前言: JSP(Java Server Page)虽然作为一门服务端的语言,但它并没有创新新的语言标准.有些人一接触jsp之后发现易学易懂.实际上,jsp的内部原理仍然是基于Servlet,它是Ser ...

随机推荐

  1. ArcGIS Engine中正确释放打开资源

    转自原文 ArcGIS Engine中正确释放打开资源 AE中对MDB,SDE等数据库操作时,打开后却往往不能及时释放资源,导致别人操作提示对象被锁定. 很多帖子说了很多原理,看的也烦且不实用,比如一 ...

  2. [Postgre] Insert Data into Postgre Tables

    // Insert one row INSERT INTO movies (title, release_date, count_stars, director_id) VALUES ( 'Kill ...

  3. [NPM] Use package.json variables in npm scripts

    In this lesson we will show that you can leverage values that you already have provided in your pack ...

  4. Android中获取当前位置的使用步骤

    在Android中得到当前位置的步骤 1.在AndroidManifest.xml中声明权限 android.permission.ACCESS_FINE_LOCATION(或者android.per ...

  5. 【z01】铺地毯

    [问题描述] 为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标 系的第一象限)铺上一些矩形地毯.一共有 n 张地毯,编号从 1 到n .现在将这些地毯按照 编号从小到大的顺 ...

  6. python 多线程拷贝单个文件

    # -*- coding: utf-8 -*- # @author: Tele # @Time : 2019/04/04 下午 12:25 # 多线程方式拷贝单个文件 import threading ...

  7. NotifyICon使用

    2010-04-11 15:47 by Ju2ender, 1438 visits, 网摘, 收藏, 编辑 最常见使用NotifyIcon的程序就是QQ了,当初我非常好奇这通知区域的小企鹅是怎样随着Q ...

  8. erlang判断模块导出函数问题

    erlang本身提供一个接口,可以用来检查模块是否有导出函数,这个接口是erlang:function_exported/3,但是很多时候这个接口无法正常使用. 下面重现一下这个问题: 1> e ...

  9. 城市三级联动 AJAX-原生js封装

    话不多说我们先来一张效果图给大家看一下: html代码如下: <!DOCTYPE html><html lang="en"><head> < ...

  10. oracle的number的浅析

    author:skate time:2011-02-14 oracle的number的浅析 从例如以下几个方面来认识number 1.表示的数值范围   2.占用的存储空间   3.number的性能 ...