标签:
Servlet规范
本文第一段是 copy 过来的,请各位看官谅解,顺序结构参照 Servlet 3.1 规范。
什么是 Servlet
Servlet 是基于 Java 技术的 web 组件,容器托管的,用于生成动态内容。像其他基于 Java 的组件技术一样,Servlet 也是基于平台无关的 Java 类格式,被编译为平台无关的字节码,可以被基于 Java 技术的 web server 动态加载并运行。容器,有时候也叫做 servlet 引擎,是 web server 为支持 servlet 功能扩展的部分。客户端通过 Servlet 容器实现的请求/应答模型与 Servlet 交互。
Servlet 容器是 web server 或 application server 的一部分,提供基于请求/响应发送模型的网络服务,解码基于 MIME 的请求,并且格式化基于MIME 的响应。Servlet 容器也包含了管理 Servlet 生命周期。
Servlet 容器可以嵌入到宿主的 web server 中,或者通过 Web Server 的本地扩展 API 单独作为附加组件安装。Servelt 容器也可能内嵌或安装到包含 web 功能的 application server 中。
容器抽象流程
```flow
start_start=>start: start
op_1=>operation: 容器初始化自己,生成一个Acceptor,多个Processor,并且初始化Servlet。
op_2=>operation: Acceptor线程循环接受请求,然后把请求包装成Socket之后交给Processor线程处理。
op_3=>operation: 由Processor线程解析Socket,包装好HttpServletRequest / HttpServletResponse对象。
op_4=>operation: 由Processor线程根据策略选择合适的Servlet,调用其service方法,传递Request / Response。
op_5=>operation: Servlet业务处理完毕通过Response写回响应,控制权交还容器,Processor继续处理下个请求。
end_end=>end: end
start_start->op_1->op_2->op_3->op_4->op_5->end_end
```
首先对于容器来说,它会先读取配置文件,并且初始化自己,然后他会不断的去读取约定好的 web.xml 并解析它,生成 Server。
然后核心是容器会初始化一个 Acceptor 线程,然后初始化多个 Processor 线程(类似一个线程池)。之后 Acceptor 线程会执行一个死循环不断的接受 Socket 请求,然后简单的对这个 Socket 包装之后,直接交给一个空闲的 Processor 线程来处理。Processor 线程就会去解析这个 Socket 并且构造核心的 HttpServletRequest / HttpServletResponse 对象,然后调用过滤器,然后去寻找合适的 Servlet 实例,调用其 service 方法,最后 service 方法执行完毕之后,请求流程结束,由容器清理并且归还 Processor 线程。
所以对于一个 Web 应用来说,调用其方法的线程(复数个)都是由容器来初始化的。
从上面的分析可以看出来,首先对于 Servlet 来说,它在容器中是一个单例的,线程不安全的,所有的请求都调用同一个 Servlet 的 service 方法。
然后 HttpServletRequest / HttpServletResponse 是线程安全的,因为它们是在一个 Processor 线程中被初始化的局部变量,而这个 Processor 线程正是调用 service 方法的线程,它们并未被其他线程共享。而根据以上分析,有的人会在 Filter 中把 HttpServletRequest / HttpServletResponse 放在一个 ThreadLocal 中保证线程安全,其实是完全没有必要的,因为执行 Filter 的线程和执行 service 的线程必然是同一个线程,不存在线程安全问题。
对于 ServletContext 和 ServletConfig 来说,他们不是线程安全的,所有的线程拿到的 ServletContext / ServletConfig 都是同一个实例,它们被多个线程共享了。
Servlet
Servlet 接口
可以看到,Servlet 接口一共定义了五个方法,其中与生命周期相关的方法有三个:
init(..),这个方法会在 Servlet 第一次被初始化的时候调用一次,对于容器来说 Servlet 是懒加载的,就是在第一次请求到来的时候加载 Servlet 并调用 init,而 ServletConfig 其实内涵的就是容器解析 web.xml 的结果。
service(..),这个方法会在每一个请求到来的时候由容器来调用,是 Servlet 服务的核心方法。
destroy(..),这个方法会在容器关闭的时候被调用。
HttpServlet 抽象类
对于此HttpServlet,它的 service 方法通过模板方法模式分派出了多种请求模式,比如常见的 get / post 等。
| ------- |详细|
|:-------:|:-|
|service | **核心方法**<br/>此方法接受请求,之后通过模板模式将请求的类型分派给不同的方法处理 |
|doGet | **GET请求处理方法**<br/> |
|doHead | **HEAD请求处理方法**<br/>只返回由GET请求产生的Header信息 |
|doPost | **POST请求处理方法**<br/>|
|doPut | **PUT请求处理方法**<br/> |
|doDelete | **DELETE请求处理方法**<br/>|
|doOptions| **OPTIONS请求处理方法**<br/>返回当前Servlet支持的请求方法 |
|doTrace | **TRACE请求处理方法**<br/>返回的响应包含TRACE请求的所有头信息 |
对于 HttpServlet 来说,它的方法都未实现这些具体的方法,仅实现了 service 方法进行分派,如果未实现这种请求方法,则服务器会返回 405 - METHOD NOT ALLOWED。
对于 doGet 方法,Servlet 可以选择性的支持一种缓存机制(可以在 service 方法中看到)。
可以看到,如果想要支持缓存机制,需要先实现 getLastModified 方法,这个方法默认是返回 -1 表示不支持缓存机制的,然后每次都会去调用 doGet 方法。如果实现了 getLastModified 方法之后,可以从请求的 Header 中获取到 HEADER_IFMODSINCE(If-Modified-Since,这个字段代表上一次客户端请求到这个资源的时间),然后比较二者,如果在客户端获取到这个资源之后此资源被修改了,则调用 doGet 方法,否则就认为此资源没有改动,返回给客户端 302 - NOT MODIFIED。(注意此处有个时间反转)
请求
|---|详细|
|:--:|:-|
|getParameter<br/>getParameterNames<br/>getParameterValues<br/>getParameterMap | **获取参数** |
|getPart<br/>getParts | **获取上传的参数** |
|getAttribute<br/>getAttributeNames<br/>setAttribute | **设置/获取属性**<br/>可以设置此请求的属性,然后通过RequestDispatcher转发此请求到另一个Servlet,在下一个Servlet获取属性 |
|getHeader<br/>getHeaders<br/>getHeaderNames | **获取头信息** |
|getContextPath | **Web 应用的根路径**<br/>以/开头,不以/结束,如果应用打包为ROOT,则它为空字符串 |
|getServletPath | **当前 Servlet 的完整匹配路径**<br/>以/开头,不以/结束<br/> URI:/catalog/garden/implements/ -> PATTERN:/garden/* -> ServletPath:/garden<br/>URI:/catalog/garden/123.jsp -> PATTERN:*.jsp -> ServletPath:/garden/123.jsp |
|getPathInfo | 请求路径剩下的部分,总满足 请求URI = ContextPath + ServletPath + PathInfo |
|ServletContext.getRealPath| **当前应用的上下文根绝对路径**<br/>需要传入一个字符串参数,如果传递 / 则返回根 |
|getRequestDispatcher(..).forward(..)| **服务器端跳转** |
如果提供的参数满足一些条件,则可以通过上述的接口直接得到参数,如果不满足这些条件,那么这些方法的调用会返回 null,如果想要获取参数,则需要通过 request.getInputStream(..) 来获取 Body 体的输入流,然后自己进行解析。
ServletContext
ServletContext 是一个 Web 应用在一个容器中的视图,一个 ServletContext 下面会有多个 Servlet / Filter。
对于每个请求来说,尝试获取 ServletContext 返回的都是同一个 ServletContext,所以 ServletContext 不是线程安全的。
对于 ServletContext 来说,它的 Path 是当前应用的根,所有与根关联的请求都会被容器路由到这个应用。
|-|详细|
|:--:|:-|
|setAttribute<br/>getAttribute<br/>getAttributeNames<br/>removeAttribute| **设置/获取属性**|
响应
|-|详细|
|:--:|:-|
|setHeader<br/>addHeader<br/>getHeader| **设置头信息** |
|getStatus<br/>setStatus| **设置返回状态**<br/>当请求有错误发生时使用此接口,如果没有错误发生,不用设置<br/>HttpServletResponse有内嵌的错误码 |
|sendRedirect| **进行客户端跳转** |
过滤器
过滤器是允许动态改变负载以及到资源的请求和来自资源的响应中的头信息的Java组件。
容器保证当请求到来之前,所有过滤器都被实例化并且调用一次其 init 方法,然后再容器关闭前,调用一次其 destroy 方法。
当请求到来之前,会先调用其第一个滤器,然后再过滤器内部由开发者通过 FilterChain 决定是否调用下一个 Filter,如果不调用,开发者需要保证写回合适的 Response,当所有过滤器都调用完毕时,会调用 service 方法。
过滤器可以通过 servlet-name 来指定需要过滤的 Servlet,或者通过 url-pattern 来指定要过滤的请求。
这里可以分析一下 Filter 的运行原理(基于 Tomcat)。
首先 Filter 的第三个参数,chain 实际上是一个 ApplicationFilterChain 的实例,每次我们都通过调用 chain.doFilter(..) 来触发下一次调用,所以分析一下 doFilter(..)。
首先这段代码最核心的就最后一句 internalDoFilter,直接被代理给了这个方法,看它。
这个函数分两段,先分析上面这段,首先注释就是 call the filter if there is one,就是说如果还有过滤器,就继续调用,那么 pos 和 n 是什么,很显然看到注释,pos 是当前调用到的 filter 的次序,n 是当前一共有多少个 filter,如果 pos < n 当然还有 filter 没有被调用,继续。
首先就拿到了这个 filter,调用了这个 filter 的 doFilter 方法,然后观察第三个参数传递的 this,这个 this 就是我们 doFilter 的 chain,如果对 chain 在调用 doFilter,就又回到了上面的方法。
对于 Filter 来说,使用的就是这种递归的机制来完成 chain 式的调用。而这种调用必须有一个起点,这个起点其实是 WsFilter 进行的调用。
在继续分析,每一个 Filter 如果得以调用,那么最后辗转回来都会进入到上面这个 if 语句,唯有一个除外,那就是最后一个 Filter。当不是最后一个 Filter 的时候,经过这种链式的调用,Filter 会在上面这个 if 里面返回,如果是对最后一个 Filter 调用了 chain.doFilter(..),那么它将会跳过上面去走下面的代码,分析下面的代码。
继续分析后半部分,如果 Filter 调用完毕了,代码就会往下走到这里,这里就是调用 Servlet 的地方,核心语句 servlet.service(r, r)。
剩下的就不分析了,给出时序图。
```sequence
WsFilter->ApplicationFilterChain: 1.ApplicationFilterChain.doFilter
ApplicationFilterChain->Filter1: 2.Filter.doFilter
Filter1->Filter1: Before Ft 1
Filter1-->ApplicationFilterChain: 3.chain.doFilter
ApplicationFilterChain->Filter2: 2.Filter.doFilter
Filter2->Filter2: Before Ft 2
Filter2-->ApplicationFilterChain: 3.chain.doFilter
ApplicationFilterChain->Servlet: servlet.service(no more filter)
Servlet->Servlet: biz
Servlet-->ApplicationFilterChain: return
ApplicationFilterChain-->Filter2: __return__
Filter2->Filter2: After Ft 2
Filter2-->Filter1: return
Filter1->Filter1: After Ft 1
Filter1-->ApplicationFilterChain: return
```
会话
Http协议是一种无状态协议,所以规范定义了 HttpSession,允许服务器通过多种不同的方案来跟踪会话。
|-|详细|
|:--:|:-|
|Cookies| 当容器向客户端发送一个Cookie,客户端的后续请求都会附带这个Cookie到服务器,明确的指定会话关联,用以关联会话的Cookie名字默认为JSESSIONID, |
|URL重写 | 当客户端不支持Cookie时,服务器可以使用URL重写代替 http://www.myserver.com/catalog/index.html;jsessionid=1234 |
生命周期
|-|详细|
|:--:|:-|
|ServletContextListener | **ServletContext创建/摧毁** |
|ServletContextAttributeListener| **在Servlet上下文的属性已添加、删除、或替换** |
|HttpSessionListener | **会话已创建、无效或超时** |
|HttpSessionAttributeListener | **在HttpSession上添加、移除、或替换属性** |
|HttpSessionActivationListener | **HttpSession已被激活或钝化** |
|HttpSessionBindingListener | **对象已经从HttpSession绑定或解除绑定** |
|ServletRequestListener | **一个servlet请求已经开始由Web组件处理** |
|ServletRequestAttributeListener| **在ServletRequest上添加、移除、或替换属性** |
|AsyncListener | **超时、连接终止或完成异步处理** |
路径匹配
1. 容器将尝试找到一个请求路径到servlet路径的精确匹配。成功匹配则选择该servlet。
2. 容器将递归地尝试匹配最长路径前缀。这是通过一次一个目录的遍历路径树完成的,使用‘/’字符作为路径分隔符。最长匹配确定选择的servlet。
3. 如果URL最后一部分包含一个扩展名(如 .jsp),servlet容器将试图匹配一个处理此扩展名请求的Servlet。扩展名定义在最后一部分的最后一个‘.’字符之后。
4. 如果前三个规则都没有产生一个servlet匹配,容器将试图为请求资源提供相关的内容。如果应用中定义了一个“默认的”servlet,它将被使用。许多容器提供了一个隐式的默认servlet用于提供内容。
注解配置
|-|详细|
|:--:|:-|
|WebServlet | **标记此类作为一个Servlet**<br/>此类必须实现Servlet接口,value为url-pattern,内附参数init-param指定参数 |
|WebListener | **标记此类作为一个Listener**<br/>此类必须实现Servlet Listener多种接口中的其中之一 |
|WebFilter | **标记此类作为一个Filter**<br/>此类必须实现Filter接口 |
|MultipartConfig| **标记此类的上传相关参数** |
Other - 01 - Servlet学习笔记 - 概览
标签:
原文地址:http://www.cnblogs.com/yaowu/p/8922458c59d600786c9427c66cf13645.html