Spring Boot 2.0深度实践之核心技术篇【第七节 渐行渐远的 Servlet】
7-1 渐行渐远的ServletSpring5开始支持WebFlux,把以前Java Web应用开发从Servlet必 选变成可选,一个是Servlet Web,另外一个是React Web(WebFlux)技术回顾传统Servlet部署通过web.xml传统Servlet组件Servlet API本身FilterListener:ServletContextListenerHandlerMeth
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
介于CGI
(Common Gateway Interface
)与服务扩展(如:Netscape Server API
或Apache
模块)之间。
在体系上,Servlet
技术(或者规范)属于Java EE
技术(规范)的一部分。不过Servle
t 并非一开始就隶属于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 月 | Java 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 应用 |
7-2 Servlet 核心 API
核心组件 | API 说明 | 起始版本 | Spring Framework 代表实现 |
---|---|---|---|
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 |
7-4 Servlet 注册
注册方式 | 传统方式 | 注解方式 | 编程方式 |
---|---|---|---|
Servlet 注册 | web.xml 部署 <servlet> + <servlet-mapping> | @WebServlet | ServletContext#addServlet |
Filter 注册 | 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/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
初始化过程
理解 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;
}
}
更多推荐
所有评论(0)