7-1 渐行渐远的Servlet

  • Spring5开始支持WebFlux,把以前Java Web应用开发从Servlet必 选变成可选,一个是Servlet Web,另外一个是React Web(WebFlux)

技术回顾

  • 传统Servlet部署
    • 通过web.xml
  • 传统Servlet组件
    • Servlet API本身
    • Filter
    • Listener:ServletContextListener
  • HandlerMethodReturnValueHandler
    • 关于异步Servlet实现
  • DispatcherServlet
  • Web自动装配

什么是 Servlet?

Servlet是一种基于Java技术的 Web组件,用于生成动态内容,由容器管理。类似于其他Java技术组件,Servlet是平台无关的 Java类组成,并且由 Java Web服务器加载执行。通常情况,由 Servlet 容器提供运行时环境。Servlet容器,有时候也称作为 Servlet引擎,作为Web服务器或应用服务器的一部分。通过请求和响应对话,提供Web客户端与 Servlets交互的能
力。容器管理Servlets实例以及它们的生命周期。
从功能上,Servlet介于 CGICommon Gateway Interface)与服务扩展(如:Netscape Server APIApache模块)之间。
在体系上,Servlet技术(或者规范)属于Java EE技术(规范)的一部分。不过 Servlet 并非一开始就隶属于 J2EE或者Java EE。接下来的小节将会介绍 Servlet各个版本。

Servlet版本

规范版本发布时间Java 平台主要更新
Servlet 4.02017 年 9 月Java EE 8支持 HTTP/2
Servlet 3.12013 年 5 月Java EE 7非阻塞 I/O、HTTP 协议更新机制(WebSocket)
Servlet 3.02009 年 12 月Java EE 6可插拔、简化部署、异步 Servlet、安全、文件上传
Servlet 2.52005 年 9 月Java EE 5Annotation 支持
Servlet 2.42003 年 11月J2EE 1.4web.xml 支持 XML Scheme
Servlet 2.32001 年 8月J2EE 1.3新增 Filter、事件/监听器、Wrapper
Servlet2.2 1999 年 8月J2EE 1.2作为 J2EE 的一部分, 以 .war 文件作为独立 web 应用

7-2 Servlet 核心 API

核心组件API 说明起始版本Spring Framework 代表实现
javax.servlet.Servlet动态内容组件1.0DispatcherServlet
javax.servlet.FilterServlet 过滤器2.3CharacterEncodingFilter
javax.servlet.ServletContextServlet应用上下文
javax.servlet.AsyncContext异步上下文3.0
javax.servlet.ServletContextListenerServletContext 生命周期监听器2.3ContextLoaderListener
javax.servlet.ServletRequestListenerServletRequest生命周期监听器2.3RequestContextListener
javax.servlet.http.HttpSessionListenerHttpSession 生命周期监听器2.3HttpSessionMutexListener
javax.servlet.AsyncListener异步上下文监听器3.0StandardServletAsyncWebRequest
javax.servlet.ServletContainerInitializerServlet 容器初始化器3.0SpringServletContainerInitializer

7-4 Servlet 注册

注册方式传统方式注解方式编程方式
Servlet注册web.xml部署 <servlet> + <servlet-mapping>@WebServletServletContext#addServlet
Filter注册web.xml部署 <filter> + <filter-mapping>@WebFilterServletContext#addFilter
*Listener注册web.xml部署 <listener>@WebListenerServletContext#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/webapp_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>

7-5 理解 Servlet 组件生命周期

理解 Servlet生命周期

  • 初始化:init(ServletConfig)
  • 服务: service(ServletRequest,ServletResponse)
  • 销毁: destroy()
DispatcherServlet初始化过程
HttpServlet.init() HttpServletBean.init() FrameworkServlet.initServletBean() FrameworkServlet.initWebApplicationContext() DispatcherServlet.onRefresh() DispatcherServlet.initStrategies() Servlet 初始化生命周期调用 将 ServletConfig 参绑定到Servlet 字段 初始化 Servlet 关联的 WebApplicationContext 触发 DispatcherServlet onRefresh 初始化 DispatcherServlet 各种组件 HttpServlet.init() HttpServletBean.init() FrameworkServlet.initServletBean() FrameworkServlet.initWebApplicationContext() DispatcherServlet.onRefresh() DispatcherServlet.initStrategies() DispatcherServlet 初始化过程

理解 Filter生命周期

  • 初始化: init(FilterConfig)
  • 服务: doFilter(ServletRequest,ServletResponse,FilterChain)
  • 销毁: destroy()

理解 ServletContext生命周期

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

7-6 Servlet 异步支持

DeferredResult支持

新建项目spring-servlet

  • 配置pom文件
pom.xml
    <packaging>war</packaging>
    <dependencies>

        <!-- Servlet 3.1 API 依赖-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- Spring Web MVC 依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>

    </dependencies>

    <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>
  • 配置文件
DefaultAnnotationConfigDispatcherServletInitializer.java
@ComponentScan(basePackages = "com.whaleson.web.controller")
//@Configuration
public class DefaultAnnotationConfigDispatcherServletInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() { // web.xml
        return new Class[0];
    }

    @Override
    protected Class<?>[] getServletConfigClasses() { // DispatcherServlet
        return new Class[]{
                getClass() // 返回当前类
        };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

  • Controller

HelloWorldAsyncController.java

@RestController
public class HelloWorldAsyncController {


    @GetMapping("/show/msg")
    public DeferredResult<String> showAsync(){

        DeferredResult<String> result = new DeferredResult<>();

        result.onCompletion(()->{
            println("I'm Completion!");
        });

        println("Hello Everyone!!!");

        result.setResult("Hello Everyone!!!");

        return result;
    }
    public static void println(Object object){

        String threadName = Thread.currentThread().getName();

        System.out.println("当前线程的名称是[" + threadName + "]内容是:" + object);

    }
}

运行

  • 打包

mvn -Dmaven.skip.test -U clean package

  • 运行

java -jar target\spring-servlet-0.0.1-SNAPSHOT-war-exec.jar

  • 浏览器输入Get请求地址

http://localhost:8080/show/msg

  • 浏览器输出结果

Hello Everyone!!!

  • 控制台输出结果

当前线程的名称是[http-bio-8080-exec-2]内容是:Hello Everyone!!!
当前线程的名称是[http-bio-8080-exec-2]内容是:I'm Completion!

  • 结论

当前程序还是同步的,因为是同一个线程。

7-9 DeferredResult 异步执行

实现方式
  • 激活()
  • 将结果放到另外一个线程

DefaultAnnotationConfigDispatcherServletInitializer.java
-)AbstractAnnotationConfigDispatcherServletInitializer.java
-)AbstractDispatcherServletInitializer.registerDispatcherServlet()
-) registration.setAsyncSupported(this.isAsyncSupported());

代码实现
  • HelloWorldAsyncController.java
@RestController
@EnableScheduling
public class HelloWorldAsyncController {

    private final BlockingQueue<DeferredResult<String>> queue = new ArrayBlockingQueue<DeferredResult<String>>(5);

    private final Random random = new Random();

    @Scheduled(fixedRate = 5000)
    public void process() throws InterruptedException {

        DeferredResult<String> result = null;

        do{
            result = queue.take();

            long timeout  = random.nextInt(3000);
            //模拟等待时间
            Thread.sleep(timeout);

            //计算结果
            result.setResult("Happy Every Day!");

            println("执行计算结果"+ timeout + "ms");
        }while (result != null);

    }
    @GetMapping("/show/msg")
    public DeferredResult<String> showAsync() throws Exception{

        DeferredResult<String> result = new DeferredResult<>(50L);

        queue.offer(result);

        println("Hello Everyone!!!");

        result.onCompletion(()->{
            //相当于finally操作
            println("I'm Completion!");
        });

        result.onTimeout(()->{
            println("I'm Timeout");
        });

        return result;
    }
    public static void println(Object object){

        String threadName = Thread.currentThread().getName();

        System.out.println("当前线程的名称是[" + threadName + "]内容是:" + object);

    }
}
  • 执行结果

当前线程的名称是[http-bio-8080-exec-1]内容是:Hello Everyone!!!
当前线程的名称是[http-bio-8080-exec-3]内容是:I'm Timeout
十月 24, 2020 3:05:15 下午 org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver logException警告: Resolved [org.springframework.web.context.request.async.AsyncRequestTimeoutException]
当前线程的名称是[http-bio-8080-exec-3]内容是:I'm Completion!
当前线程的名称是[pool-1-thread-1]内容是:执行计算结果2000ms

7-10 Callable 异步执行

@GetMapping("/callable")
    public Callable<String> callableHelloWorld(){

        println("Hello World ! My girl!");
        long startTime = System.currentTimeMillis();
        return () ->{
            println("执行程序消耗时间\t" + (System.currentTimeMillis() -startTime));
            return "hello world";
        };
    }
  • 缺点

没有回调;需要回调建议使用ListenableFutureCallback

7-11 CompletionStage 异步执行

关于CompletionStage<T>接口

它的实现有CompletableFuture<T>

  • HelloWorldAsyncController
@GetMapping("/completable")
    public CompletionStage<String> completionStageHelloWorld(){

        long startTime = System.currentTimeMillis();

        println("Hello CompletableFuture ! ");

        return CompletableFuture.supplyAsync(()-> {

            println("执行程序消耗时间\t" + (System.currentTimeMillis() -startTime));

            return "Goodbye CompletableFuture !";//异步执行结果

        });
    }
  • 浏览器地址

http://localhost:8080/completable

  • 输出结果

当前线程的名称是[http-bio-8080-exec-4]内容是:Hello CompletableFuture !
当前线程的名称是[ForkJoinPool.commonPool-worker-1]内容是:执行程序消耗时间17

7-12 MVC 异步支持原理分析

Spring Web MVC异步Servlet实现原理
  • HandlerMethodReturnValueHandler
  • Servlet 3.0 AsyncContext
  • DispatcherServlet整合

7-13 异步 Servlet 实现

Java Specification Requests (JSR)

  • 自定义Servlet继承HttpServlet
public class AsyncServlet extends HttpServlet {
}
  • 添加类注解WebServlet

    @WebServlet

    • 激活异步特性

    asyncSupported = true

    • 添加映射

    urlPatterns = "/async-servlet"

  • 重写service方法
    • 判断当前请求是否支持异步

    req.isAsyncSupported()

    • 创建AsyncContext

    AsyncContext asyncContext = req.startAsync();

    • 添加监听器

    asyncContext.addListener()

@WebServlet(
        asyncSupported = true,
        urlPatterns = "/async-servlet"
)
public class AsyncServlet extends HttpServlet {

    @Override
    public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        if (req.isAsyncSupported()) {

            println("Hey man.");
            AsyncContext asyncContext = req.startAsync();

            asyncContext.setTimeout(50L);

            asyncContext.addListener(new AsyncListener() {
                @Override
                public void onComplete(AsyncEvent event) throws IOException {
                    println("complete");

                }

                @Override
                public void onTimeout(AsyncEvent event) throws IOException {

                    println("timeout");

                }

                @Override
                public void onError(AsyncEvent event) throws IOException {

                    println("error");
                }

                @Override
                public void onStartAsync(AsyncEvent event) throws IOException {

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

    public static void println(Object object) {

        String threadName = Thread.currentThread().getName();

        System.out.println("当前线程的名称是[" + threadName + "]内容是:" + object);

    }
}
  • 浏览器地址

    http://localhost:8080/async-servlet

  • 浏览器内容

    Hello

  • 控制台内容

    当前线程的名称是[http-bio-8080-exec-1]内容是:Hey man.
    当前线程的名称是[http-bio-8080-exec-3]内容是:timeout
    当前线程的名称是[http-bio-8080-exec-3]内容是:complete

7-14 DefferedResult 实现原理

DeferredResultMethodReturnValueHandler

7-15 Spring Boot 嵌入式 Servlet 容器限制

Servlet 特性兼容性解决方案
web.xml不支持RegistrationBean 或 @Bean 注册
ServletContainerInitializer不支持ServletContextInitializer
@WebServlet 等有限支持依赖 @ServletComponentScan
pom.xml
  • 引入spring-servlet
<dependency>
   <groupId>com.whaleson</groupId>
   <artifactId>spring-servlet</artifactId>
   <version>0.0.1-SNAPSHOT</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>demo</artifactId>
        <groupId>com.example</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.whaleson</groupId>
    <artifactId>spring-boot-servlet</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.whaleson</groupId>
            <artifactId>spring-servlet</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>
SpringBootServletApplication.java
@EnableAutoConfiguration
//@ServletComponentScan(basePackages = "com.whaleson.web")
public class SpringBootServletApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringBootServletApplication.class,args);

    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ServletRegistrationBean asyncServletRegistrationBean(){
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new AsyncServlet(),"/async-servlet");
        return  servletRegistrationBean;

    }

    @Bean
    public ServletContextInitializer servletContextInitializer(){
        return servletContext -> {
            CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();

            FilterRegistration.Dynamic  filterRegistration = servletContext.addFilter("filter",characterEncodingFilter);

            filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),false,"/");

        };
    }
}
  • @ServletComponentScan

该注解扫描的是spring-servlet模块里AsyncServlet.java所在的包

  • @Order(Ordered.HIGHEST_PRECEDENCE)

asyncServletRegistrationBean()方法中,该注解添加与否都可以。

  • 浏览器地址

http://localhost:8081/async-servlet

  • 浏览器结果

Hello

  • 控制台结果

当前线程的名称是[http-nio-8081-exec-4]内容是:Hey man.
当前线程的名称是[http-nio-8081-exec-3]内容是:timeout
当前线程的名称是[http-nio-8081-exec-3]内容是:complete

7-16 Spring Boot 嵌入式 Servlet 容器限制 原理分析

通过 RegistrationBean注册

  • ServletContextInitializer
    • RegistrationBean
      • ServletListenerRegistrationBean

        @WebListener

      • FilterRegistrationBean

        @WebFilter

      • ServletRegistrationBean

        @WebServlet

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

7-17 Spring Boot 应用传统 Servlet 容器部署

  • 不支持web.xml部署
  • 不支持ServletContainerInitialier接口
  • 注解驱动限制
Spring Boot Servlet注册
  • @Bean方式:RegistrationBean、Servlet组件
  • 注解方式:@ServletComponentScan
  • 编程方式:ServletContextInitializer

在这里插入图片描述

public class DefaultSpringBootServletInitializer extends SpringBootServletInitializer {

    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        builder.sources(SpringBootServletApplication.class);
        return builder;
    }
}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐