渐行渐远的Servlet

Servlet简介

Servlet 是一种基于 Java 技术的 Web 组件,用于生成动态内容,由容器管理。类似于其他 Java 技术组件,Servlet 是平台无 关的 Java 类组成,并且由 Java Web 服务器加载执行。通常情况,由 Servlet 容器提供运行时环境。Servlet 容器,有时候也 称作为 Servlet 引擎,作为Web服务器或应用服务器的一部分。通过请求和响应对话,提供Web 客户端与 Servlets 交互的能 力。容器管理Servlets实例以及它们的生命周期。

从功能上,Servlet 介于 CGI(Common Gateway Interface)与服务扩展(如:Netscape Server API 或 Apache 模块)之 间。

在体系上,Servlet 技术(或者规范)属于 Java EE 技术(规范)的一部分。不过 Servlet 并非一开始就隶属于 J2EE 或者 Java EE。接下来的小节将会介绍 Servlet 各个版本。

Servlet 版本

规范版本 发布时间 Java平台 主要更新
Servlet 4.0 2017年9月 Java EE 8 支持Http/2
Servlet 3.1 2013年5月 Java EE 7 非阻塞I/O、Http协议更新机制(WebSocket)
Servlet 3.0 2009年12月 Jave EE 6 可插拔、简化部署、异步Servlet、安全、文件上传
Servlet 2.5 2005年9月 Java EE 5 Annotation支持
Servlet 2.4 2003年11月 J2EE 1.4 web.xml 支持 XML Scheme
Servlet 2.3 2001年8月 J2EE 1.3 新增Filter、事件/监听器、Wrapper
Servlet 2.2 1999年8月 J2EE 1.2 作为J2EE的一部分,以.war 文件作为独立web应用

Servlet 核心API

核心组件API 说明 起始版本 SpringFramework代表实现
javax.servlet.Servlet 动态内容组件 1.0 DispatcherServlet
javax.servlet.Filter Servlet过滤器 2.3 CharacterEncodingFilter
javax.servlet.ServletContext Servlet应用上下文
javax.servlet.AsyncContext 异步上下文 3.0
javax.servlet.ServletContextListener ServletContext生命周期监听器 2.3 ContextLoaderListener
javax.servlet.ServletRequestListener ServletRequest生命周期监听器 2.3 RequestContextListener
javax.servlet.http.HttpSessionListener HttpSession生命周期监听器 2.3 HttpSessionMutexListener
javax.servlet.AsyncListener 异步上下文监听器 3.0 StandardServletAsyncWebRequest
javax.servlet.ServletContainerInitializer Servlet容器初始化器 3.0 SpringServletContainerInitializer

Servlet 组件注册

Servlet注册

注册方式 传统方式 注解方式 编程方式
Servlet注册 web.xml 部署<servlet> + <servlet-mapping> @WebServlet ServletContext#addServlet
Fileter注册 web.xml部署<filter> + <filter-mapping> @WebFilter ServletContext#addFilter
*Listener注册 web.xml 部署<listener> @WebListener ServletContext#addListener
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-
app_2_5.xsd"
         metadata-complete="true" version="2.5">
    <context-param>
        <description>
Spring 配置文件路径参数,
该参数值将被 org.springframework.web.context.ContextLoaderListener 使用 </description>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:/META-INF/spring/spring-context.xml
        </param-value>
    </context-param>
    <listener>
        <description>
org.springframework.web.context.ContextLoaderListener 为可选申明Listener </description>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

Spring Servlet Web

理解Servlet生命周期

  • 初始化:init(ServletConfig)
  • 服务:service(ServletRequest,ServletResponse)
  • 销毁:destroy()

DispatcherServlet 初始化过程

Servlet初始化过程

理解Filter生命周期

  • 初始化
  • 服务
  • 销毁

理解ServletContext生命周期

  • 初始化:contextInitialized(ServletContextEvent)
  • 销毁:contextDestroyed(ServletContextEvent)

Servlet 异步支持

@WebServlet(asyncSupported = true,//激活异步特性
        name = "asyncServlet",// servlet 名字
        urlPatterns = "/async-servlet")
public class AsyncServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
        // 判断是否支持异步
        if (request.isAsyncSupported()) {
            // 创建 AsyncContext
            AsyncContext asyncContext = request.startAsync();
            // 设置超时时间
            asyncContext.setTimeout(50L);
            asyncContext.addListener(new AsyncListener() {
                @Override
                public void onComplete(AsyncEvent event) throws IOException {
                    println("执行完成");
                }

                @Override
                public void onTimeout(AsyncEvent event) throws IOException {
                    HttpServletResponse servletResponse = (HttpServletResponse) event.getSuppliedResponse();
                    servletResponse.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
                    println("执行超时");
                }

                @Override
                public void onError(AsyncEvent event) throws IOException {
                    println("执行错误");
                }

                @Override
                public void onStartAsync(AsyncEvent event) throws IOException {
                    println("开始执行");
                }
            });
        }

            println("Hello,World");
//            ServletResponse servletResponse = asyncContext.getResponse();
//            // 设置响应媒体类型
//            servletResponse.setContentType("text/plain;charset=UTF-8");
//            // 获取字符输出流
//            PrintWriter writer = servletResponse.getWriter();
//            writer.println("Hello,World");
//            writer.flush();
    }

        private static void println(Object object) {
            String threadName = Thread.currentThread().getName();
            System.out.println("AsyncServlet[" + threadName + "]: " + object);
        }

}

DeferredResult 支持

@GetMapping("/hello-world")
    public DeferredResult<String> helloWorld() {
        DeferredResult<String> result = new DeferredResult<>(50L);
//        result.setResult("Hello,World");
        // 入队操作
//        queue.offer(result);
        println("Hello,World");
        result.onCompletion(() -> {
            println("执行结束");
        });

        result.onTimeout(() -> {
            println("执行超时");
        });

        return result;
    }

Callable支持

@GetMapping("/callable-hello-world")
    public Callable<String> callableHelloWorld() {
        final long startTime = System.currentTimeMillis();

        println("Hello,World");

        return () -> {
            long costTime = System.currentTimeMillis() - startTime;
            println("执行计算结果,消耗:" + costTime + " ms.");
            return "Hello,World";
        };
    }

CompletionStage支持

@GetMapping("/completion-stage")
    public CompletionStage<String> completionStage(){
        final long startTime = System.currentTimeMillis();

        println("Hello,World");

        return CompletableFuture.supplyAsync(()->{
            long costTime = System.currentTimeMillis() - startTime;
            println("执行计算结果,消耗:" + costTime + " ms.");
            return "Hello,World"; // 异步执行结果
        });
    }

Spring Web MVC异步Servlet实现原理

Java Specification Requests (JSR) : https://github.com/mercyblitz/jsr

Spring Boot Servlet Web

Spring Boot嵌入式Servlet容器限制

Servlet特性 兼容性 解决方案
web.xml 不支持 RegistrationBean或 @Bean 注册
ServletContainerInitializer 不支持 ServletContextInitializer
@WebServlet等 有限支持 依赖@ServletComponentScan

参考资料一

87.2 Convert an Existing Application to Spring Boot

you may need to add some configuration to your Application context, by replacing those elements from the web.xml , as follows:
A @Bean of type Servlet or ServletRegistrationBean installs that bean in the container as if it were a <servlet/> and <servlet-mapping/> in web.xml .
A @Bean of type Filter or FilterRegistrationBean behaves similarly (as a <filter/> and <filter- mapping/> ).
An ApplicationContext in an XML file can be added through an @ImportResource in your Application . Alternatively, simple cases where annotation configuration is heavily used already can be recreated in a few lines as @Bean definitions.

参考资料二

27.4.2 Servlet Context Initialization

Embedded servlet containers do not directly execute the Servlet 3.0+
javax.servlet.ServletContainerInitializer interface or
Spring’s org.springframework.web.WebApplicationInitializer interface. This is an intentional design decision intended to reduce the risk that third party libraries designed to run inside a war may break Spring Boot applications.

参考资料三

Scanning for Servlets, Filters, and listeners

When using an embedded container, automatic registration of classes annotated
with @WebServlet , @WebFilter , and @WebListener can be enabled by using @ServletComponentScan .

Spring Boot Servlet注册

通过RegistrationBean 注册

  • ServletContextInitializer

    • RegistrationBean
      • ServletListenerRegistrationBean
        • @WebListener
      • FilterRegistrationBean
        • @WebFilter
      • ServletRegistrationBean
        • @WebServlet

    @ServletComponentSacn 扫描package->@Web* ->RegistrationBean Bean定义 -> RegistrationBean Bean

通过@Bean注册

通过@ServletComponentScan注册

Spring Boot应用传统Servlet容器部署

基本原理

扩展SpringBootServletInitializer

public class DefaultSpringBootServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        builder.sources(SpringBootServletBootstrap.class);
        return builder;
    }

}
@EnableAutoConfiguration
@ServletComponentScan(basePackages = "com.imooc.spring.web.servlet")
class SpringBootServletBootstrap {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootServletBootstrap.class, args);
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ServletRegistrationBean asyncServletServletRegistrationBean(){
        ServletRegistrationBean registrationBean =  new ServletRegistrationBean(new AsyncServlet(),"/");
        registrationBean.setName("MyAsyncServlet");
        return registrationBean;
    }

    @Bean
    public ServletContextInitializer servletContextInitializer() {
        return servletContext -> {
            CharacterEncodingFilter filter = new CharacterEncodingFilter();
            FilterRegistration.Dynamic registration = servletContext.addFilter("filter", filter);
            registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/");
        };
    }
}

使用Tomcat 7插件(Servlet3.0)

 
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.1</version>
            <executions>
                <execution>
                    <id>tomcat-run</id>
                    <goals>
                        <goal>exec-war-only</goal>
                    </goals>
                    <phase>package</phase>
                    <configuration>
                        <!-- ServletContext path -->
                        <path>/</path>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

使用 Tomcat 8 插件(Servlet 3.1)

<build>
        <plugins>
            <!-- Tomcat 8 Maven 插件用于构建可执行 war -->
            <!-- https://mvnrepository.com/artifact/org.apache.tomcat.maven/tomcat8-maven-plugin --> <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat8-maven-plugin</artifactId>
            <version>3.0-r1655215</version>
            <executions>
                <execution>
                    <id>tomcat-run</id>
                    <goals>
                        <!-- 最终打包成可执行的jar包 -->
                        <goal>exec-war-only</goal>
                    </goals>
                    <phase>package</phase>
                    <configuration>
                        <!-- ServletContext 路径 -->
                        <path>/</path>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        </plugins>
    </build>
    <pluginRepositories>
        <pluginRepository>
            <!-- tomcat8-maven-plugin 所在仓库 -->
            <id>Alfresco</id>
            <name>Alfresco Repository</name> <url>https://artifacts.alfresco.com/nexus/content/repositories/public/</url> <snapshots>
            <enabled>false</enabled>
        </snapshots>
        </pluginRepository>
    </pluginRepositories>
  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容