# Java Web 高级

# 1. JavaWeb 中的静态资源访问

# Tomcat 中的两个默认 Servlet

Tomcat 有两个默认的 Servlet,你的 Web 项目会【无意中】用到它们。JSPServlet 和 DefaultServlet 。

JSPServlet 用于响应 .jsp 请求;DefaultServlet 则是默认的【兜底】的 Servlet 。

# JSPServlet

其实 JSPServlet 并非一个,应该是整个项目有多少个 JSP 页面,就会有对应的多少个 JSPServlet 。

JSPServlet 的工作大家都很清楚了,它涉及到 .jsp 文件的工作原理。

当你第一次访问一个 .jsp 页面时,Tomcat 会根据你的 jsp 页面【帮】你写一个 Servlet,即此处的 JSPServlet 。

访问 jsp 页面最终触发的是这个 Servlet 的执行。在这个 Servlet 中会生成一个页面的内容(html格式字符串),并发回给客户端浏览器。

# DefaultServlet

DefaultServlet 是 Tomcat 提供的默认的【兜底】的 Servlet,相当于它的 <urlpattern> 配置的是 /

DefaultServlet 中的 doPost 方法交由 doGet 方法进行处理。而 doGet 方法兜兜转转最后执行了一个 copy 方法,在 copy 方法中把找到静态资源文件,将其内容读出并写入 Response 对象的输出流,这样你的浏览器就看到静态数据了。

# 配置引起的 bug

结合我们自定义的 Servlet,和 JSPServlet、DefaultServlet,会让初学者造成一些不知所以的 bug :

# 情况一

将 HelloServlet 的 urlpattern 配置为 *.do,此时项目中的各个 Servlet 的分工如下:

  1. Tomcat 默认的 JSPServlet 负责响应 .jsp 请求。

  2. 我们自己的 HelloServlet 负责响应 .do 请求。

  3. 凡是没有 Servlet 响应的请求,都被【漏给】DefaultServlet 处理。

# 情况二

将 HelloServlet 的 urlpattern 配置为 /,此时项目中的各个 Servlet 的分工如下:

  1. Tomcat 默认 JSPServlet 负责响应 .jsp 请求。

  2. HelloServlet 负责响应所有的其它请求。

注意,你的 HelloServlet 就替代了 Tomcat 默认的 DefaultServlet 在做【兜底】的工作。

此时,你就无法访问静态资源!除非你的 HelloServlet 实现了 Tomcat 的 DefaultServlet 同样的功能。

# 情况三

将 HelloServlet 的 urlpattern 配置为 /*,此时项目中的各个 Servlet 的分工如下:

所有的请求都由你的 HelloServlet 处理

/* 是路径匹配,它的优先级高于 .jsp。所以当用户输入 xxx.jsp 时,是 HelloServlet【先】响应了这个请求,轮不到 Tomcat 的 JSPServlet 来响应这个 .jsp 请求。

此时,在静态资源无法访问的基础上,jsp 也无法访问了。

# 总结

逻辑上,用户所访问的资源分为 3 种:

  • Servlet

  • JSP

  • 静态资源(例如:html、css、js、png 等)

Tomcat 判断请求的资源的类型,也是按照上述顺序在判断:先判断是否是请求 Servlet(.do 请求),再判断是否是 JSP(.jsp 请求)。要是都不是,那么就是静态资源(.png 等请求)。

通过配置,进行合理安排,我们应该/预期达到如下效果:

  • 对于 Servlet 的请求的处理,由我们自定义的 Servlet 进行处理。

  • 对于 JSP 的请求的处理,由 Tomcat 中的 JspServlet 自动处理。

  • 对于 静态资源 的处理,由 Tomcat 中的 DefaultServlet 自动处理。

注意

从本质上来讲,DefaultServlet 并不是『专门』处理静态资源的工具。而是说,既不是由我们自定义的 Servlet 处理的资源,又不是由 JspServlet 处理的资源,最后统统都交由 DefaultServlet 处理。

DefaultServlet 作为『兜底』的 Servlet ,它的 url-pattern 是 / ,注意,并非 /*

毫无疑问,web.xml不需要 显示地配置 DefaultServlet(否则,它也就不会叫 Default Servlet 了)。

同样的道理,其实我们也从未在(也不需要在) web.xml 中显示地配置过 JspServlet 。

# 如何允许静态资源访问

当要访问静态资源时,可以在 web.xml 中明确指定什么样的请求(即对静态资源的请求)交由 DefaultServlet 进行处理(逻辑上,以下配置也可以省略不写,默认既是如此):

<servlet-mapping>
  <servlet-name>default</servlet-name> <!-- 在默认的配置中,DefaultSevlet 的 servelt-name 就是叫 default -->
  <url-pattern>*.html</url-pattern>
  <url-pattern>*.css</url-pattern>
  <url-pattern>*.js</url-pattern>
  <url-pattern>*.jpg</url-pattern>
  <url-pattern>*.png</url-pattern>
</servlet-mapping>

WARNING

  • WEB-INF 目录下内容不允许直接公共访问,所以静态资源通常是放到与 WEB-INF 同级的目录下面。
  • 如果是 SpringMVC 项目,对于静态资源的访问有其他的操作。

# 2. 过滤器(Filter)

# 基本概念

过滤器(Filter)是拦截 Request 请求的对象:在用户的请求访问资源前处理 ServletRequest 和 ServletResponse 。

Filter 相关的接口有:Filter、FilterConfig、FilterChain 。

Filter 的实现必须实现 Filter 接口。这个接口包含了 Filter 的3个生命周期方法:init()doFilter()destroy()

Servlet 容器(Tomcat)初始化Filter时,会触发 Filter 的 init() 方法,一般来说是在应用开始时(注意,不是第一次使用时)。这个方法只会被调用一次。

FilterConfig 对象由 Servlet 容器传入 init() 方法中。

当 Servlet 容器每次处理 Filter 相关的资源时,都会调用该 Filter 实例的 doFilter() 方法。就像容器调用 Serviet 的 service() 方法。

在 Filter 的 doFilter() 方法中,最后一行需要调用 FilterChain 中的 doChain() 方法。注意,FilterChain 是 doFilter() 方法的第三个参数。

一个 URL 资源可能被多个 Filter 关联(即一条 Filter 链),这时 Filter.doFilter() 的方法将触发 Filter 链中下一个 Filter。只有在 Filter 链中最后一个 Filter 里调用 doFilter() 方法,才会触发 Controller 中处理 URL 资源的方法。

如果在 Filter 的 doFilter() 方法中,因为故意(或无意)没有调用 FilterChain 的 doFilter() 方法,那么这个 Request 请求将终止,后面的处理就会中断。

注意,FilterChain 接口中,唯一的方法就是 doFilter() 方法,它和 Filter 接口中的 doFilter() 方法定义是不一样的。

Filter 接口中,最后一个方法是 destroy(),该方法在 Servlet 容器要销毁 Filter 时触发。

类似于 Servlet,Filter 也是单例。

# Filter 的配置

和 Servlet 的配置非常相似,Filter 的配置主要有三方面:

  • 确认哪些资源需要本 Filter 进行拦截处理。
  • 配置 Filter 的初始化参数和值,这些参数在 Filter 的 init() 方法中可以读取到。
  • 给 Filter 取一个名称(一般来说这个配置是不需要的)。在一些特殊的情况下,系统通过这个名字来识别Filter。
@WebFilter(filterName = "firstFilter",
            urlPatterns = {"/*"},
            initParams = {
                @WebInitParam(name="", value=""),
                @WebInitParam(name="", value="")
            })
public class FirstFilter implements Filter {
    ...
}
<filter>
    <filter-name>firstFilter</filter-name>
    <filter-class>com.hemiao.filter.FirstFilter</filter-class>
    <init-param>
        <param-name>author</param-name>
        <param-value>ben</param-value>
    </init-param>
    <init-param>
        <param-name>email</param-name>
        <param-value>hemiao3000@126.com</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>firstFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

# 3. 文件上传与下载

# 文件上传

为了能上传文件,必须将表单的 method 设置为 POST,并将 enctype 设置为 multipart/form-data

有两种实现文件上传的方式:

  • 底层使用 Apache Commons FileUpload 包

  • 底层使用 Servlet 3.1 内置的文件上传功能

无论是哪种方式,其使用方式都是一样的,将 file 类型的请求参数绑定到请求处理方法的特定类型的参数上:

  • CommonsMultipartFile(commons-fileupload)

  • MultipartFile(Servlet 3.1)

# Web 3.0 的文件上传

普通的表单(form)元素无法直接上传文件,必须通过“特殊处理”。

对上传文件功能而言,有些特殊的地方:

  • form 表单内,要添加控件 <input type="file" name="myfile">
  • form 表单的提交方式必须是 post 方式
  • form 表单的内容格式要定义成 multipart/form-data 格式
<form action="..." method="post" enctype="multipart/form-data">
  ...
</form>

enctype="multipart/form-data" 表示表单元素中的 input 数据以二进制的方式发送到服务端。此时如果是普通的 input 数据,无法像之前一样从 request 中直接获得。

对于上传文件的的大小问题,惯例是:

  • 足够小的文件,先接收到内存中,最后写入磁盘。
  • 稍大的文件,写入磁盘临时文件,最后写入最终目的地。
  • 大型文件,禁止上传。

在 Web 3.0 之前 使用 commons-fileupload 库是最常见的上传办法。当 Servlet 的设计者意识到文件上传的重要性后,在 Web 3.0 中它就成了一项内置的特性。

Web 3.0 中的文件上传主要围绕着 MultipartConfig 注解和 Part 接口。

# @MultipartConfig 注解
fileSizeThreshold 可选属性
超过该值大小的文件,在上传过程中,将被写入磁盘临时文件,而不是保存在内存中。
maxFileSize 可选属性
每个上传文件的大小上限。
maxRequestSize 可选属性
一次请求(可能包含多个上传)的大小上限。
@WebServlet(urlPatterns="/hello.do")
@MultipartConfig(maxFileSize = 5*1024*1024)
public class HelloServlet extends HttpServlet  {
  ...
}
# Part 接口

在一个表单(Form)中,无论是否有文件上传控件,Servlet 3.0 都会将这些表单控件对应成代码中的一个 Part 对象。

通过 request 对象的 .getParts() 方法可以获得所有的这些 Part 对象。

Collection<Part> parts = request.getParts();

在一个或多个部分组成的请求中,每一个表单域(包括非文本域),都将被转换成一个 Part 。

普通文本域和文件上传域的区别在于,其 Part 对象的 .getContentType() 方法返回值的不同。对于普通文本域的 Part 对象而言,该方法返回 null 。

for (Part part : parts) {
    if (part.getContentType() == null) {
        System.out.println("普通文本域");
    }
    else {
        System.out.println("文件上传域");
    }
}

补充,如果是要获取普通文本域的值,其实直接使用正常 request.getParameter() 就行。

每一个 Part 分为“头”和“体”两部分。普通文本域只有头部,而文件上传域则有头有体。

普通文本域的头部形式为:

content-disposition:form-data; name="域名"

上传文本域的头部形式为:

content-type:内容类型
content-disposition:form-data; name="域名"; filename="文件名"

对我们而言,需要的是文本上传域中的 content-disposition 中的 filename 部分。

String header = part.getHeader("content-disposition"); 
// 内容为 form-data; name="域名"; filename="文件名"

通常会使用工具类,将 content-disposition 中的 filename 中的值截取出来。

private String getFileName(String header) {

    String[] arr = header.split("; ");

    String item = null;
    for (String cur : arr) {
        // System.out.println("debug: " + cur);
        if (cur.startsWith("filename=")) {
            item = cur;
            break;
        }
    }

    int start = item.indexOf('"')+1;
    int end = item.lastIndexOf('"');

    String filename = item.substring(start, end);

    // System.out.println(filename);

    return filename;
}

Part 对象直接提供了方法将上传文件的内容写入盘:

String savePath = request.getServletContext().getRealPath("/WEB-INF/uploadFile/");
String filePathName = savePath + File.separator + fileName; // 目标文件路径名
part.write(filePathName); // 把文件写到指定路径

Part的其它常用方法

getContentType() 方法
获得Part的内容类型。如果Part是普通文本,那么返回null。
该方法可以用于区别是普通文本域,还是文件上传域。
getHeader() 方法
该方法用于获取指定的标头的值。
对于上传文本域的 Part,该参数有 content-typecontent-disposition
对于普通文本域,则只有 content-disposition 一种。
getName() 方法
无论是普通文本域Part,还是文件上传域Part,都是获得域名值。
write() 方法
将上传文件写入磁盘中。
delete() 方法
手动删除临时文件
getInputStream() 方法
以InputStream形式返回上传文件的内容。

# 利用 commons-fileupload 文件上传

利用 commons-fileupload 文件上传需要利用引入 commons-fileupload 包(它依赖于 commons-io 包)

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

作为 Servlet 内置上传功能之前的【准标准】,Servlet 在引入内置上传功能时借鉴了 commons-fileupload 的实现方式。因此,在了解 Servlet 内置上传功能之后,再回头看 commons-fileupload 文件上传时,你会发现它们的基本逻辑/大道理时一样的,只不过 commons-fileupload 的实现会罗嗦一些

在 Servlet 内置的上传功能中,从 request 中获得的是一个 Part[],其中的每一个 Part 对象对应着表单中的一个表单域(Form Field)。而 commons-fileupload 中我们从 request 中获得的是一个 List<FileItem>,commons-fileupload 中使用 FileItem 来对应每一个表单域,起到和 Part 一样的作用。

commons-fileupload 的罗嗦体现在以下几个方面:

  • commons-fileupload 不能对 Servlet 使用注解,因此相关的上传配置需要通过编码实现。
  • commons-fileupload 不能使用 request.getParameter()

为了能够从 request 中获得 List<FileItem>,你需要两个对象:

// 创建上传所需要的两个对象
DiskFileItemFactory factory = new DiskFileItemFactory();  // 磁盘文件对象
ServletFileUpload sfu = new ServletFileUpload(factory);   // 文件上传对象

如果不做出设置,那么相关设置则采用默认值。

// 设置上传过程中所收到的数据是【存内存】还是【存磁盘】的阈值
factory.setSizeThreshold(100 * 1024); 
// 设置磁盘临时文件的保存目录
factory.setRepository(new File("D:/upload"));

// 设置解析文件上传中的文件名的编码格式,解决上传文件名中文乱码问题
sfu.setHeaderEncoding("utf-8");
// 限制单个文件的大小
sfu.setFileSizeMax(10*1024);
// 限制上传的总文件大小
sfu.setSizeMax(100*1024);

在创建文件上传对象(并作出相应设置)之后,我们可以通过它从 request 中获取我们所需要的 List<FileItem>

List<FileItem> list = sfu.parseRequest(request);

FileItem 自带了方法,可以判断当前的 FileItem 对应的是页面上的普通文本域,还是文件上传域:

for (FileItem item : list) {
    if (item.isFormField()) {
        System.out.println("普通文本域");
    }
    else {    
        System.out.println("文件上传域");
    }
}

由于 commons-fileupload 中无法使用 request.getParameter(),因此,为了获得普通文本域中的数据,需要使用 FileItem 自己的方法:

for (FileItem item : list) {
    if (item.isFormField()) {
        String fieldName = item.getFieldName();         // 例如:username / password
        String fieldValue = item.getString("UTF-8");    // 例如,tom / 123456
        System.out.println(fieldName + ": " + fieldValue);
    }
    else {    
        System.out.println("文件上传域");
    }
}

由于 commons-fileupload 引用了 commons-io,所以,将上传的文件内容写入磁盘倒是十分简单:

for (FileItem item : list) {
    if (item.isFormField()) {
        ...
    }
    else {    
        System.out.println("文件上传域");

        // 创建输出文件
        String name = item.getName();// 获取上传文件的名字
        String outPath = "D:/upload/" + name;
        FileOutputStream output = new FileOutputStream(new File(outPath));

        // 获得上传文件字节流
        InputStream input = item.getInputStream(); 

        // 使用 IOUtils 工具将输入流中的数据写入到输出流。
        IOUtils.copy(input, output);

        System.out.println("上传成功!保存的路径为:" + outPath);
        input.close();      // 关闭输入流
        output.close();     // 关闭输出流
        item.delete();      // 删除处理文件上传时生成的临时文件
    }
}

# 文件下载

内容类型 文件扩展名 描述
text/plain txt 文本文件(包括但不仅包括txt)
application/msword doc Microsoft Word
application/pdf pdf Adobe Acrobat
application/zip zip winzip
audio/mpeg mp3 mp3 音频文件
image/gif gif COMPUSERVE GIF 图像
image/jpeg jpeg jpg JPEG 图像
image/png png PNG 图像

详细 MIME 参见 网址 (opens new window)

相对于上传而言,下载文件较为简单,只需要完成两步:

  1. 设置响应的内容类型。
  2. 添加一个 content-disposition 到响应标头(addHeader()方法),其值为:attachment; filename=文件名
  3. 通过 resp 对象获得输出流,将文件内容发送至客户端。
resp.setContentType("text/plain");      // step 1
resp.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode("D:/note.txt", "UTF-8")); // step 2

InputStream is = new FileInputStream(new File("D:/note.txt"));
OutputStream out = resp.getOutputStream();

byte[] buffer = new byte[1024];
int n = 0;
while ((n = is.read(buffer)) > 0) {
  out.write(buffer, 0, n);            // step 3
}

is.close();
out.close();
System.out.println("下载成功");

# 4. Cookies

Cookie 本质上是一个小文件,由浏览器创建/管理,保存在浏览器本地,即用户自己的电脑上。

当你访问一个网站/网址时,浏览器会“帮”你将这个文件的内容发送至服务端(Tomcat)。这个小文件的内容都是“名值(name-value)对”。只有浏览器本地有这个网站/网址的相关 Cookie(小文件),浏览器『一定』会把它的内容“帮”你发送到服务端。这个过程无需程序员参与,不受程序员的控制。

浏览器“帮”你发送的 Cookie,可能不止一个。服务端获得浏览器发送来的所有 Cookie 的方法是通过 request 对象的 getCookies()

Cookie(小文件)是由浏览器在本地创建,但是,它是由服务端“通知/要求”浏览器去创建时,才会创建的。

浏览器通常支持每个网站 20 个 cookies 。

注意,cookie 中不能直接存放中文,所以需要做相应的处理。常见处理办法是使用 URLEncoder 和 URLDecoder 将中文字符串编码/解码成URL格式编码字符串。

可以通过传递 name 和 value 两个参数给Cookie类的构造函数来创建一个 cookie。在创建完 cookie 之后,可以设置它的 maxAge 属性,这个属性决定了cookie 何时过期(单位为秒)。

要将 cookie 发送到浏览器,需要调用 HttpServletResponse 的 add() 方法。

服务器若要读取浏览器提交的 cookie,可以通过 HttpServletRequest 接口的 getCookie() 方法,该方法返回一个 Cookie 数组,若没有 cookies 则返回 null 。你需要遍历整个数组来查询某个特定名称的 cookie 。

注意,并没有一个直接的方法来删除一个 cookie,你只能创建一个同名的 cookie,并将 maxAge 设置为 0,并添加到 HttpServletResponse 中来“覆盖”原来的那个 cookie 。

Cookie 最大的问题在于用户可以通过设置禁用浏览器的 cookie 功能。

# 5. 监听器(Listener)

为了能够在 Servlet/JSP 应用程序中进行事件驱动编程(Event-Driven Programming),Servlet API 提供了一整套事件类和监听器接口。所以事件类均源自 java.util.Event,并且监听器在以下三个不同级别中均可使用:ServeletContext、HttpSession 及 ServletRequest 。

# 监听器接口和注册

创建监听器时,只要创建一个实现相关接口的 Java 类即可。在 Servlet 3.0 中,监听器有 2 种方法,以便 Servlet 容器能够认出来。

  1. 使用 @WebListener 注解

    @WebListener
    public class 自定义监听器 implements 监听器接口 {
    
    }
    
  2. 在部署描述符(web.xml)使用一个 <listener> 元素:

    <listener>
        <listener-class>自定义监听器</listener-class>
    </listener>
    

在应用程序中可以想要多少个监听器就可以有多少个监听器。注意,对监听器方法的调用时同步进行的。

# Servelt Context 监听器

在 ServletContext 级别上有 2 个监听器接口:

  • ServletContextListener
  • ServletContextAttributeListener

# ServletContextListener

ServletContextListener 会对 ServletCotnext 的初始化(init)和解构(destroy)做出响应。ServletContext 被初始化时,Servlet 容器会在所有已注册的 ServletContextListner 中调用 contextInitialized 方法。

void contextDestroyed(ServletContextEvent event);

当 ServletContext 要被解构和销毁时,Servlet 容器会在所有已注册的 ServletContextListener 中调用 contextDestroyed 方法。

void contextDestroyed(ServletContextEvent event);

contextInitialized 和 contextDestroyed 都会收到一个来自 Servlet 容器的 ServletContextEvent 。ServletContextEvent 是 java.util.EventObject 的子类,它定义了一个 getServletContext 方法,用以返回 ServletContext :

ServletContext getServletContext();

# ServletContextAttributeListener

每当 ServletContext 中添加、删除或替换了某个属性时,ServletContextAttributeListener 的事件都会收到通知。以下就是在这个监听器接口中定义的三个方法:

void attributeAdded(ServletContextAttributeEvent event);
void attributeRemoved(ServletContextAttributeEvent event);
void attributeReplaced(ServletContextAttributeEvent event);
  • 每当 ServletContext 中添加了某个属性时,Servlet 容器就会调用 attributeAdded 方法;
  • 每当 ServletContext 中移除了某个属性时,Servlet 容器就会调用 attributeRemoved 方法;
  • 每当 ServletContext 被新的代替时,Servlet 容器就会调用 attributeReplaced 方法。

所有的监听器方法都会收到一个 ServletContextAttributeEvent 实例,从这个参数中你可以获取属性名称(getName)和属性值(getValue)

# Session 监听器

与 HttpSession 有关的监听器有 4 个,我们常见的 2 个是:HttpSessionListenerHttpSessionAttributeListener

# HttpSessionListener

当有 HttpSession 被创建或销毁时,Servlet 容器就会调用所有已注册的 HttpSessionListener。HttpSessionListener 中定义的 2 个方法是:

void sessionCreated(HttpSessionEvent event);
void sessionDestroyed(HttpSessionEvent event);

这 2 个方法都会收到一个 HttpSessionEvent 实例,我们可以调用这个参数的 getSession 方法来获得所创建或销毁的 HttpSession 对象。

# HttpSessionAttributeListener

HttpSessionAttributeListener 就像 ServletContextAttributeListener 一样,只不过当 HttpSession 中有添加、删除或替换属性的时候它才会调用。它定义的方法有:

void attributeAdded(HttpSessionBindingEvent event);
void attributeRemoved(HttpSessionBindingEvent event);
void attributeReplaced(HttpSessionBindingEvent event);
  • 当 HttpSession 中添加某个属性时,由 Servlet 容器调用 attributeAdded 方法;
  • 当 HttpSession 中删除某个属性时,由 Servlet 容器调用 attributeRemoved 方法;
  • 当 HttpSession 属性被新属性代替时,由 Servlet 容器调用 attributeReplaced 方法。

所有的监听器方法都会收到一个 HttpSessionBindingEvent 实例,从这个参数中,你可以获得响应的 HttpSession 对象和属性名以及属性值。

# ServletRequest 监听器

在 ServletRequest 级别上有 3 个监听器接口,我们常见的有 2 个:ServletRequestListenerServletRequestAttributeListener

# ServletRequestListener

ServletRequestListener 对 ServletRequest 的创建和销毁做出响应。在 Servlet 容器中时通过池(pool)来重用 ServletRequest 的,“创建” ServletRequest 花费的事件相当于从池中获取一个 ServletRequest 对象的事件,销毁它的时间则相当于将 ServletRequest 重新放回 pool 的时间开销。

ServletRequestListener 接口定义了 2 个方法:

void requestInitialized(ServletRequestEvent event);
void requestDestroyed(ServletRequestEvent event);

创建(本质上是从池中取出)ServletRequest 时会调用 requestInitialized 方法,ServletRequest 被销毁(本质上是放回池中)时会调用 requestDestroyed 方法。这 2 个方法都会收到一个 ServletRequestEvent,通过调用 event 的 getServletRequest 方法,可以从中获取到相应的 ServletRequest 实例

ServletRequest getServletRequest()

另外,ServletRequestEvent 接口还定义了返回 ServletContext 的 getServletContext 方法,方法签名如下:

ServletContext getServletContext()

# ServletRequestAttributeListener

当在 ServletRequest 中添加、删除或者替换某个属性时,会调用 ServletRequestAttributeListener 。ServletRequestAttributeListener 接口中定义了 3 个方法:

void attributeAdded(ServletRequestAttributeEvent event)
void attributeRemoved(ServletRequestAttributeEvent event)
void attributeReplaced(ServletRequestAttributeEvent event)

所有方法都会收到一个 ServletRequestAttributeEvent 实例。通过参数 event 的 getName 和 getValue 方法你可以获得属性名和属性值。