不安分的黑娃
踏踏实实,坚持学习,慢慢就懂了~

Servlet 3.1学习笔记

目录

参考文档

什么是 Servlet ?

Servlet 是基于 Java 平台的 Web 组件,由一个容器管理,能够生成动态内容.

什么是 Servlet 容器?

Servlet 容器是提供网络服务的 Web Server 或 应用服务器的一部分.
Servlet 容器包含和管理 servlets.
所有 Servlet 容器必须支持 HTTP 协议.可能支持 HTTPS.必须实现 HTTP/1.0 and HTTP/1.1 标准.
Servlet 容器必须基于 Java SE 7及以上版本构建.

Tomcat 就是一个 Servlet 容器.

客户端发起请求和容器处理并返回的过程

  1. 客户端发起一个 HTTP 请求
  2. Web 服务器收到请求,转发给 Servlet 容器.
  3. Servlet 容器根据 Servlet 的配置决定调用哪个Servlet ,通过 request 和 response 对象 请求这个Servlet.
  4. Servlet 使用 Request对象发现,远程用户是谁,HTTP POST 传了什么参数和其它有关的参数.Servlet 执行业务逻辑通过 response 对象返回给 客户端.
  5. 一旦 Servlet 完成处理请求, Servlet 容器确保响应正确flush,将控制权返回给 Web 服务器.

和其它技术比较

  • 比 CGI 脚本快的多
  • 使用很多 Web Server 支持的标准API
  • 拥有 Java 语言自身的各种优势
  • 能够使用 Java 平台的很多API

JDK 版本要求

JDK 1.7 +

Servlet 接口

Servlet 接口是 Java Servlet API 的核心抽象. Java Servlet API 提供了 2 个实现类: GenericServletHttpServlet.
通常情况,继承 HttpServlet 实现自己的 Servlet.

Java Servlet API 中 Servlet 接口的源码:

点击查看代码

public interface Servlet {

    /**
     * Called by the servlet container to indicate to a servlet that the servlet
     * is being placed into service.
     *
     * <p>
     * The servlet container calls the <code>init</code> method exactly once
     * after instantiating the servlet. The <code>init</code> method must
     * complete successfully before the servlet can receive any requests.
     *
     * <p>
     * The servlet container cannot place the servlet into service if the
     * <code>init</code> method
     * <ol>
     * <li>Throws a <code>ServletException</code>
     * <li>Does not return within a time period defined by the Web server
     * </ol>
     *
     *
     * @param config
     *            a <code>ServletConfig</code> object containing the servlet's
     *            configuration and initialization parameters
     *
     * @exception ServletException
     *                if an exception has occurred that interferes with the
     *                servlet's normal operation
     *
     * @see UnavailableException
     * @see #getServletConfig
     */
    public void init(ServletConfig config) throws ServletException;

    /**
     *
     * Returns a {@link ServletConfig} object, which contains initialization and
     * startup parameters for this servlet. The <code>ServletConfig</code>
     * object returned is the one passed to the <code>init</code> method.
     *
     * <p>
     * Implementations of this interface are responsible for storing the
     * <code>ServletConfig</code> object so that this method can return it. The
     * {@link GenericServlet} class, which implements this interface, already
     * does this.
     *
     * @return the <code>ServletConfig</code> object that initializes this
     *         servlet
     *
     * @see #init
     */
    public ServletConfig getServletConfig();

    /**
     * Called by the servlet container to allow the servlet to respond to a
     * request.
     *
     * <p>
     * This method is only called after the servlet's <code>init()</code> method
     * has completed successfully.
     *
     * <p>
     * The status code of the response always should be set for a servlet that
     * throws or sends an error.
     *
     *
     * <p>
     * Servlets typically run inside multithreaded servlet containers that can
     * handle multiple requests concurrently. Developers must be aware to
     * synchronize access to any shared resources such as files, network
     * connections, and as well as the servlet's class and instance variables.
     * More information on multithreaded programming in Java is available in <a
     * href
     * ="http://java.sun.com/Series/Tutorial/java/threads/multithreaded.html">
     * the Java tutorial on multi-threaded programming</a>.
     *
     *
     * @param req
     *            the <code>ServletRequest</code> object that contains the
     *            client's request
     *
     * @param res
     *            the <code>ServletResponse</code> object that contains the
     *            servlet's response
     *
     * @exception ServletException
     *                if an exception occurs that interferes with the servlet's
     *                normal operation
     *
     * @exception IOException
     *                if an input or output exception occurs
     */
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    /**
     * Returns information about the servlet, such as author, version, and
     * copyright.
     *
     * <p>
     * The string that this method returns should be plain text and not markup
     * of any kind (such as HTML, XML, etc.).
     *
     * @return a <code>String</code> containing servlet information
     */
    public String getServletInfo();

    /**
     * Called by the servlet container to indicate to a servlet that the servlet
     * is being taken out of service. This method is only called once all
     * threads within the servlet's <code>service</code> method have exited or
     * after a timeout period has passed. After the servlet container calls this
     * method, it will not call the <code>service</code> method again on this
     * servlet.
     *
     * <p>
     * This method gives the servlet an opportunity to clean up any resources
     * that are being held (for example, memory, file handles, threads) and make
     * sure that any persistent state is synchronized with the servlet's current
     * state in memory.
     */
    public void destroy();
}

处理请求方法

Servlet 接口的 service 方法处理请求.

每个 Servlet 的实例个数

每个Servlet 在同一个 JVM 只有1个实例.

Servlet 生命周期

生命周期: 加载和实例化,初始化,处理来自客户端的请求 对应API 中的 init ,service 和 destroy 方法.

加载和实例化

Servlet 容器负责 Servlet 的加载和实例化. 加载和实例化发生于 容器启动,或者当调用 Servlet 处理请求时.

初始化

在Servlet 对象实例化后,容器必须在处理请求前进行初始化.
初始化是为了能够读取持久化配置数据或初始化消耗资源(比如JDBC).
容器调用 Servlet#init(ServletConfig config) 方法初始化 Servlet, ServletConfig 是每个 Servlet 都有1个.ServletConfig 允许 Servlet 访问从 Web 应用的配置信息来的初始化参数.

ServletConfig 源码如下

点击查看代码
public interface ServletConfig {
    public String getServletName();
	// 可以拿到 servlet 运行环境
    public ServletContext getServletContext();
    public String getInitParameter(String name);
    public Enumeration<String> getInitParameterNames();
}

ServletConfig 提供了能够访问 ServletContext ( ServletContext 描述了 Servlet 运行环境 )
  1. 初始化错误条件
    在初始化过程中, Servlet 实例可以抛出 UnavailableException 或 ServletException,这时 Servlet 必须被容器释放掉. 并且 destroy 方法不会被调用.

  2. 注意事项
    必须确保 init 方法被调用后才能允许使用 Servlet.

处理请求

在 Servlet 被初始化完成后,容器可能使用它去处理客户端请求.
使用 ServletRequest 对象来代表请求,ServletResponse 代表响应.
特定的,HTTP 请求使用 HttpServletRequest来代表,HTTP 响应使用 HttpServletResponse 代表.

  1. 多线程
    Servlet 容器肯能发送并发请求.为了处理这样的请求,必须 在 service 方法中进行多线程处理.

强烈建议 不要在在 service 方法加 synchronized 关键字,因为这样会影响性能.

  1. 处理请求过程中的异常
    在处理请求的过程中,可能会抛出 ServletException 或 UnavailableException.
    ServletException 表明 一些错误,容器适当的采取合理措施清除请求.
    UnavailableException 表明 sevlet 暂时或永久性不能处理请求.

异步处理请求

异步处理过程:

  1. 容器收到请求,经过认证等普通过滤器后传给 Servlet
  2. Servlet 处理请求参数和内容判断请求的特征
  3. Servlet 处理请求(比如调用外部web服务,等待返回结果)
  4. Servlet 返回时没有生成响应
  5. 过一段时间,请求的资源可用时(调用外部web服务完成,收到响应),线程继续在相同线程中继续事件,要么使用 AsyncContext 分发给容器的另一个资源.

内容很多.....回头再说.

Request

Request 对象封装了 客户端请求的所有信息.在HTTP协议中,该信息通过HTTP头和请求的消息体从客户端传输到服务器。

HTTP 协议参数

参数是 name-value 对儿;通过ServletRequest 接口的以下方法可以访问 参数:

  • public String getParameter(String name);
  • public Enumeration getParameterNames();
  • public String[] getParameterValues(String name);
  • public Map<String, String[]> getParameterMap();

来自 Query string 和 post body 的数据会被聚集放到请求 parameter 集合中.例如:

if a request is made with a query string of a=hello and a post body of a=goodbye&a=
world , the resulting parameter set would be ordered a=(hello, goodbye, world) .

Path parameters 是GET请求的一部分,必须从 getRequestURI 或 getPathInfo方法返回值中进行解析.

参数什么时候可用?

满足以下条件,post 表单数据才能解析到 parameter集合中

  1. 请求是 HTTP 请求或 HTTPS 请求
  2. HTTP 方法是 POST
  3. content-type 是application/x-www-form-urlencoded
    4.Servlet 已经调用 getParameter 方法

如果条件没满足,那么表单数据仍存在 request 的InputStream 中,从 InputStream读取即可.

文件上传

Servlet 容器允许上传文件,当数据作为 multipart/form-data 发送时.

Servlet 容器提供 multipart/form-data 处理,如果下面条件任满足1个:

  • Servlet 标注了@MultipartConfig注解
  • web.xml 配置了 multipart-config 元素

如果Servlet 容器提供 multipart/form-data 处理,那么通过HttpServletRequest 接口的以下两个方法读取数据:

  • public Collection getParts()

  • public Part getPart(String name)

    每一个 Part 都能访问 headers ,content type 以及 通过 Part.getInputStream 读取它的内容.

    对于作为Content-Disposition的 form-data 的parts,但是没有文件名, part 的字符串值将通过 HttpServletRequest 接口的getParameter方法 和 getParameterValues 方法可用.

For parts with form-data as the Content-Disposition , but without a filename,
the string value of the part will also be available through the getParameter and
getParameterValues methods on HttpServletRequest , using the name of the
part.

Attributes

Attributes 是跟请求相关联的对象,一般当 API 不能满足表达信息时使用或者 Servlet 之间通信使用(RequestDispatcher).
通过 ServletRequest 接口的以下方法访问 Attributes:

  • getAttribute
  • getAttributeNames
  • setAttribute

以 java.和 javax. 前缀命名 Attribute name 是本标准预留的属性名.相似的 sun.,com.sun.,oracle和 com.oracle 都是预留.

Headers

Servlet 可以通过 HttpServletRequest 接口以下方法,访问 HTTP 请求头信息:

  • getHeader
  • getHeaders
  • getHeaderNames

Request Path Elements

请求路径由多个重要场景组成.请求 URI path 包含以下元素:

  • Context path :上下文路径.与servlet所属的ServletContext关联的路径前缀.如果是 default context 那么返回空字符串,如果不是,则返回以 '/'开头但不以'/'结尾的字符串.
  • Servlet Path : Servlet 匹配路径
  • PathInfo: 资源路径;可为 null 或以 '/'结尾的字符串

对应 HttpServletRequest 接口的方法:

  • getContextPath
  • getServletPath
  • getPathInfo

URI 和 path 关系:
requestURI = contextPath + servletPath + pathInfo

Context 配置示例:

Context Path /catalog
Servlet Mapping Pattern: /lawn/*
Servlet: LawnServlet
Servlet Mapping Pattern: /garden/*
Servlet: GardenServlet
Servlet Mapping Pattern: *.jsp
Servlet: JSPServlet

观察各 Path 元素的行为:

Request Path Path Elements
/catalog/lawn/index.html ContextPath: /catalog
ServletPath: /lawn
PathInfo: /index.html
/catalog/garden/implements/ ContextPath: /catalog
ServletPath: /garden
PathInfo: /implements/
/catalog/help/feedback.jsp ContextPath: /catalog
ServletPath: /help/feedback.jsp
PathInfo: null

Path Translation 方法

API 提供了以下2种方法,用于将本地路径转为特定path:

  • ServletContext.getRealPath
  • HttpServletRequest.getPathTranslated

Non Blocking IO

非阻塞IO只能在 async 请求处理才生效.

ReadListener 为非阻塞IO提供了以下回调方法:

  • onDataAvailable()
  • onAllDataRead()
  • onError(Throwable t)

Servlet 容器必须通过线程安全的方式访问 ReadListener 的方法.

ServletInputStream 类增加了以下方法:

  • boolean isFinished(): 当已经读取了所有数据,则返回 true
  • boolean isReady().如果数据可以不阻塞读取则返回true
  • void setReadListener(ReadListener listener) :注册一个 ReadListener 则会开启非阻塞IO.

Cookies

HttpServletRequest 接口通过 getCookies 方法去包含一个 cookies 数组.这些Cookies 是从客户端发过来的数据.

cookies 可以设置为 HttpOnly,可以减少跨站点脚本攻击.

SSL Attributes

Web 容器必须暴露以下属性给Servlet 编程者:

Attribute Attribute Name Java Type
cipher suite javax.servlet.request.cipher_suite String
bit size of the algorithm javax.servlet.request.key_size Integer
SSL session id javax.servlet.request.ssl_session_id String

Internationalization 国际化

客户端通过 Accept-Language 头告诉服务器响应使用的语言.
ServletRequest 接口的提供以下方法判断 发送者更喜欢的locale:

  • getLocale
  • getLocales

请求数据编码

默认使用 ISO-8859-1 编码解析POST数据.
ServletRequest 接口提供 setCharacterEncoding(String enc) 方法设置编码.

Request 对象的一生

每个 请求对象只在 Servlet#service 方法或 Filter#doFilter 方法中有效.如果是异步处理,则请求对象在 AsyncContext的 complete 方法被调用之前是有效的.

Servlet Context

ServletContext 接口定义了一个 web 应用的一个Servlet 视图.
容器提供者负责ServletContext接口的实现.

ServletContext 范围

部署在容器中的每个 Web应用都关联一个 ServletContext 实例.

在分布式容器中,默认 ServletContext 非分布,必须只能存在一个 JVM 中.

初始化参数

通过 ServletContext 接口以下方法,访问由web.xml定义的 Web应用上下文初始化参数:

  • getInitParameter
  • getInitParameterNames

配置方法

从 Servlet 3.0 开始,ServletContext 新增以下方法,开启编程式定义 Servlets, Filters ,url 样式:

  • ServletContextListener#contexInitialized() 2.3新增
  • ServletContainerInitializer#onStartup() 3.0新增
    另外,还可以查找一个 Registration 实例。

如果 ServletContextListener 既没有在 web.xml or web-fragment.xml中声明,也没有标注 @WebListener,当ServletContext传给 ServletContextListener#contexInitialized() 时 必须抛出 UnsupportedOperationException。

编程式新增和配置 Servlet

ServletContext 提供了 3个重载方法和1个创建 Servlet方法 ,用于添加、创建 Servlet:

  • public ServletRegistration.Dynamic addServlet(String servletName, String className);
  • public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet);
  • public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass);
  • public T createServlet(Class c) throws ServletException;

编程式新增和配置 Filter

  • public FilterRegistration.Dynamic addFilter(String filterName, String className);
  • public FilterRegistration.Dynamic addFilter(String filterName, Filter filter);
  • public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass);

编程式新增和配置 Listener

API 提供的 Listener 有以下几种:

  • javax.servlet.ServletContextAttributeListener
  • javax.servlet.ServletRequestListener
  • javax.servlet.ServletRequestAttributeListener
  • javax.servlet.http.HttpSessionListener
  • javax.servlet.http.HttpSessionAttributeListener
  • javax.servlet.http.HttpSessionIdListener

编程式新增 Listener 方法有:

  1. void addListener(String className)
  2. void addListener(T t)
  3. void addListener(Class <? extends EventListener> listenerClass)

但是注意:如果是通过 ServletContainerInitializer#onStartup(Set<Class<?>> c, ServletContext ctx) 添加的监听器有可能是实现ServletContextListener接口.

Listener 调用顺序和它们的声明顺序相对应.声明越晚,调用就越靠后.

  1. void createListener(Class clazz)
    clazz 必须是上面说明的几个接口实现类.此方法必须支持这些 Listener 接口上的所有规范定义的注解.
    本方法是返回一个 Listener实例,然后需要调用 addListener 方法注册到ServletContext中.Listener 的实现类必须有无参构造器,用于实例化.

编程式添加 Servlet Filter 和 Listener 的注解处理要求
编程式添加 Servlet时,Servlet上的注解:(@ServletSecurity, @RunAs, @DeclareRoles, @MultipartConfig)的元数据必须全部被应用.除非,调用 ServletRegistration.Dynamic / ServletRegistration 的 API 进行覆盖.
Filter 和 Listener 则不需要这些注解.

Context Attributes

Servlet 可以通过name绑定一个对象属性到 context. 任何绑定到上下文的属性,同一个 Web 应用的其他 Servlet 也可以使用.

ServletContext 接口提供以下方法:

  • setAttribute
  • getAttribute
  • getAttributeName
  • removeAttribute

在分布式容器中, 由于 Context attributes 位于JVM中, 这阻止了 ServletContext属性成为分布式容器中的共享内存存储。所以,如果需要在分布式环境的Servlet 间共享数据时,那么可以将数据放到 Session ,存储到数据库,或者放到 EJB 组件中.

resources

ServletContext 接口提供了以下方法直接访问静态文档,包括 HTML,JPEG 文件等:

  • getResource
  • getResourceAsStream

这两个方法的参数path,是相对于 context 的根或者相对于Web 应用下 WEB-INF/lib目录下 jar包中的 META_INF/resources 目录.
这两个方法先查找 web 应用上下文根路径下的资源,然后再扫描 WEB-INF/lib目录下的jar包.

这些方法不包含动态内容.比如,getResource("/index.jsp") 将返回源码.

可以通过 getResourcePaths(String path) 方法 访问 Web 应用的 资源完整清单.举个例子, for a web application containing 以下资源:

/welcome.html
/catalog/index.html
/catalog/products.html
/catalog/offers/books.html
/catalog/offers/music.html
/customer/login.jsp
/WEB-INF/web.xml
/WEB-INF/classes/com.acme.OrderServlet.class,

那么 getResourcePaths("/") 返回 {"/welcome.html", "/catalog/", "/customer/", "/WEB-INF/"}, getResourcePaths("/catalog/") 返回 {"/catalog/index.html", "/catalog/products.html", "/catalog/offers/"}.

多主机和 Servlet Contexts

Web 服务器可能支持多个逻辑主机共享1个IP地址.这种能力又称为"虚拟主机". 每个虚拟主机都有自己的 Servlet Context,且不能共享.
ServletContext 接口的 getVirtualServerName 方法获取所部署的主机名.

Reloading 注意事项

Servlet 和它们使用的类都必须在同一个 class loader 范围中.

Although a Container Provider implementation of a class reloading scheme for ease
of development is not required, any such implementation must ensure that all
servlets, and classes that they may use 2 , are loaded in the scope of a single class
loader. This requirement is needed to guarantee that the application will behave as
expected by the Developer. As a development aid, the full semantics of notification
to session binding listeners should be supported by containers for use in the
monitoring of session termination upon class reloading.
Previous generations of containers created new class loaders to load a servlet,
distinct from class loaders used to load other servlets or classes used in the servlet
context. This could cause object references within a servlet context to point at
unexpected classes or objects, and cause unexpected behavior. The requirement is
needed to prevent problems caused by demand generation of new class loaders.

临时工作目录

每个 Servlet Context 都要有1个临时存储目录.容器必须给每个 Servlet Context 提供私有临时目录,并可以 通过 javax.servlet.context.tempdir 属性拿到. 这个属性关联的对象必须是 java.io.File 类型.

容器必须保证一个 Servlet Context 的临时目录的内容对其他 Servlet Context 不可见.

Response

Response 对象封装了服务器返回给客户端响应的所有信息.

Buffering

Servlet 容器允许缓冲返回给client 的输出.
ServletResponse 接口的以下方法,可以设置 Buffering 信息:

  • getBufferSize(获取缓冲大小,如果没用缓冲,则返回数字 0)
  • setBufferSize(设置缓冲大小)
  • isCommitted(是否有任何字节返回给客户端)
  • reset(清除缓冲区数据)
  • resetBuffer
  • flushBuffer

在使用缓冲的时候,容器必须立马将填满的缓冲区内容刷到客户端.

Headers

通过 HttpServletResponse 接口的以下方法设置 HTTP 响应头:

  • setHeader
  • addHeader
  • setIntHeader
  • setDateHeader
  • addIntHeader
  • addDateHader

Header 必须在响应提交前设置.

建议,使用 X-Powered-By HTTP 响应头可以发布一些实现信息:

X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source
Edition 4.0 Java/Oracle Corporation/1.7)

Non Blocking IO

非阻塞IO仅在异步请求处理中生效.
WriteListener提供了以下回调方法:

  • void onWritePossible() 当 WriteListener 注册到 ServletOutputStream时候,容器在第一次能够写数据时调用这个方法.且仅当 ServletOutputStream#isReady() 返回false时,容器才会调用此方法.

  • onError(Throwable t)

ServletOutputStream 提供以下方法:

  • boolean isReady().当返回 true,则可以在 ServletOutputStream 执行写操作;如果没有数据要写入到 ServletOutputStream,然后这个方法就返回 false,一直到数据都被刷走;这时,容器将会调用 WriteListener#onWritePossible() 方法,随后这个方法返回 true.

  • void setWriteListener(WriteListener listener) .

Servlet 容器必须在线程安全的方式访问 WriteListener 里的方法.

快捷方法

  1. HttpServletResponse#sendRedirect
    sendRedirect 方法将设置适当的 headers 和 内容体然后重定向 客户端到另一个不同的 URL. 参数可以是相对 URL path.
  2. HttpServletResponse#sendError
    sendError 方法返回错误信息给客户端.

国际化

Servlet 应该设置地区和响应的字符编码.
通过 ServletResponse.setLocale 方法设置地区.
setCharacterEncoding , setContentType,setLocale 都可以改变字符编码.
使用 setContentType 设置字符编码时, 提供的字符串必须包含 'charset'属性才行.
当 setCharacterEncoding 和 setContentType 都没有设置字符编码时,才使用 setLocale设置编码.

默认编码为 ISO-8859-1.

地区通过 Content-Language 头告诉客户端; 字符编码通过 Content-Type 头告诉客户端.

关闭 Response 对象

当 响应关闭时,容器必须立刻将 response Buffering 中的所有剩余内容刷到客户端.

Response 对象声明周期

每个 response 对象只能在以下范围中生效(除了 异步请求):

  1. Servlet 的 service 方法中
  2. Filter 的 doFilter 方法
    如果请求对象开启了异步处理,那么在AsyncContext#startAsync 方法调用之前, 请求对象一直保留.

Filtering

过滤器是Java组件,它允许将请求中的有效负载和报头信息动态地转换为资源和来自资源的响应

什么是 filter ?

filter 是一段可重复使用的代码,能够转换 HTTP 请求,响应内容和头信息.Filters 通常不创建响应,只是修改信息.

一些过滤器组件例子:

  • Authentication filters 认证过滤器
  • Logging and auditing filters 日志和审计过滤器
  • Image conversion filters
  • Data compression filters 数据压缩
  • Encryption filters
  • Tokenizing filters
  • Filters that trigger resource access events
  • XSL/T filters that transform XML content
  • MIME-type chain filters
  • Caching filters

核心概念

实现 javax.servlet.Filter 接口,并提供公共无参构造器.
在 web.xml 中,通过<filter> 标签声明一个 Filter;<filter-mapping>标签用于定义怎么调用 filter.可以映射一个Filter到具体的 Servlet 名字或者映射 Filter 到一个 URL pattern.

Filter 生命周期

在 Web应用部署后,请求访问前,容器必须定位到应用到Web 资源的 filters 清单.容器必须保证清单里的 Filter 实例化并且调用了 init(FilterConfig config) 方法.

每个 <filter> 标签声明的在其容器的JVM中只能有1个实例.容器提供了 FilterConfig,用于引用 ServletContext和初始化参数集合.

当容器收到进来的请求时,它拿到filter清单中的第一个filter实例,然后调用 doFilter(ServletRequest request,ServletResponse response, FilterChain chain) 方法.

Filter 的 doFilter 方法典型实现样式:

  1. 检查 Request headers
  2. 包装 请求
  3. 包装 响应
  4. Filter 可能调用 Filter chain 中的下一个实体.
  5. 当 下一个 FIlter 调用完成后,检查响应 header
  6. filter 可能抛出异常,指明错误信息
  7. 当最后一个 filter调用完成后,下一个 访问的实体就是目标 Servlet 或 资源.
  8. 在 filter 实体被移除之前,必须调用它的 destroy 方法释放资源.

包装 请求和响应

容器必须保证,传递给下一个 filter 的 Request对象和 response 对象必须是同一个.包括 RequestDispatcher.forward or RequestDispatcher.include 调用.
如果是包装的请求对象,传给下一个 filter 也必须是同一个对象.

Filter 环境

使用 web.xml 中使用<init-param>标签描述初始化参数集合.通过 FilterConfig 的 getInitParameter 和getInitParameterNames方法可以访问初始化参数.FilterConfig 还能访问 ServletContext.

Filter 和目标 Servlet 必须执行在同一个线程.

Web 应用的 Filter 配置

Filter 可以通过 @WebFilter 定义,也可以通过 web.xml (deployment descriptor)的 <filter> 标签定义.

web.xml 声明一个 Filter 例子:

<filter>
    <filter-name>过滤器名字,用于 filter-mapping</filter-name>
	<filter-class>过滤器类</filter-class>
	<init-param>
	    <param-name>aaa</param-name>
        <param-value>222</param-value>
	</init-param>
	<init-param>
	    <param-name>ccc</param-name>
        <param-value>333</param-value>
	</init-param>
</filter>

filter 与 Servlet 映射:

<filter-mapping>
<filter-name>Image Filter</filter-name>
<servlet-name>ImageServlet</servlet-name>
</filter-mapping>

filter 与 URL pattern 映射:

<filter-mapping>
<filter-name>Logging Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

一个 Filter mapping 可同时包含,容器必须按配置的顺序展开为多个Filter mapping,比如以下配置

<filter-mapping>
    <filter-name>Multipe Mappings Filter</filter-name>
    <url-pattern>/foo/*</url-pattern>
    <servlet-name>Servlet1</servlet-name>
    <servlet-name>Servlet2</servlet-name>
    <url-pattern>/bar/*</url-pattern>
</filter-mapping>

等同于:

<filter-mapping>
    <filter-name>Multipe Mappings Filter</filter-name>
    <url-pattern>/foo/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>Multipe Mappings Filter</filter-name>
    <servlet-name>Servlet1</servlet-name>
</filter-mapping>
<filter-mapping>
    <filter-name>Multipe Mappings Filter</filter-name>
    <servlet-name>Servlet2</servlet-name>
</filter-mapping>
<filter-mapping>
    <filter-name>Multipe Mappings Filter</filter-name>
    <url-pattern>/bar/*</url-pattern>
</filter-mapping>

关于过滤器链的顺序的要求意味着容器,当接收到传入请求时,按如下方式处理该请求:

  1. 识别目标 Web 资源.按照 映射规则
  2. 如果根据 Servlet Name 匹配到了 Filters ,容器按照 web.xml 声明Filter 的顺序构建 Filter chain.
  3. 如果使用 <url-pattern>匹配到了 Filters ,容器按照 web.xml 声明Filter 的顺序构建 Filter chain.

为了高性能,Web 容器将能够缓存 Filter Chain.

Filters and the RequestDispatcher

自从 2.4 标准,能够在请求分发器的 forward() 和 include() 方法调用配置的 filter。
通过使用 标签,开发者可以指示一个 filter-mapping 是否应用到此请求上 ,当:

  1. 请求直接从客户端发来
<dispatcher>REQUEST</dispatcher>
  1. 由 forward() 转发过来的请求
<dispatcher>FORWARD</dispatcher>
  1. 由 include() 调用
<dispatcher>INCLUDE</dispatcher>
  1. 错误页面
<dispatcher>ERROR</dispatcher>
  1. async context 分发过来的请求
<dispatcher>ASYNC</dispatcher>

举个例子:

<filter-mapping>
    <filter-name>Logging Filter</filter-name>
    <url-pattern>/products/*</url-pattern>
</filter-mapping>

下面 filter-mapping,只能是通过调用 RequestDispatcher#include() 方法请求 ProductServlet 时 Logging Filter 才能应用到 ProductServlet 上:

<filter-mapping>
    <filter-name>Logging Filter</filter-name>
    <servlet-name>ProductServlet</servlet-name>
    <dispatcher>INCLUDE</dispatcher>
</filter-mapping>

下面 filter-mapping,由客户端直接发过来的请求,或者调用 RequestDispatcher#forward() 方法 发过来的请求:

<filter-mapping>
    <filter-name>Logging Filter</filter-name>
    <servlet-name>ProductServlet</servlet-name>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

Session

HTTP(Hypertext Transfer Protocol)是一个无状态协议。
HttpSession 接口允许 Servlet Container 使用任一路径跟踪用户的会话。

Session 跟踪机制

Cookies

使用 HTTP Cookies 跟踪会话是最多使用的会话跟踪技术。
要求所有 Servlet 容器支持。

容器发送一个 cookie 给客户端。客户端会将cookie跟随每个子请求发给服务器。
会话跟踪cookie 的标准名称必须是"JSESSIONID"。

所有的 servlet 容器必须提供配置是否标记 cookie 作为 HttpOnly。

SSL Sessions

Secure Sockets Layer (SSL),HTTPS协议中的加密技术。
SSL 有一个内置的机制——允许来自一个客户端的多个请求作为会话的一部分进行验证。

URL Rewriting

当一个客户端不接受 Cookie 时,服务器 可能使用 URL 重写作为会话跟踪的基础。
URL 重写包含:给 URL path 添加数据,session ID 。session ID必须被编码为 path 参数且参数名必须是 jsessionid, 比如:

http://www.myserver.com/catalog/index.html;jsessionid=1234

当 cookies 或 SSL session 可用的情况下,URL 重写机制不能用于 session 跟踪。

Session 完整性

Web Container 必须支持 HTTP session;为了满足这个要求,Web Container 普遍支持 URL 重写机制

创建 session

因为 HTTP 是基于请求-响应式协议,所以 HTTP Session 被认为是一个新会话,直到 客户端 “加入”它。当服务器返回session 跟踪信息表明session已经建立时, 客户端才加入一个会话。直到客户端加入会话以后,接下来的请求都会被认为是 session 的一部分。
每个 session 都有一个 ID。

session 范围

HttpSession 对象范围必须是 应用(或者 servlet context)级别。

Servlet A 通过 RequestDispatcher 调用另一个 Web 应用的 Servlet B,那么 Servlet B 创建的任何 Sessions 和可见的 Session与 Servlet A 都不一样。

给 Session 绑定属性

servlet 可以给 HttpSession 绑定一个对象属性。
任何绑定到 session 的对象对于属于相同 ServletContext 的其他 Servlet 都可用。

HttpSessionBindingListener 接口可以监听属性对象设置和移除。 下面方法可以发送消息(对象绑定到 session 或者对象从session 中解绑):

  • valueBound:必须在通过 HttpSession#getAttribute 方法让对象生效之前调用。
  • valueUnbound:必须在通过 HttpSession#getAttribute 方法让对象不可用之后调用。

session 超时时间

超时机制指明客户端什么时候失效。

通过 HttpSession#setMaxInactiveInterval() 方法定义 session 超时时间,单位:秒。

最近访问时间

HttpSession#getLastAccessedTime()

重要的 Session 语义

线程

同一时间多个 Servlet 处理请求的线程会访问同一个 Session 对象。容器必须保证线程安全的方式访问。

分布式环境

在一个分布式应用中,属于同一 session 的所有请求必须一次只能由1个JVM处理。

  • 容器必须接受 实现 Serializable 接口的对象
  • 容器可能支持其他指定对象的存储。
  • 会话的迁移将由特定于容器的工具来处理。

如果分布式 servlet 容器不支持会话迁移那么需要抛出 IllegalArgumentException。
分布式 servlet 容器必须支持Serializable 接口的对象的迁移。
容器提供者能够将会话对象及其内容从分布式系统的任何活动节点移动到系统的不同节点,从而确保服务特性(如负载平衡和故障转移)的可伸缩性和质量。

在一个 session 的迁移过程中,容器必须通知任何实现了 HttpSessionActivationListener 接口的session 属性。

客户端语义

事实上,由于 cookies 和 SSL 证书都由浏览器处理。为了获得最大的可移植性,开发人员应该总是假设客户端的所有窗口都参与同一个会话。

注解和可插拔性

本章描述了如何使用注释和其他增强功能来支持框架和库的可插拔性,以便在web应用程序中使用。

在一个 web 应用中, 使用注解的类只有位于 WEB-INF/classes 目录或者 WEB-INF/lib 下的jar包中时才会被处理.

web 应用部署描述符 在 <web-app>标签增加了一个 新属性metadata-complete.这个属性定义了 在部署时,web 描述符是否完整,jar包里类文件是否检查 注解和 web fragments.
web.xml 文件 <web-app>标签 metadata-complete 属性设置为 true,则部署工具必须忽略任何 Servlet 注解(声明了不熟信息);如果设置为 false,则部署工具会检查注解的完整性并且扫描 jar包的 web fragment配置信息。

注解

实现 Servlet 3.0 的web 容器必须支持以下注解:

  • @WebServlet
  • @WebFilter
  • @WebInitParam
  • @WebListener
  • @MultipartConfig

@WebServlet

@WebServlet 用于定义web 应用中的一个 Servlet 组件。这个注解声明在类上。
必须声明 @WebServlet的 urlPatterns或者value属性。
不能同时声明 urlPatterns 和 value属性。
@WebServlet 必须声明至少1个 urlPattern。
web.xml 中描述的同一个 servlet 类但 Servlet 名字不同的Servlet 必须创建多个servlet 类的实例。

@WebServlet 标注的类必须扩展自 javax.servlet.http.HttpServlet 类,比如:

@WebServlet(name=”MyServlet”, urlPatterns={"/foo", "/bar"})
public class SampleUsingAnnotationAttributes extends HttpServlet{
    public void doGet(HttpServletRequest req, HttpServletResponse res) {
     }
}

@WebFilter

@WebFiltert 用于定义web 应用中的一个 Filter 组件。这个注解声明在类上。
该注解的 urlPatterns (或者value)属性和 servletNames 属性必须声明。

@WebFilter 标注的类必须实现 javax.servlet.Filter 接口,比如:

@WebFilter(“/foo”)
public class MyFilter implements Filter {
    public void doFilter(HttpServletRequest req, HttpServletResponse res)
    {
    ...
    }
}

@WebInitParam

@WebInitParam 用于声明任何传给 Servlet 和 Filter 的初始化参数.
@WebInitParam 是 @WebServlet 和 @WebFilter 主机的一个属性.

@WebListener

@WebListener 注解用于标注一个 listener 用于获取web 应用上下文中各种各样操作的事件.
标注 @WebListener 注解的类必须实现以下接口中的一个:

  • javax.servlet.ServletContextListener
  • javax.servlet.ServletContextAttributeListener
  • javax.servlet.ServletRequestListener
  • javax.servlet.ServletRequestAttributeListener
  • javax.servlet.http.HttpSessionListener
  • javax.servlet.http.HttpSessionAttributeListener
  • javax.servlet.http.HttpSessionIdListener

例如:

@WebListener
public class MyListener implements ServletContextListener{
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext sc = sce.getServletContext();
        sc.addServlet("myServlet", "Sample servlet",  "foo.bar.MyServlet", null, -1);
        sc.addServletMapping("myServlet", new String[] { "/urlpattern/*" });
    }
}

@MultipartConfig

这个注解当标注在 Servlet 上时,指明,期望的请求类型为 mime/multipart.
HttpServletRequest 对象必须能够通过 getParts 和 getPart 方法遍历各种 mime 附件.
javax.servlet.annotation.MultipartConfig 的 location 属性和 子标签被翻译为绝对路径,默认值为 javax.servlet.context.tempdir.
如果声明的是相对路径,则将会是相对于 javax.servlet.context.tempdir 位置.
测试是相对路径还是绝对路径可通过 java.io.File.isAbsolute 测试.

其他注解或约定

Java代码中的元数据也称为注解.在 Java EE中, 注解被用于对外部资源的依赖和在 java 代码上配置数据(而不需要在配置文件中定义数据).

以下资源注入相关的注解仍旧会生效:

  • @DeclareRoles
  • @EJB
  • @EJBs
  • @Resource
  • @PersistenceContext
  • @PersistenceContexts
  • @PersistenceUnit
  • @PersistenceUnits
  • @PostConstruct
  • @PreDestroy
  • @Resources
  • @RunAs
  • @WebServiceRef
  • @WebServiceRefs
  • Contexts and Dependency Injection for Java EE requirements

默认所有应用将会在 web.xml 的 welcome-file-list 标签下有 index.html 和 index.jsp.

当使用注解时,Listeners,Servlets加载的顺序无法保证;如果顺序很重要则使用web.xml 和 web-fragment.xml ,而且顺序只能在web.xml 和 web-fragment.xml中声明.

可插拔性

web.xml 的模块化

如果 web.xml 的 metadata-complete 设置为true, 那么 jar包里的类文件上描述部署信息的注解和web-fragment.xml都不会处理.这也就是暗示,所有的元数据均通过 web.xml 描述符声明.

为了更好的插拔性和更小配置,我们将介绍 web fragment 这个概念. web fragment 是 web.xml 的一部分.web fragment可以被声明和包含到jar包中或jar包的 META-INF 目录下.
一个没有 web-fragment.xml 且位于 WEB-INF/lib 目录下的简单 jar文件 也被任务是一个 fragment.这个jar包中的注解(@WebServlet 等)将会被处理.

一个 web fragment 是 web 应用逻辑上一部分,开发者不用编辑和修改 web.xml 文件即可使用.web-fragment.xml 几乎包含 web.xml 相同的元素.然而,描述符的顶级元素必须是 web-fragment,对应的描述符文件必须被称为web fragment.xml。

如果框架打包成 jar 文件使用,那么 web-fragment.xml 必须在 META-INF/ 下; 如果想使用 META-INF/web-fragment.xml 去补充 web.xml ,那么这个框架必须位于 Web 应用的 WEB-INF/lib 目录下. 只有位于 Web 应用的 WEB-INF/lib 目录下JAR 文件才需要被扫描查找 web-fragment.xml .

在部署期间,容器负责扫描web-fragment.xml的位置和发现并处理web-fragment.xml.

META-INF/web-fragment.xml 文件内容实例:

<web-fragment>
    <servlet>
        <servlet-name>welcome</servlet-name>
        <servlet-class>
            WelcomeServlet
        </servlet-class>
    </servlet>
    <listener>
        <listener-class>
            RequestListener
        </listener-class>
    </listener>
</web-fragment>

web.xml 和 web-fragment.xml 顺序

配置来自于 web-fragment.xml 和 annotations 的顺序并没有定义.

由于本标准允许由多个配置文件(web.xml 和 web-fragment.xml)组成 应用配置资源,发现和加载来自不同的位置,所以必须解决 顺序的问题.

web-fragment.xml中有一个顶级标签 <name>, 这个name 标签只能配置1个,如果配置了这个标签,依据name标签进行排序.

绝对顺序

web.xml 中添加 <absolute-ordering> 标签定义web-fragment 顺序.web.xml 只能有1个 <absolute-ordering> 元素.

如果添加了 <absolute-ordering> 标签那么相对顺序的配置必须被忽略.

web.xml 和 WEB-INF/classes 必须 absolute-ordering 清单里的 web-fragments 处理之前先被处理.

absolute-ordering 清单里 name 绝对顺序就是 web-fragment 的处理顺序.

如果 absolute-ordering 标签下没有定义 <others/>标签,name标签未提到的任何 web fragment 必须被忽略;被排除的jar包也不会被扫描查找 注解 Servlet ,Filter等.如果 web.xml 或者 未排除的 web-fragment.xml 配置了 被排除的jar包的 Servlet ,那么这个 Servlet的注解也会被处理(除非被 metadata-complete排除).
排除jar包中 TLD 文件中的 ServletContextListeners不能使用编程式 API 配置 Filter等.如果 发现ServletContainerInitializer 从排除的jar包加载的那么就会被忽略.

不考虑 metadata-complete 配置, 被 web.xml 的absolute-ordering标签排除在外的 jar 不会被扫描查找由 ServletContainerInitializer 处理的类.

当检索<absolute-ordering>下的子标签时,如果遇到有相同name的,只有第一个会生效.

举个例子
web.xml 内容如下:

<web-app>
    <absolute-ordering>
        <name>MyFragment3</name>
        <name>MyFragment2</name>
    </absolute-ordering>
    ...
</web-app>

那么处理先后顺序依次是: web.xml MyFragment3 MyFragment2

相对顺序

web-fragment.xml 的<ordering>元素定义了相对位置.每个web-fragment.xml 只能有1个<ordering>元素.

<ordering>元素下可以包含 <before><after> 两个元素.

web.xml 和 WEB-INF/classes 必须 <ordering>元素列举的 web-fragments 处理之前先被处理.

如果定义了相同 name 元素,则需要打印错误信息,部署失败!

举个例子
有 3个 web-fragment: MyFragment1, MyFragment2 MyFragment3 和 1个webxml ,它们的内容如下:

=========== MyFragment1 ============
<!-- MyFragment1 在 MyFragment2 之后-->

<web-fragment>
    <name>MyFragment1</name>
    <ordering>
        <after><name>MyFragment2</name></after>
    </ordering>
    ...
</web-fragment>

=========== MyFragment2 ============
<!-- MyFragment2 没有任何顺序依赖 -->
<web-fragment>
    <name>MyFragment2</name>
    ...
</web-fragment>

=========== MyFragment3 ============
<!-- MyFragment3 在其他所有的 web-fragment 之前-->
<web-fragment>
    <name>MyFragment3</name>
    <ordering>
        <before><others/></before>
    </ordering>
    ..
</web-fragment>

=========== web.xml ============
<web-app>
...
</web-app>

以上处理的顺序依次是: web.xml MyFragment3 MyFragment2 MyFragment1

如果发现循环依赖,那么应用部署失败并打印失败日志!

组合 web.xml, web-fragment.xml 和 annotations 的描述符

使用注解定义 Servlet Filter 等并没有声明顺序.
以下规则应用于最终部署描述符:

  1. Listeners, servlets,和 filter 的相对顺序必须在 web.xml 或 web-fragment.xml 声明
  2. 以下顺序基于 web.xml 的 absolute-ordering 元素或者 web-fragment.xml 的 ordering 定义的顺序
    2.1 匹配同一个请求的 Filters 按照 web.xml 中声明的顺序被串联起来.
    2.2 Servlet 在请求处理时再初始化或者在部署期间初始化,初始化根据 load-on-startup 来排序.
    2.3 listeners 按照 web.xml 中声明的顺序调用:
    2.3.1 对于 javax.servlet.ServletContextListener 的实现 按照其声明的顺序调用 contextInitialized 方法, 按照声明倒叙调用 contextDestroyed 方法.
    2.3.2 对于javax.servlet.ServletRequestListener 的实现,按照其声明的顺序调用 requestInitialized 方法, 按照声明倒叙调用 requestDestroyed 方法.
    2.3.3 对于 javax.servlet.http.HttpSessionListener 的实现,按照其声明的顺序调用 sessionCreated 方法, 按照声明倒叙调用 sessionDestroyed 方法.
    2.3.4 ServletContextAttributeListener, ServletRequestAttributeListener 和 HttpSessionAttributeListener 的实现,按照其声明的顺序调用.

2.4 如果 web.xml 的 enabled 元素设置一个 Servlet 不可用,那么这个 Servlet 将在为这个 Servlet 声明的 url-pattern 中不可用.

2.5 在处理 web.xml , web-fragment.xml 和注解之间的冲突时, web.xml 具有最高的优先级.

2.6 如果 web.xml 没有设置 metadata-complete 或者,设置为false,那么应用有效的元数据来自 annotation 和 xml中.合并的规则如下:
(1) web fragments 设置的配置信息用于补充已经声明在 web.xml的.
(2) web fragments 设置的配置信息按照上边说的 [ we.xml 和 web-fragment.xml 顺序] 添加到 web.xml 中对应的配置
(3) web.xml 的 metadata-complete 设置为 true, 在部署时不扫描注解和fragment,忽略absolute-ordering 和 ordering;如果 web-fragement.xml 的 metadata-complete 设置为 true,只能扫描当前jar包里的 annotations.
(4) Web fragments 被合并到主 web.xml ,除非 web.xml 的 metadata-complete 设置为 true.合并动作发生在对应fragment 里注解处理之后.
(5) 下面是使用web fragment 补充web.xml遇到的冲突问题:

  • 多个 配置了 相同的参数
  • 多个 配置了相同 不同
    以上冲突的解决方法:
  • web.xml 和 web fragment 冲突则 web.xml 优先级最高
  • web fragment 之间冲突,则报错

(6) 多个 web-fragments 元素叠加后放到 web.xml ,举个例子,拥有不同 将会叠加到一起.
(7) web.xml 中声明的元素的值覆盖 web-fragment 相同名字的元素的值.
(8) 如果1个元素最小出现0次,最大出现1次并且在web fragment 中 声明,那么 主 web.xml 则继承 web fragment的这个配置;如果 web.xml 和 web fragment 都出现,那么 web.xml的配置 拥有更高优先级.

(9) 如果1个元素最小出现0次,最大出现1次并且在多个不同的 web fragment 中 声明,则报错.

(10) <welcome-file> 声明将会累加.
(11) 不同 web fragment 具有相同<servlet-name><servlet-mapping> 累加; web.xml 声明的<servlet-mapping> 覆盖 web fragment中相同<servlet-name><servlet-mapping> .
(12) 不同 web fragment 具有相同<filter-name><filter-mapping> 累加; web.xml 声明的<filter-mapping> 覆盖 web fragment中相同<filter-name><filter-mapping> .

(13) 相同的<listener-class>多个 <listener>元素 被认为是1个 <listener>声明.

(14) 所有 web fragments 都被标记为 <distributable>时,合并后的 web.xml 才被认为是 <distributable>.

(15) web fragment 的 '' (和它的子标签) '' '' 会被忽略.

(16) jsp-property-group 会累加.在jar文件的META-INF/resources目录中绑定静态资源时,建议jsp-config元素使用url模式,而不是扩展映射。

(17) 所有资源引用元素(env-entry, ejb-ref,ejb-local-ref,service-ref,resource-ref,resource-env-ref,message-destination-ref,persistence-context-ref 和 persistenct-unit-ref) 应用以下规则:

  • 1个 web fragement 的声明任何资源引用,如果 web.xml 中不存在,则合并到 web.xml 中;如果 web.xml 和这个web fragement声明了相同名字的元素,那么以 web.xml 优先;
  • 在 web.xml 没有配置资源引用时,如果2个 web fragment 声明了相同名字的资源引用,并且资源引用的所有属性和子元素完全相同,那么则 合并到主 web.xml 中;在 web.xml 没有配置资源引用时,如果2个 web fragment 声明了相同名字的资源引用,并且资源引用的所有属性和子元素不相同,那么则报错!
  • 如果 web fragments 有相同名字 元素将会合并到主 web.xml .

(18) 资源引用注解(@Resource, @Resources, @EJB, @EJBs, @WebServiceRef,
@WebServiceRefs, @PersistenceContext,
@PersistenceContexts,@PersistenceUnit, and
@PersistenceUnits)应用规则:

  • resource reference 注解标注在一个 class 上等于定义一个资源而不等于定义一个 injection-target. 按照上面 规则处理.
  • resource reference 注解用于成员变量是等同于在 web.xml 定义一个 , 且 web.xml 没有配置 ,然后 fragment 的
  • 如果 web.xml 配置了injection-target, 并且有一个相同资源名字的 resource reference 注解,则 web.xml 则会覆盖 注解的配置.

(19) 在 web.xml 没有配置 data-source 元素时,如果2个 web fragment 声明了相同名字的data-source,并且data-source的所有属性和子元素相同,那么 data-source 则合并到主 web.xml 中;在 web.xml 没有配置 data-source 时,如果2个 web fragment 声明了相同名字的data-source,并且data-source的属性和子元素不相同,那么则报错!

(20) 合并规则看文档吧,太多了这里不说了。

(21) 任何通过注解声明的元数据,如果部署描述符里没有配置那么将会增加到有效的 web.xml :
1. web.xml 和 web-fragment.xml 中声明的配置比注解有更高的优先级。
2. 通过 @WebServlet 注解定义的 servlet的名字和 web.xml 中的servlet 名字一致时,才能被 web.xml 覆盖.
3. 注解定义的 servlet 的init param的名字如果和 web.xml 定义的 servlet 的 init param 名字一样,那么就会被web.xml 覆盖;init params 将会合并。filter 的 init param 和 servlet 一样。
4. servlet 的 url-patterns, web.xml覆盖 注解。
5. 通过 @WebFilter 注解定义的 filter 的名字和 web.xml 中的 filter 名字一致时,才能被 web.xml 覆盖.
6. filter 的 url-patterns, web.xml覆盖 注解。
7. filter应用的DispatcherTypes , web.xml覆盖 注解。

共享的 lib 或 运行时 可插拔性

通过 SPI机制查找 jar包ServletContainerInitializer 类.
对于每一个应用, 在应用启动阶段,由容器创建 ServletContainerInitializer 类的一个示例.
框架提供的 ServletContainerInitializer 类的实现类的全限定名必须放在 jar包中 META-INF/services 目录下 javax.servlet.ServletContainerInitializer 文件中.

和 ServletContainerInitializer 搭配使用的还有一个 HandlesTypes 注解.这个注解指明了感兴趣的类都有哪些,也就是
指明了 onStartup 的 Set<Class> c 参数的都有什么类型.

@HandlesTypes 注解不受 metadata-complete 配置束缚,即有无metadata-complete 这个注解都会正常应用.

如果一个 ServletContainerInitializer 的实现没有@HandlesTypes注解,那么onStartup 的 Set<Class> c 参数会是null.

ServletContainerInitializer#onStartup() 方法调用在所有 servlet 监听事件之前触发.

ServletContainerInitializer#onStartup() 的 Set<Class> c 参数里的类型就是 @HandlesTypes 注解声明的哪些类型.

spring框架的 3.1 版本新增了 ServletContainerInitializer 的实现类:org.springframework.web.SpringServletContainerInitializer .

应用 WEB-INF/lib 目录下 jar 包中的 ServletContainerInitializer#onStartup() 方法只会调用1次;如果jar包在 WEB-INF/lib 目录外,但仍会被 SPI 发现,那么它的 onStartup() 方法在 应用每次启动时就被调用.

ServletContainerInitializer 接口的实现将会被运行阶段的 SPI 机制或者 容器的声明机制所发现.

如果 web fragment 在绝对顺序之外,那么来自 web fragment jar包的 ServletContainerInitializer services 必须被忽略.
如果 web fragment 在绝对顺序之内,那么在 web fragment jar包的发现的 ServletContainerInitializer services 必须遵从应用类加载委派模型.

JSP container 可插拔性

ServletContainerInitializer 和编程式注册特性,使得JSP容器和Servlet 容器职责分明,即JSP 容器解析 TLD(Tag
Library Descriptor) 资源, Servlet 容器负责解析 web.xml 和 web-fragment.xml.

从 3.0 和之后的版本,扫描 TLD 资源可能交给 JSP 容器.
从 3.0 和之后的版本,通过 ServletContext.getJspConfigDescriptor 就能拿到 web.xml 或 web-fragment.xml 中jsp-config 相关的配置信息.

TLD 中发现或者编程式注册的任何 ServletContextListeners 调用 Servlet 3.0新增的 ServletContext API 方法时需要抛出 UnsupportedOperationException.

从 3.0 和之后的版本, Servlet 容器必须提供一个名字为 "javax.servlet.context.orderedLibs" 的 ServletContext 属性.这个属性存放了WEB-INF/lib目录下可能按 web fragment 名字排序(或者 null) 的 jar 包名字的列表.

处理注解 和 fragment

下面表格描述了是否处理注解和web fragments:

Deployment descriptor metadata-complete 是否处理 annotations 和 web fragments
web.xml 2.5 yes no
web.xml 2.5 no yes
web.xml 3.0 + yes no
web.xml 3.0 + no yes

Dispatching Requests

在构建一个 web 应用时,经常用到将转发一个请求的处理到另一个 Servlet,或者将另一个 Servlet 的结果包含到当前 response 中.
RequestDispatcher 接口提供了一个实现机制.

当一个请求开启了异步处理, AsyncContext 允许用户将请求分发给 Servlet 容器.

获取 RequestDispatcher

RequestDispatcher 接口实现对象可能通过以下方法获取:

  • ServletContext#getRequestDispatcher()
  • ServletContext#getNamedDispatcher()

ServletContext#getRequestDispatcher(String path)
path 必须是以 '/'开头,相对与 ServletContext 的root目录,或者为空.该方法使用path查找 Servlet ,依据 " Mapping Requests to Servlets" Servlet path 匹配规则进行匹配,并将 Servlet 包装为 RequestDispatcher 对象.
如果基于path 没找到 Servlet,那么 RequestDispatcher 将会返回那个Path的内容

ServletContext#getNamedDispatcher(String name)
getNamedDispatcher 方法传入一个 Servlet的名字给 ServletContext.如果 Servlet 能找到,那么将 Servlet 包装为 RequestDispatcher 对象返回.如果 Servlet 没找到,那么该方法必须返回 null.

ServletRequest#getRequestDispatcher(String path)
该方法允许传入一个相对于当前请求的路径.
举个例子:
上下文根目录:"/",一个 "/garden/tools.html"请求,然后通过 ServletRequest.getRequestDispatcher("header.html") 获取请求分发,等同于调用 ServletContext.getRequestDispatcher("/garden/header.html") .

Request Dispatcher 路径中的查询字符串

ServletContext 和 ServletRequest 使用 path 创建 RequestDispatcher 对象的方法允许 path 带着查询字符串.
例如:

String path = “/raisins.jsp?orderno=5”;
RequestDispatcher rd = context.getRequestDispatcher(path);
rd.include(request, response);

RequestDispatcher 关联的参数声明周期
RequestDispatcher 关联的参数仅应用于 include 或者 forward 方法调用.

使用 Request Dispatcher

Servlet 调用 RequestDispatcher#include 方法或 RequestDispatcher#forward 方法,使用请求分发器.

容器提供者应该确保向目标servlet发送请求的过程发生在与原始请求相同的JVM的相同线程中。

include 方法

include 方法的目标 Servlet可以访问请求对象的所有内容,但是在 response 对象的使用有更大的限制.

目标 Servlet 只能将信息写入到 response 对象的 ServletOutputStream 或者 Writer中;不能设置 header 或者调用任何修改response header的方法. HttpServletRequest.getSession() 和 HttpServletRequest.getSession(boolean) 方法必须抛出 IllegalStateException.

如果默认 Servlet 是 RequestDispatch.include() 的目标 Servlet,并且 请求资源并不存在,那么必须抛出FileNotFoundException异常.如果异常没有捕获或处理,status code 必须是 500.

include 请求参数

目标 Servlet 能够通过 getAttribute 方法从request 对象中访问以下属性:

javax.servlet.include.request_uri  // 请求的URL
javax.servlet.include.context_path
javax.servlet.include.servlet_path
javax.servlet.include.path_info
javax.servlet.include.query_string

如果include 多层嵌套,那么就会被当前调用include给替换掉.

forward 方法

只有在当前 Servlet 没有提交输出给客户端时才能调用.
如果调用 forward 方法前,响应缓存中存在缓存,则必须清除后才能调用 forward方法.
如果调用 forward 方法前,如果响应已经提交给客户端之后调用 forward方法.那么必须抛出 IllegalStateException 异常.

暴露给目标 Servlet 的 Path元素,必须映射获取 RequestDispatcher使用的Path.

通过 getNamedDispatcher 方法获取的 RequestDispatcher,目标Servlet 的Request对象中的Path元素必须映射源 request的Path.

请求分发机制负责在forwarding 或者 including 收集参数.

在调用 RequestDispatcher(通过 path 获取的RequestDispatcher) 的 forward 方法时,以下请求属性必须设置:

javax.servlet.forward.request_uri
javax.servlet.forward.context_path
javax.servlet.forward.servlet_path
javax.servlet.forward.path_info
javax.servlet.forward.query_string

以上参数与 HttpServletRequest 的 getRequestURI , getContextPath , getServletPath ,
getPathInfo , getQueryString 返回值一一对应.

getRequestDispatcher(String path) 方法获取的 RequestDispatcher 在调用 forward 方法时,以上参数 一定不能设置!

错误处理

目标 Servlet 抛出的 ServletException 或者 IOException 必须传给当前 Servlet.
其他的异常需全部包装为 ServletException,不传给当前 Servlet.

获取一个 AsyncContext

可能通过 ServletRequest#startAsync 方法获取一个 AsyncContext.
要么使用 complete() 方法完成请求的处理,要么使用以下某个 dispatch分发请求:

  • dispatch(path)
  • dispatch(servletContext, path)
  • dispatch()

AsyncContext#dispatch 方法

dispatch(path)
path : 必须是相对 ServletContext 根目录的路径与且以/字符开始
dispatch(servletContext, path)
path : 必须是相对 ServletContext 根目录的路径与且以/字符开始
dispatch()
没有参数,使用源 url 作为Path.

在调用 AsyncContext#dispatch 方法后,应用就等待 异步事件的产生,一旦调用 AsyncContext#complete 方法,必须抛出IllegalStateException 异常信息.并且所有dispatch 返回会立刻返回(不提交响应).

暴露给目标Servlet 的path元素必须映射在 AsyncContext.dispatch 声明的Path.

Web Applications

一个 WEB 应用是 Servlet,html 页面,类和其他资源组成.

Web 服务器中的 Web 应用

Web 应用是位于Web 服务器中的具体目录.例如, catalog 应用就位于 http://www.mycorp.com/catalog ,所有以这个为前缀的请求将会路由到代表 catalog应用的 ServletContext.

默认, 一个Web 应用的实例必须运行在1个VM(虚拟机)中.如果 web.xml 标记了这个应用是“distributable”,则可以运行在多个VM(虚拟机)中.

与 ServletContext 的关系

Servlet 容器必须保证 web 应用和 ServletContext 一一对应!

Web 应用的元素

  • Servlets
  • JSP 页面
  • 工具类
  • 静态文档(HTML,images,sound)
  • 客户端 JAVA applets ,beans 和 classes
  • 跟随以上元素的说明文档

目录结构

例子:

/index.html
/howto.jsp
/feedback.jsp
/images/banner.gif
/images/jumping.gif
/WEB-INF/web.xml
/WEB-INF/lib/jspbean.jar
/WEB-INF/lib/catalog.jar!/META-INF/resources/catalog/moreOffers/books.html
/WEB-INF/classes/com/mycorp/servlets/MyServlet.class
/WEB-INF/classes/com/mycorp/util/MyUtils.class

Web 应用打包文件

使用 标准的 Java 打包工具可以吧 web 应用打包成 .war 格式的文件.

web 应用部署文件

web 应用部署文件包含以下类型的配置和部署信息:

  • ServletContext 初始化参数
  • Session 配置
  • Servlet/jsp 定义
  • Servlet/jsp 映射
  • MIME 类型映射
  • 欢迎文件列表
  • 错误页面
  • Security

依赖扩展

扩展必须在war包中提供 META-INF/MANIFEST.MF 列举所有需要的扩展.
META-INF/MANIFEST.MF 文件也可以在 WAR.包的 WEB-INF/lib 下的jar包中.

Web 应用类加载器

加载 WAR 中 Servlet 的 类加载器,必须允许使用 getResource 加载 jar 包里的包含的各种资源文件.

Thread.currentThread.getContextClassLoader() 必须返回一个类加载器实例.
每个 web 应有都有1个类加载器,不同应用之间的类加载器是不同的!

替换 web 应用

服务器应该保证不重启容器的情况下,使用新版本的应用替换当前版本.

在替换应用程序时,容器应该提供一种健壮的方法来在该应用程序中保存会话数据.

错误处理

请求属性

如果错误处理器是一个 Servlet 或者 JSP:

  • Servlet 或者 JSP收到 容器创建的 request 和 response 对象
  • 如果 RequestDispatcher.forward 分发到一个错误资源,那么以下的请求属性必须设置
Request Attributes                                  Type
javax.servlet.error.status_code             java.lang.Integer
javax.servlet.error.exception_type        java.lang.Class
javax.servlet.error.message                  java.lang.String
javax.servlet.error.exception                  java.lang.Throwable
javax.servlet.error.request_uri               java.lang.String
javax.servlet.error.servlet_name           java.lang.String

以上这些属性,允许 Servlet 根据 status_code ,exception_type等生成专业的内容.
自从 2.3 标准提供了 exception 对象,exception_type 和 message比较多余,保留它们是为了兼容以前版本.

错误页面

为了允许在 Servlet 产生错误时,开发者能够自定义返回内容样式,web.xml 提供了一个错误页面列表.

如果调用 Response#sendError(int sc, String msg) 方法返回错误时,会尝试根据status code 匹配错误页面.

Web 应用可能使用 exception-type 声明错误页面.容器根据抛出异常的类型来匹配错误页面.

如果 Servlet 产生一个错误,但没有被 ERROR page 机制处理,那么 容器必须确保返回 500 的响应.

sendError 方法可以使用 error page机制,setStatus 方法并不会.

如果应用正在使用异步操作,应用负责在创建的线程中处理所有错误!任何 dispatch 方法执行过程中出现的任何错误必须被按照以下方法由容器捕获和处理:

  1. 调用ServletRequest中已注册所有 AsyncListener 实例的onError(AsyncEvent).
  2. 如果没有Listener 调用 AsyncContext.complete 或者 任何 AsyncContext.dispatch方法,然后就分发 一个500 错误.
  3. 如果没有匹配到error page,或者 而ERROR page 没有调用AsyncContext.complete 或者 任何 AsyncContext.dispatch方法,容器必须调用 AsyncContext.complete 方法.

欢迎列表

web.xml 定义的欢迎文件列表:

<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>

WAR 包中静态内容结构:

/foo/index.html
/foo/default.jsp
/foo/orderform.html
/foo/home.gif
/catalog/default.jsp
/catalog/products/shop.jsp
/catalog/products/register.jsp

那么不同的URL 返回的内容如下:

请求URI 返回内容
/foo /foo/
/foo/ /foo/index.html
/catalog /catalog/
/catalog/ /catalog/default.jsp
/catalog/index.html 404 not found
/catalog/products /catalog/products/
/catalog/products/ 可能传给默认的 Servlet

web 应用环境

不属于Java EE技术的 Servlet容器不要求实现应用环境.
Java EE 定义命名环境,即允许应用更容易的访问资源和外部信息,不需要知道外部信息如何命名和组织.
Java EE在部署描述符提供以下元素允许 Servlet 获取资源和beans的引用:

  • env-entry
  • ejb-ref
  • ejb-local-ref
  • resource-ref
  • resource-env-ref
  • service-ref
  • message-destination-ref
  • persistence-context-ref
  • persistence-unit-ref

开发者使用这些元素描述具体的对象,前提是 web 应用需要注册到 web 容器的 JNDI 命名空间中.

如果是 Java EE 技术的 Servlet 容器,则必须支持以上元素.

web 应用部署

当一个 web 应用已经部署到一个容器中,在开始处理来自客户端的请求之前依次执行以下步骤:

  1. 对web.xml配置的每个 Listener 实例化一个实例.
  2. 对于 ServletContextListener 接口的listener 的实例的 contextInitialized()方法.
  3. 为web.xml配置的每个 filter 实例化一个实例并调用每个实例的 init 方法.
  4. 对web.xml配置的包含<load-on-startup>元素的 Servlet 按照 <load-on-startup>的值的顺序依次进行实例化一个实例 ,并调用每个实例的 init 方法.

包含 web.xml

如果一个 web 应用只有 静态资源或jsp页面,那么就不需要包含一个web.xml

Application Lifecycle Events

事件类型和监听器接口

Servlet Context Event

事件类型 描述 接口
Lifecycle Servlet Context 刚被创建,对于 Servlet的第一个请求可用,或者 Servlet Context 关闭 javax.servlet.ServletContextListener
Changes to attributes 添加,移除或替换 Servlet Context中的属性 javax.servlet.ServletContextAttributeListener

HTTP Session Event

事件类型 描述 接口
Lifecycle HttpSession对象被创建/失效/超时 javax.servlet.http.HttpSessionListener
Changes to attributes HttpSession 添加 /移除/替换属性 javax.servlet.http.HttpSessionAttributeListener
Changes to id HttpSession 的id 变更 javax.servlet.http.HttpSessionIdListener
对象绑定 对象已经从HttpSession 被绑定或者解绑 javax.servlet.http.HttpSessionBindingListener
会话迁移 HttpSession 已经被激活或者 钝化 javax.servlet.http.HttpSessionActivationListener

钝化:就是序列化,把对象转化为字节序列,把Session对象从内存保存到硬盘里。(持久化操作)
活化:就是反序列化,把字节序列转化为对象,把Session对象从硬盘再读回内存。

会话的活化和钝化 参考 我喜欢煎蛋卷 博客 使用HttpSessionActivationListener对Session进行钝化/活化的监听

Servlet Request Event

事件类型 描述 接口
Lifecycle Web 组件开始处理Servlet 请求 javax.servlet.ServletRequestListener
Changes to attributes 添加,删除,替换 ServletRequest中的属性 javax.servlet.ServletRequestAttributeListener
Async events 超时,连接关闭或者异步处理完成 javax.servlet.AsyncListener

listener class 配置

  1. 实现 javax.servlet API 中的1个或多个监听器接口

  2. Web 应用部署描述符(web.xml)使用 listener 元素声明 listener class

  3. 注册 listener

  4. Web 容器创建每个监听器 class 的实例,在处理第一个请求前为每个事件通知注册监听器。HttpSessionListener.destory 方法是按倒序调用。

  5. 应用关闭通知
    应用关闭时,按倒序调用监听器。

web.xml 示例

<web-app>
    <display-name>MyListeningApplication</display-name>
    <listener>
        <listener-class>com.acme.MyConnectionManager</listenerclass>
    </listener>
    <listener>
        <listener-class>com.acme.MyLoggingModule</listener-class>
    </listener>
    <servlet>
        <display-name>RegistrationServlet</display-name>
        ...etc
    </servlet>
</web-app>

监听器实例和线程

要求容器,在开始处理第1个请求前完成监听器类的实例化

ServletContext 和 HttpSession 对象的属性可能并发改变,容器不要求同步通知结果。监听器负责处理这种情况。

监听器异常

开发者希望正常处理监听器产生的异常,必须在通知方法内处理自己的异常。

分布式容器

分布式 web 容器中,HttpSession 实例属于处理 session请求的特定 JVM 中,ServletContext 对象属于 Web 容器的JVM中。

监听器实例属于每个 JVM 每一个部署描述符范围。

session 事件

监听器提供了一种跟踪一个 web 容器中 session 的方式。

Mapping Requests to Servlets

使用 URL path 映射请求到 Servlet

收到客户端请求,web 应用必须选择最长上下文(从请求 URL 的开端匹配),匹配到的URL部分就是 context path。

Web 容器必须定位 servlet 处理请求。
URL path 映射规则按以下顺序使用:

  1. 根据请求的 URL path 精确匹配查找 servlet
  2. 容器递归尝试匹配最长的路径前缀。最长匹配决定选择哪个 servlet。
  3. 如果URL path 的最后段包含扩展名(比如 .jsp),容器将会匹配处理这个扩展名的servlet
  4. 如果 精确匹配,path最长前缀匹配,和扩展名匹配都没查到,容器将会尝试使用默认 servlet 提供内容。

容器必须使用区分大小写的字符串匹配

声明映射

web 应用部署描述符(web.xml)使用以下语法定义 servlet 映射:

  • 以 '/' 开始,以 '/*'为结尾的字符串通常是 path 映射
  • 以 '*.'为前缀的字符串作为扩展名映射。
  • 空字符串是映射应用上下文根。请求格式为 http://host:port/ .在这种情况下,path info 是 '/', servlet path 是 '/', context path 是空字符串。
  • 字符串'/'表示应用的默认 servlet;在这种情况下,servlet路径是请求URI减去 context path,且路径信息为空。
  • 其他字符串都是精确匹配

如果 url-pattern 匹配到多个 Servlet 那么容器部署必须失败!

隐式映射

如果一个容器有个内置的 JSP 容器,那么 *.jsp 扩展名就映射到它。这种被称为隐式映射。

如果 web.xml 定义了 *.jsp 映射,那么该配置具有更高的优先级。

例子

servlet 映射集合:

path pattern Servlet
/foo/bar/* servlet1
/baz/* servlet2
/catalog servlet3
*.bop servlet4

以下请求分别匹配到 servlet 如下表:

进来的 path 处理请求的servlet
/foo/bar/index.html servlet1
/foo/bar/index.bop servlet1
/baz servlet2
/baz/index.html servlet2
/catalog servlet3
/catalog/index.html 默认servlet
/catalog/racecar.bop servlet4
/index.bop servlet4

如果是 /catalog/index.html 和 /catalog/racecar.bop,映射到 “/catalog” 的 servlet 不会用到。

Security

介绍

servlet 容器有一些机制满足安全需求,下面分享几个:

  • 认证
  • 访问资源控制
  • 数据完整
  • 数据保密

声明 Security

部署描述符是声明 security 主要方式。

编程式 Security

编程式 Security 由以下 HttpServletRequest 接口的方法组成:

  • authenticate
  • login
  • logout
  • getRemoteUser
  • isUserInRole
  • getUserPrincipal

HttpServletRequest#authenticate

身份验证方法允许应用程序在不受约束的请求上下文中由容器发起对请求调用者的身份验证。

HttpServletRequest#login

login方法允许基于 用户名密码 登录(另一个选择是 表单登录)

HttpServletRequest#logout

重置一个请求的身份证明

HttpServletRequest#getRemoteUser

返回远程用户的名字。如果没有认证,那么此方法返回 null。

HttpServletRequest#isUserInRole

判断请求关联的远程用户是否属于安全角色。如果没有认证,返回false。

此方法需要传一个角色字符串。web.xml 中有 security-role-ref 元素用来描述角色名和角色的应用关系,例如:

<security-role-ref>
    <role-name>FOO</role-name>
    <role-link>manager</role-link>
</security-role-ref>

如果一个用户属于“manager”安全角色,那么 isUserInRole("FOO")方法返回结果是 true。

如果没有匹配到 security-role-ref,容器必须测试用户的角色关系的 security-role 和请求用到的角色是否相等。

调用 isUserInRole 方法的 role name 不能是“*”!

HttpServletRequest#getUserPrincipal

决定 远程用户的名字,并返回一个 java.security.Principal 对象。如果没有认证,那么此方法返回 null。

编程式 Security 策略配置

@ServletSecurity 注解

@ServletSecurity 注解提供了访问控制。
等同于 web.xml 的 a security-constraint 配置或者 ServletRegistration接口的 setServletSecurity方法 配置。
@ServletSecurity 必须修饰在实现 javax.servlet.Servlet 接口的类上。

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ServletSecurity {

    /**
     * The default constraint to apply to requests not handled by specific
     * method constraints
     *
     * @return http constraint
     */
    HttpConstraint value() default @HttpConstraint;

    /**
     * An array of HttpMethodConstraint objects to which the security constraint
     * will be applied
     *
     * @return array of http method constraint
     */
    HttpMethodConstraint[] httpMethodConstraints() default {};
}

@HttpConstraint
应用于所有 HTTP 协议方法的安全限制。
HttpConstraint 源码:

/**
 * This annotation represents the security constraints that are applied to all
 * requests with HTTP protocol method types that are not otherwise represented
 * by a corresponding {@link javax.servlet.annotation.HttpMethodConstraint} in a
 * {@link javax.servlet.annotation.ServletSecurity} annotation.
 *
 * @since Servlet 3.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpConstraint {

    /**
     * The EmptyRoleSemantic determines the behaviour when the rolesAllowed list
     * is empty.
     *
     * @return empty role semantic
     */
    EmptyRoleSemantic value() default EmptyRoleSemantic.PERMIT;

    /**
     * Determines whether SSL/TLS is required to process the current request.
     *
     * @return transport guarantee
     */
    TransportGuarantee transportGuarantee() default TransportGuarantee.NONE;

    /**
     * The authorized roles' names. The container may discard duplicate role
     * names during processing of the annotation. N.B. The String "*" does not
     * have a special meaning if it occurs as a role name.
     *
     * @return array of names. The array may be of zero length, in which case
     *         the EmptyRoleSemantic applies; the returned value determines
     *         whether access is to be permitted or denied regardless of the
     *         identity and authentication state in either case, PERMIT or DENY.<br>
     *         Otherwise, when the array contains one or more role names access
     *         is permitted if the user a member of at least one of the named
     *         roles. The EmptyRoleSemantic is not applied in this case.
     *
     */
    String[] rolesAllowed() default {};

}

  • value() 默认的授权语义,只应用于 rolesAllowrd是空的时候。

  • rolesAllowrd 包含授权角色名字的数组

  • transportGuarantee 请求到达的连接必须满足的数据保护需求。

HttpMethodConstraint

应用于具体的HTTP 协议消息的安全限制。

@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpMethodConstraint {

    /**
     * HTTP Protocol method name (e.g. POST, PUT)
     *
     * @return method name
     */
    String value();

    /**
     * The EmptyRoleSemantic determines the behaviour when the rolesAllowed list
     * is empty.
     *
     * @return empty role semantic
     */
    EmptyRoleSemantic emptyRoleSemantic() default EmptyRoleSemantic.PERMIT;

    /**
     * Determines whether SSL/TLS is required to process the current request.
     *
     * @return transport guarantee
     */
    TransportGuarantee transportGuarantee() default TransportGuarantee.NONE;

    /**
     * The authorized roles' names. The container may discard duplicate role
     * names during processing of the annotation. N.B. The String "*" does not
     * have a special meaning if it occurs as a role name.
     *
     * @return array of names. The array may be of zero length, in which case
     *         the EmptyRoleSemantic applies; the returned value determines
     *         whether access is to be permitted or denied regardless of the
     *         identity and authentication state in either case, PERMIT or DENY.<br>
     *         Otherwise, when the array contains one or more role names access
     *         is permitted if the user a member of at least one of the named
     *         roles. The EmptyRoleSemantic is not applied in this case.
     */
    String[] rolesAllowed() default {};
}
  • value() HTTP 协议方法名

  • emptyRoleSemantic 默认的授权语义,只应用于 rolesAllowrd是空数组的时候。

  • rolesAllowrd 包含授权角色名字的数组

  • transportGuarantee 请求到达的连接必须满足的数据保护需求。

@ServletSecurity 注解一定不能声明到方法上!

例子

all HTTP methods, no constraints

@ServletSecurity
public class Example1 extends HttpServlet {
}

all HTTP methods, no auth-constraint, confidential transport required

@ServletSecurity(@HttpConstraint(transportGuarantee =
TransportGuarantee.CONFIDENTIAL))
public class Example2 extends HttpServlet {
}

all HTTP methods, all access denied

@ServletSecurity(@HttpConstraint(EmptyRoleSemantic.DENY))
public class Example3 extends HttpServlet {
}

all HTTP methods, auth-constraint requiring membership in Role R1

@ServletSecurity(@HttpConstraint(rolesAllowed = "R1"))
public class Example4 extends HttpServlet {
}

All HTTP methods except GET and POST, no constraints
for methods GET and POST, auth-constraint requiring membership in Role R1;
for POST, confidential transport required

@ServletSecurity((httpMethodConstraints = {
 @HttpMethodConstraint(value = "GET", rolesAllowed = "R1"),
 @HttpMethodConstraint(value = "POST", rolesAllowed = "R1",
 transportGuarantee = TransportGuarantee.CONFIDENTIAL)
 })
public class Example5 extends HttpServlet {
}

all HTTP methods except GET auth-constraint requiring membership in Role R1; for GET, no constraints

@ServletSecurity(value = @HttpConstraint(rolesAllowed = "R1"),
 httpMethodConstraints = @HttpMethodConstraint("GET"))
public class Example6 extends HttpServlet {
}

all HTTP methods except TRACE, auth-constraint requiring membership in Role R1; for TRACE, all access denied

@ServletSecurity(value = @HttpConstraint(rolesAllowed = "R1"),
 httpMethodConstraints = @HttpMethodConstraint(value="TRACE",
emptyRoleSemantic = EmptyRoleSemantic.DENY))
public class Example7 extends HttpServlet {
}

@ServletSecurity 与 security-constraint 映射

在 @ServletSecurity没有包含 @HttpMethodConstraint 元素的时候,那么就对应一个单独的包含了 web-resource-collection 元素但没有包含http-method 的security-constraint

Roles

认证

认证有以下几个认证机制:

  • HTTP Basic 认证
  • HTTP Digest 认证
  • HTTPS Client 认证
  • Form Based 认证

HTTP Basic 认证

使用用户名密码的 HTTP 基本认证是 HTTP/1.0 标准定义的。

HTTP Digest 认证

Form Based 认证

HTTPS Client 认证

使用 HTTPS 认证是强认证机制。这个机制要求客户端有一个公钥证书。公钥证书在电子商务应用程序中很有用,也可以用于浏览器内的单点登录

额外容器认证机制

Servlet 容器应该提供公共接口,用于集成和配置额外的 HTTP 消息层认证机制。为了更便捷集成额外的认证机制, 建议所有的容器实现 Java 平台容器认证SPI 的 Servlet 容器配置。SPI 下载地址:JavaTM Authentication Service Provider Interface for Containers

Server Tracking of Authentication Information

  1. 根据 web 应用的环境的一个属性决定 登录机制和策略。
  2. 相同容器中的所有应用使用相同的认证信息。
  3. 只有超过安全策略领域边界时才需要重新认证。

所以容器必须在容器级别跟踪认证信息。这样,一个 web 应用的用户认证可以访问容器的资源。

声明 Security 约束

安全约束是定义保护 web 内容的一种方式。

部署描述符(web.xml) 的 security-constraint 由以下元素组成:

  • web 资源集合(web-resource-collection)
  • 授权约束(auth-constraint )
  • 用户数据约束((user-data-constraint)

web 资源集合(web-resource-collection)由以下几个元素组成:

  • URL 样式(url-pattern)
  • HTTP 方法((http-method or http-method-omission)

授权约束(auth-constraint )由以下几个元素组成:

  • role name(role-name)

用户数据约束((user-data-constraint)由以下几个元素组成:

  • 传输保证(transport-guarantee )

混合约束

例子

<security-constraint>
    <web-resource-collection>
        <!-- 除了 GET和 POST 方法外,其余的HTTP方法都能访问 /* -->
        <web-resource-name>precluded methods</web-resource-name>
        <url-pattern>/*</url-pattern>
        <url-pattern>/acme/wholesale/*</url-pattern>
        <url-pattern>/acme/retail/*</url-pattern>
        <http-method-omission>GET</http-method-omission>
        <http-method-omission>POST</http-method-omission>
    </web-resource-collection>
    <!-- auth-constraint没有配置具体角色,则所有都拒绝访问 /* -->
    <auth-constraint/>
</security-constraint>
<security-constraint>
    <web-resource-collection>
        <web-resource-name>wholesale</web-resource-name>
        <url-pattern>/acme/wholesale/*</url-pattern>
        <http-method>GET</http-method>
        <http-method>PUT</http-method>
    </web-resource-collection>
    <auth-constraint>
        <role-name>SALESCLERK</role-name>
    </auth-constraint>
</security-constraint>

<security-constraint>
    <web-resource-collection>
        <web-resource-name>wholesale 2</web-resource-name>
        <url-pattern>/acme/wholesale/*</url-pattern>
        <http-method>GET</http-method>
        <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
        <role-name>CONTRACTOR</role-name>
    </auth-constraint>
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>
<security-constraint>
    <web-resource-collection>
        <web-resource-name>retail</web-resource-name>
        <url-pattern>/acme/retail/*</url-pattern>
        <http-method>GET</http-method>
        <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
        <role-name>CONTRACTOR</role-name>
        <role-name>HOMEOWNER</role-name>
    </auth-constraint>
</security-constraint>

以上的配置对应的 安全限制规则如下:

序号 url-pattern http 方法 准许的角色 支持的连接类型
1 /* 除了 GET,POST外其他所有方法 拒绝访问 没有限制
2 /acme/wholesale/* 除了 GET,POST外其他所有方法 拒绝访问 没有限制
3 /acme/retail/* 除了 GET,POST外其他所有方法 拒绝访问 没有限制
4 /acme/wholesale/* GET SALESCLERK CONTRACTOR 没有限制
5 /acme/wholesale/* POST CONTRACTOR CONFIDENTIAL
6 /acme/retail/* GET CONTRACTOR HOMEOWNER 没有限制
7 /acme/retail/* POST CONTRACTOR HOMEOWNER 没有限制

处理请求

接收请求和分发请求必须满足一下规则:

  1. 收到的请求的 connection 必须是约束里规定的 connection 类型()。如果不满足此规则那么容器应该拒绝该请求并转发到 HTTPS 端口。
  2. 请求的认证应该满足约束里定义的任何 认证和角色要求。比如 如果访问拒绝,则返回 403(禁止访问);如果角色不满足,则返回 401 (未授权);如果用户的角色不满足则返回 403(禁止访问)。

未覆盖 HTTP 协议方法

security-constraint 使用例子1:

<!-- 除了 GET 方法外,其他 HTTP 方法都没覆盖 -->
<security-constraint>
    <web-resource-collection>
        <web-resource-name>wholesale</web-resource-name>
        <url-pattern>/acme/wholesale/*</url-pattern>
        <http-method>GET</http-method>
    </web-resource-collection>
    <auth-constraint>
        <role-name>SALESCLERK</role-name>
    </auth-constraint>
</security-constraint>

security-constraint 使用例子2:

<!-- 只有 GET 方法没覆盖(http-method-omission 表示忽略 )  -->
<security-constraint>
    <web-resource-collection>
        <web-resource-name>wholesale</web-resource-name>
        <url-pattern>/acme/wholesale/*</url-pattern>
        <http-method-omission>GET</http-method-omission>
    </web-resource-collection>
    <auth-constraint/>
</security-constraint>

security-constraint 使用例子3:

<!-- 除了 GET,POST 方法外,其他方法都没覆盖  -->
@ServletSecurity((httpMethodConstraints = {
 @HttpMethodConstraint(value = "GET", rolesAllowed = "R1"),
 @HttpMethodConstraint(value = "POST", rolesAllowed = "R1",
 transportGuarantee = TransportGuarantee.CONFIDENTIAL)
 })
public class Example5 extends HttpServlet {
}
Security Constraint 配置

确保所有约束的URL patterns 的所有 HTTP 方法有 安全保护

  1. 不要在约束里命名 HTTP 方法。这样能够应用到 HTTP 所有方法。
  2. 如果不能遵守第1条,添加 和在 URL patterns 的约束上声明所有 HTTP 方法
  3. 如果无法遵守第2条,使用 元素或者 @HttpMethodConstraint 注解来表示所有 HTTP 方法的集合。
未覆盖 HTTP 方法的处理

如果 web.xml 设置了 deny-uncovered-http-methods,那么容器必须拒绝任何 HTTP 协议方法。被拒绝的请求应该被返回 403 响应。

默认策略

访问资源时默认不需要认证。只有在 url-pattern 匹配并且 auth-constraint 强加在 HTTP 方法上时才需要认证。

类似的,受保护的传输不需要,除非添加了 user-data-constraint 或者 transport-guarantee 安全限制条件。

登录和退出

Deployment Descriptor Elements

posted on 2022-03-27 14:23  不安分的黑娃  阅读(257)  评论(0编辑  收藏  举报