Servlet
Servlet 是一些遵从Java Servlet API的Java类,这些Java类可以响应请求。尽管Servlet可以响应任意类型的请求,但是它们使用最广泛的是响应web方面的请求。 Servlet必须部署在Java servlet容器才能使用。
Servlet 生命周期
Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:
- Servlet 初始化后调用 init () 方法。
- Servlet 调用 service() 方法来处理客户端的请求。
- Servlet 销毁前调用 destroy() 方法。
- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
ServletContext
当 Servlet 容器(比如 Apache Tomcat)启动后,会部署和加载所有 web 应用。当web 应用被加载,Servlet 容器会创建一次 ServletContext
,然后将其保存在服务器的内存中。web 应用的web.xml
被解析,找到其中所有 servlet
、filter
和 Listener
或 @WebServlet
、@WebFilter
和@WebListener
注解的内容,创建一次并保存到服务器的内存中。对于所有过滤器会立即调用init()
。当 Servlet 容器停止,将卸载所有 web 应用,调用所有初始化的 Servlet 和过滤器的destroy()
方法,最后回收 ServletContext
和所有 Servlet
、Filter 与 Listener
实例。
HttpServletRequest 与 HttpServletResponse
Servlet 容器附加在一个 web 服务上,这个 web 服务会在某个端口号上监听 HTTP 请求,在开发环境中这个端口通常为 8080,生产环境中通常为 80。当客户端(web 浏览器)发送了一个 HTTP 请求,Servlet 容器会创建新的 HttpServletRequest
和 HttpServletResponse
对象,传递给已创建好并且请求的 URL 匹配 url-pattern
的 Filter
和 Servlet
实例中的方法,所有工作都在同一个线程中处理。
request 对象可以访问所有该 HTTP 请求中的信息,例如 request header 和 request body。response 对象为你提供需要的控制和发送 HTTP 响应方法,例如设置 header 和 body(通常会带有 JSP 文件中的 HTML 内容)。提交并完成HTTP 响应后,将回收 request 和 response 对象。
HttpSession
当用户第一次访问该 web 应用时,会通过 request.getSession()
第一次获得 HttpSession。之后 Servlet 容器将会创建 HttpSession
,生成一个唯一的 ID(可以通过 session.getId()
获取)并储存在服务器内存中。然后 Servlet 容器在该次 HTTP 响应的 Set-Cookie
头部设置一个 Cookie
,以JSESSIONID
作为 Cookie 名字,那个唯一的 session ID 作为 Cookie
的值。
你可以在 web.xml
中设置 session-timeout
,默认值为 30 分钟。超时到达之前 HttpSession
会一直存活。所以当客户端不再访问该 web 应用超过 30 分钟后,Servlet 容器就会回收这个 session。后续每个请求,即使指定 cookie 名称也不能再访问到相同的 session。Servlet 容器会创建一个新的 Cookie
。
另一方面,客户端上的 session cookie 有一个默认存活时间,该事件和该浏览器实例运行时间一样长。所以,当客户端关闭该浏览器实例(所有标签和窗口)后,这个 session 就会被客户端回收。新浏览器实例不再发送与该 session 关联的 cookie。一个新的 request.getSession()
将会返回新的HttpSession
并设置一个拥有新 session
ID 的 cookie。
Servlet中有4个包装类ServletRequestWrapper/ServletResponseWrapper/HttpServletRequestWrapper/HttpServletResponseWrapper,可用来改变Servlet请求/响应的行为, 这些包装类遵循装饰者模式(Decorator).
异步处理
在Servlet模型中,每个请求都是由某个线程处理,然后,将响应写入IO流,发送给客户端。从开始处理请求,到写入响应完成,都是在同一个线程中处理的。
实现Servlet容器的时候,只要每处理一个请求,就创建一个新线程处理它,就能保证正确实现了Servlet线程模型。在实际产品中,例如Tomcat,总是通过线程池来处理请求,它仍然符合一个请求从头到尾都由某一个线程处理。
这种线程模型非常重要,因为Spring的JDBC事务是基于ThreadLocal实现的,如果在处理过程中,一会由线程A处理,一会又由线程B处理,那事务就全乱套了。此外,很多安全认证,也是基于ThreadLocal实现的,可以保证在处理请求的过程中,各个线程互不影响。
但是,如果一个请求处理的时间较长,例如几秒钟甚至更长,那么,这种基于线程池的同步模型很快就会把所有线程耗尽,导致服务器无法响应新的请求。如果把长时间处理的请求改为异步处理,那么线程池的利用率就会大大提高。Servlet从3.0规范开始添加了异步支持,允许对一个请求进行异步处理。
web.xml主要有几点不同:
- 不能再使用的DTD声明,必须用新的支持Servlet 3.1规范的XSD声明,照抄即可;
- 对DispatcherServlet的配置多了一个,默认值是false,必须明确写成true,这样Servlet容器才会支持async处理。
- 在Controller中编写async处理逻辑
- 返回一个Callable
- 返回一个DeferredResult对象
异步 Filter 在web.xml中添加并设置为true
务必注意普通Filter的不要匹配async请求路径。
Servlet
/Filter
默认会一直占用请求处理线程, 直到它完成任务.如果任务耗时长久, 且并发用户请求量大, Servlet容器将会遇到超出线程数的风险.
Servlet 3.0 中新增了一项特性, 用来处理异步操作. 当Servlet
/Filter
应用程序中有一个/多个长时间运行的任务时, 你可以选择将任务分配给一个新的线程, 从而将当前请求处理线程返回到线程池中,释放线程资源,准备为下一个请求服务.
异步Servlet/Filter
- 异步支持
@WebServlet
/@WebFilter
注解提供了新的asyncSupport
属性:
|
|
同样部署描述符中也添加了<async-supportted/>
标签:
|
|
支持异步处理的Servlet
/Filter
可以通过在ServletRequest
中调用startAsync()
方法来启动新线程:
- 只能将原始的
ServletRequest
/ServletResponse
或其包装器(Wrapper/Decorator)传递给第二个startAsync()
方法. - 重复调用
startAsync()
方法会返回相同的AsyncContext
实例, 如果在不支持异步处理的Servlet
/Filter
中调用, 会抛出java.lang.IllegalStateException
异常. AsyncContext
的start()
方法不会造成方法阻塞.
在异步Servlet
/Filter
中需要完成以下工作, 才能真正达到异步的目的:
- 调用
AsyncContext
的start()
方法, 传递一个执行长时间任务的Runnable
; - 任务完成时, 在
Runnable
内调用AsyncContext
的complete()
方法或dispatch()
方法
在实际使用时,经常用到的就是DeferredResult,因为返回DeferredResult时,可以设置超时、正常结果和错误结果,易于编写比较灵活的逻辑。
使用async异步处理响应时,要时刻牢记,在另一个异步线程中的事务和Controller方法中执行的事务不是同一个事务,在Controller中绑定的ThreadLocal信息也无法在异步线程中获取。
此外,Servlet 3.0规范添加的异步支持是针对同步模型打了一个“补丁”,虽然可以异步处理请求,但高并发异步请求时,它的处理效率并不高,因为这种异步模型并没有用到真正的“原生”异步。Java标准库提供了封装操作系统的异步IO包java.nio,是真正的多路复用IO模型,可以用少量线程支持大量并发。使用NIO编程复杂度比同步IO高很多,因此我们很少直接使用NIO。相反,大部分需要高性能异步IO的应用程序会选择Netty这样的框架,它基于NIO提供了更易于使用的API,方便开发异步应用程序。
@ServletComponentScan
在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册到嵌入式Servlet容器中,无需其他代码。
Servlet 3.0 注解
- javax.servlet.annotation.WebFilter
- javax.servlet.annotation.WebListener
- javax.servlet.annotation.WebServlet
Servlet3.0作为J2EE 6规范一部分,并随J2EE6一起发布,@WebListener是该注解用于将类声明为监听器,是Servlet3.0的新特性,不需要在web.xml进行配置,简化了配置。
|
|
- 如果不加@ServletComponentScan注解则会报404即找不到页面,控制台也扫描不到我们配置的servlet:/test,即无法被映射
- 如果Application类和Servlet类不在同一包下,则@ServletComponentScan需要添加相应的路径,如Application类在包com.hui.xiao下,则写为@ServletComponentScan(“com.hui.xiao”)或@ServletComponentScan(“com.hui”)
由于嵌入式容器不支持@ WebServlet,@ WebFilter和@WebListener注释,Spring Boot在很大程度上依赖于嵌入式容器,因此引入了新的注释@ServletComponentScan来支持一些使用这3个注释的从属jar。
@WebListener用法
该注解用于将类声明为监听器,被 @WebListener 标注的类必须实现以下至少一个接口:
- ServletContextListener
- ServletContextAttributeListener
- ServletRequestListener
- ServletRequestAttributeListener
- HttpSessionListener
- HttpSessionAttributeListener
|
|