简介
任务
前后端分离场景下,Servlet的任务:
- 读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。
- 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。
- 发送显式的数据(即文档)到客户端(浏览器)。一般静态文件由前端提供,动态文件由后端提供
- 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。
Servlet的本质是智商就是在特定web服务器(大多是情况下是tomcat)下运行的特殊程序,用于提供泛型意义上的“服务”(这也是Servlet名字的含义)
Servlet生命周期
Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:
- Servlet 初始化后调用 init () 方法。
- Servlet 调用 service() 方法来处理客户端的请求。
- Servlet 销毁前调用 destroy() 方法。
- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
本文分析最常见的DispatchServlet及其OO体系下的Servlet行为,代码见org.springframework.web.servlet.DispatcherServlet的继承结构:
init() 方法
它被设计成只调用一次,用于初始化创建Servlet,默认创建于用户第一次调用对应于该 Servlet 的 URL 时,但也可以指定在服务器第一次启动时被加载。
当用户调用一个 Servlet 时,就会创建一个 Servlet 实例,每一个用户请求都会产生一个新的线程,适当的时候移交给 doGet 或 doPost 方法。
以本文例子中的调用层来看知道HttpServletBean开始,init函数才被填入内容:
@Overridepublic final void init() throws ServletException {// Set bean properties from init parameters.PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {...}// Let subclasses do whatever initialization they like.initServletBean();}
从代码中可以看出,此处init基于参数初始化Bean,所以抽象的Servlet的init含义是在其所处context下做好准备工作。
service() 方法
service() 方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。
每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。
HttpServlet抽象层就提供了service()的实现,它本质上是讲Servlet层面的请求与回复转写为Http层面,并转而根据属性调用doGet() 、doPost()等方法
destroy() 方法
destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。
在FrameworkServlet层对destory进行了实现:
@Overridepublic void destroy() {getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");// Only call close() on WebApplicationContext if locally managed...if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) {((ConfigurableApplicationContext) this.webApplicationContext).close();}}
实质上就是删除指定Servlet对应的Context(注意删除的是context而非bean),这似乎也意味着Servlet在Spring中的存在是context的形式。
destroy行为在tomcat层面实际上是基于配置的异步过程,一般不会在被销毁后立即被回收。
Servlet实例化&挂载
实现了HttpServlet的所有类被编译后都可以由Tomcat挂载执行,这也是tomcat作为java服务器的本质任务。
Servlet编写过滤器(Filter)
Servlet过滤器可以单个过成组地添加到Servlet上(以及已经过时的JSP)
可以使用注解方法配置Filter,普通的Filter实现如下:
@WebFilter(urlPatterns = "/*")@Slf4j@Component// 必要public class LearnFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.warn("filter init");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {log.warn("here goes the filter");// 接收请求前期filterChain.doFilter(request, response);// 必要// 接收请求后期,Interceptor所有的工作完乘}@Overridepublic void destroy() {log.warn("filter destroy");}}
关键在于@WebFilter注解对过滤器的功能范围进行了配置,同时可以看到,如果希望在Spring体系下工作,声明Filter的同时还应注册其为Bean。
另外对于大多数普通的请求,继承org.springframework.web.filter.OncePerRequestFilter,并实现其doFilterInternal方法是更好的选择,它提供了错误处理的选项,面向的也是http请求。
注意,无论在何处的过滤方法中,都应当调用filterChain.doFilter(request, response);,否则过滤链就会断裂,这可能会导致服务器无法正常响应http请求(因为有些Filter下,请求的实际执行也是封装在底层Filter之中的),同时可以在doFilter方法之后做后处理,一般自定义的Filter可以包裹在最外层,但要注意,此时response已经在doFilter的执行链中发送给客户端,所以在这一步是服务器的后处理步骤,而不能修改返回给客户端的任何内容。
Servlet对Cookie的支持
使用request.getCookies()即可取得cookie,虽然cookie是键值类型数据,但是似乎由于其相对不可信,Java没有提供根据关键词查询的接口,只是返回cookie列表并由服务端自行处理
可以使用response.addCookie()向客户端添加cookie,客户端便可以选择在之后的请求中使用对应的cookie,这给予了服务端验证和授权的机制,session的实现方式之一基于这种机制。
Servlet跟踪Session
session的实现方式主要有3种:基于cookie,基于SSL和基于url重写(基于隐式表单的session传送已经被弃用,因为不通用且过于tricky),本质都是在请求时附加一个唯一映射的sessionId,servlet可以对应识别这些标识,并在服务器创建对应的Session,实现状态化的http。
可以用HttpSession session = request.getSession(true);的形式获取Session,其中true的含义是在当前客户端无session的情况下创建session。
Session由cookie提供的sessionId唯一标识,它的主体也是基于KV的数据对,K是name,而V是对应的对象(而非字符串),所以可以存储多种类型的数据(但因为StandardSession实现了序列化,用实现序列化的对象作为V的类型是更好的选择)。HttpSession接口中也定义了session有maxInactiveInterval这一属性,它是session的过期时间(Java的默默认有效时间是30分钟),一旦超过这个时间域而没有访问,session就会被逻辑失效(invalidate)。为它设置非正值将使session永远有效。
