过滤器和监听器 

过滤器

过滤器是什么?

  • 是向Web应用程序的请求和响应添加功能的Web服务组件
  • 过滤器可以统一的集中处理请求和响应
  • 使用过滤器技术实现对请求数据的过滤
  • 过滤器用于拦截传入的请求和传出的响应
  • 监视、修改或以某种方式处理正在客户端和服务器之间交换的数据流

使用过滤器封装公共任务具有如下优点:模块化;声明式;可重用;透明性

过滤器的常见用途

利用过滤器特殊的执行时机,可以实现 Web 应用程序中的预处理和后期处理逻辑。如:日志记录;改善性能;安全与会话管理;敏感字过滤。

Servlet体系详解_下篇_java

过滤器的使用步骤

1、建立实现Filter接口的类(javax.servlet.Filter)

2、实现过滤行为:doFilter ( )

3、在web. xmI中配置过滤器



<filter>
<filter-name>过滤器名</filter-name>
<filter-class>过滤器的完全限定名</filter-class>
</filter>

<filter-mapping>
<filter-name>过滤器名<filter-name>
<url-pattern>过滤器映射的Web资源</url-pattern>
</filter-mapping>
<!--
url-pattern指的是我过滤谁
完全匹配:/index.jsp
目录匹配:/admin/*
扩展名匹配:*.do
全部匹配:/*
-->


注:Filter的启动会比Servlet早。

过滤器的生命周期

  1. 实例化
  2. 初始化
  3. 过滤doFiIter( )
  4. 销毁destroy( )

Filter接口

javax.servlet.Filter接口定义了过滤器需要实现的方法。

  • void  init( FilterConfig  filterConfig):web容器调用该方法实现过滤器的初始化
  • void  doFilter(ServletRequest request,ServletResponse response,FilterChain chain ):当客户端请求资源时,Web容器会调用与资源对应的过滤器的doFilter( )方法。在该方法中,可以对请求和响应进行处理,实现过滤器的功能。
  • void  destroy( ):web容器销毁过滤器时调用该方法,可用来释放过滤器所用的资源

FilterConfig接口

在过滤器初始化过程中获取配置信息:

  • String  getFilterName():返回部署描述符中定义的过滤器的名称
  • Enumeration<String> getInitParameterNames():返回 <filter> 元素中定义的 init 参数的名称的字符串枚举
  • String  getInitParameter (String name):获取web.xml中设置的以name命名的初始化参数值
  • ServletContext  getServletContext():返回过滤器所属的Web上下文对象引用

配置和读取初始化参数



<web-app>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>javaeedemo.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
……
</web-app>



public class CharacterEncodingFilter implements Filter {
private String charset = null;

……

public void init(FilterConfig arg0) throws ServletException {
String initParam = arg0.getInitParameter("charset");

//读取指定名称的参数

if (initParam != null && (initParam = initParam.trim()).length() != 0 ) {
log.info("将CharacterEncodingFilter的charset设置为" + initParam);
charset = initParam;

}
}
……
}


过滤器链

  • 请求的URL与多个过滤器的映射范围相吻合时,这些过滤器会依次对请求进行过滤,形成过滤器链
  • 过滤器链的运行建立在嵌套调用的基础上
  • 可以根据业务需要,通过映射配置将过滤器链接组合起来,以提供更加全面的辅助功能

Servlet体系详解_下篇_监听器_02

过滤器链中各过滤器之间是嵌套调用关系。

调用FilterChain对象的doFilter( )方法会导致链中的下一个过滤器被调用。如果正在执行的是链中最后一个过滤器,则目标资源将被调用。

过滤器链式排列的顺序由web.xml描述信息中<filter-mapping>元素的顺序决定,即先定义先执行。

Servlet体系详解_下篇_ide_03

Filter高级开发

在filter中可以得到代表用户请求和响应的request、response对象,因此在编程中可以使用Decorator(装饰器)模式对request、response对象进行包装,再把包装对象传给目标资源,从而实现一些特殊需求。

1、Decorator设计模式介绍

当某个对象的方法不适应业务需求时,通常有2种方式可以对方法进行增强:

  • 编写子类,覆盖需增强的方法。
  • 使用Decorator设计模式对方法进行增强。

装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象

在实际应用中遇到需增强对象的方法时,到底选用哪种方式比较好呢?这个没有具体的定式,只能是根据具体的需求来采用具体的方式,不过有一种情况下,必须使用Decorator设计模式:即被增强的对象,开发人员只能得到它的对象,无法得到它的class文件。比如request、response对象,开发人员之所以在servlet中能通过sun公司定义的HttpServletRequest\response接口去操作这些对象,是因为Tomcat服务器厂商编写了request、response接口的实现类。web服务器在调用servlet时,会用这些接口的实现类创建出对象,然后传递给servlet程序。此种情况下,由于开发人员根本不知道服务器厂商编写的request、response接口的实现类是哪个?在程序中只能拿到服务器厂商提供的对象,因此就只能采用Decorator设计模式对这些对象进行增强。

Decorator设计模式的实现

  1. 首先看需要被增强对象继承了什么接口或父类,编写一个类也去继承这些接口或父类。
  2. 在类中定义一个变量,变量类型即需增强对象的类型。
  3. 在类中定义一个构造函数,接收需增强的对象。
  4. 覆盖需增强的方法,编写增强的代码。

2、使用Decorator设计模式增强request对象

Servlet API 中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper 类实现了request 接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request 对象的对应方法,以避免用户在对request对象进行增强时需要实现request接口中的所有方法。

(1) 使用Decorator模式包装request对象解决get和post请求方式下的中文乱码问题

编写一个用于处理中文乱码的过滤器CharacterEncodingFilter,代码如下:



import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName: CharacterEncodingFilter
* @Description: 此过滤器用来解决解决get、post请求方式下的中文乱码问题
*
*/
public class CharacterEncodingFilter implements Filter {

private FilterConfig filterConfig = null;
//设置默认的字符编码
private String defaultCharset = "UTF-8";

public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//得到在web.xml中配置的字符编码
String charset = filterConfig.getInitParameter("charset");
if(charset==null){
charset = defaultCharset;
}
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
response.setContentType("text/html;charset="+charset);

MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(request);
chain.doFilter(requestWrapper, response);
}

public void init(FilterConfig filterConfig) throws ServletException {
//得到过滤器的初始化配置信息
this.filterConfig = filterConfig;
}

public void destroy() {

}
}

/**
* @ClassName: MyCharacterEncodingRequest
* @Description: Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,
* (HttpServletRequestWrapper类实现了request接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request对象的对应方法)
* 以避免用户在对request对象进行增强时需要实现request接口中的所有方法。
* 所以当需要增强request对象时,只需要写一个类继承HttpServletRequestWrapper类,然后在重写需要增强的方法即可
* 1.实现与被增强对象相同的接口
2、定义一个变量记住被增强对象
3、定义一个构造函数,接收被增强对象
4、覆盖需要增强的方法
5、对于不想增强的方法,直接调用被增强对象(目标对象)的方法
*/
class MyCharacterEncodingRequest extends HttpServletRequestWrapper{
//定义一个变量记住被增强对象(request对象是需要被增强的对象)
private HttpServletRequest request;
//定义一个构造函数,接收被增强对象
public MyCharacterEncodingRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 覆盖需要增强的getParameter方法
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
try{
//获取参数的值
String value= this.request.getParameter(name);
if(value==null){
return null;
}
//如果不是以get方式提交数据的,就直接返回获取到的值
if(!this.request.getMethod().equalsIgnoreCase("get")) {
return value;
}else{
//如果是以get方式提交数据的,就对获取到的值进行转码处理
value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
return value;
}
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}


在web.xml文件中配置CharacterEncodingFilter



<!--配置字符过滤器,解决get、post请求方式下的中文乱码问题-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>me.gacl.web.filter.CharacterEncodingFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


(2) 使用Decorator模式包装request对象实现html标签转义功能

编写一个html转义过滤器,代码如下:



import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName: HtmlFilter
* @Description: html转义过滤器
*
*/
public class HtmlFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;

MyHtmlRequest myrequest = new MyHtmlRequest(request);
chain.doFilter(myrequest, response);

}


public void destroy() {

}


public void init(FilterConfig filterConfig) throws ServletException {

}
}

/**
* @ClassName: MyHtmlRequest
* @Description: 使用Decorator模式包装request对象,实现html标签转义功能
*
*/
class MyHtmlRequest extends HttpServletRequestWrapper {

private HttpServletRequest request;

public MyHtmlRequest(HttpServletRequest request) {
super(request);
this.request = request;
}

/* 覆盖需要增强的getParameter方法
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
String value = this.request.getParameter(name);
if (value == null) {
return null;
}
//调用filter转义value中的html标签
return filter(value);
}

/**
* @Method: filter
* @Description: 过滤内容中的html标签
* @Anthor:孤傲苍狼
* @param message
* @return
*/
public String filter(String message) {
if (message == null){
return null;
}
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return result.toString();
}
}


在web.xml文件中配置HtmlFilter



<!--配置Html过滤器,转义内容中的html标签-->
<filter>
<filter-name>HtmlFilter</filter-name>
<filter-class>me.gacl.web.filter.HtmlFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>HtmlFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


(3) 使用Decorator模式包装request对象实现敏感字符过滤功能

编写一个敏感字符过滤器,代码如下:



import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName: DirtyFilter
* @Description: 敏感词过滤器
*
*/
public class DirtyFilter implements Filter {

private FilterConfig config = null;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.config = filterConfig;
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
DirtyRequest dirtyrequest = new DirtyRequest(request);

chain.doFilter(dirtyrequest, response);
}

@Override
public void destroy() {

}

/**
* @Method: getDirtyWords
* @Description: 获取敏感字符
*
* @return
*/
private List<String> getDirtyWords(){
List<String> dirtyWords = new ArrayList<String>();
String dirtyWordPath = config.getInitParameter("dirtyWord");
InputStream inputStream = config.getServletContext().getResourceAsStream(dirtyWordPath);
InputStreamReader is = null;
try {
is = new InputStreamReader(inputStream,"UTF-8");
} catch (UnsupportedEncodingException e2) {
e2.printStackTrace();
}
BufferedReader reader = new BufferedReader(is);
String line;
try {
while ((line = reader.readLine())!= null) {//如果 line为空说明读完了
dirtyWords.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return dirtyWords;
}

/**
* @ClassName: DirtyRequest
* @Description: 使用Decorator模式包装request对象,实现敏感字符过滤功能
*
*/
class DirtyRequest extends HttpServletRequestWrapper{

private List<String> dirtyWords = getDirtyWords();
private HttpServletRequest request;
public DirtyRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 重写getParameter方法,实现对敏感字符的过滤
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {

String value = this.request.getParameter(name);
if(value==null){
return null;
}

for(String dirtyWord : dirtyWords){
if(value.contains(dirtyWord)){
System.out.println("内容中包含敏感词:"+dirtyWord+",将会被替换成****");
//替换敏感字符
value = value.replace(dirtyWord, "****");
}
}
return value;
}
}
}


在web.xml文件中配置DirtyFilter:



<!--配置敏感字符过滤器-->
<filter>
<filter-name>DirtyFilter</filter-name>
<filter-class>me.gacl.web.filter.DirtyFilter</filter-class>
<!-- 配置要过滤的敏感字符文件 -->
<init-param>
<param-name>dirtyWord</param-name>
<param-value>/WEB-INF/DirtyWord.txt</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>DirtyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


当用户填写的内容包含一些敏感字符时,在DirtyFilter过滤器中就会将这些敏感字符替换掉。

我们如果将上述的CharacterEncodingFilter、HtmlFilter、DirtyFilter这三个过滤器联合起来使用,那么就相当于是把request对象包装了3次,request对象的getParameter方法经过3次重写,使得getParameter方法的功能大大增强,可以同时解决中文乱码,html标签转义,敏感字符过滤这些需求。

在实际开发中完全可以将上述的三个过滤器合并成一个,让合并后的过滤器具有解决中文乱码,html标签转义,敏感字符过滤这些功能,例如:



import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName: AdvancedFilter
* @Description: 这个过滤器是用来解决中文乱码,转义内容中的html标签,过滤内容中的敏感字符的
*
*/
public class AdvancedFilter implements Filter {

private FilterConfig filterConfig = null;
//设置默认的字符编码
private String defaultCharset = "UTF-8";

@Override
public void init(FilterConfig filterConfig) throws ServletException {
//得到过滤器的初始化配置信息
this.filterConfig = filterConfig;
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//得到在web.xml中配置的字符编码
String charset = filterConfig.getInitParameter("charset");
if(charset==null){
charset = defaultCharset;
}
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
response.setContentType("text/html;charset="+charset);

AdvancedRequest requestWrapper = new AdvancedRequest(request);
chain.doFilter(requestWrapper, response);
}

@Override
public void destroy() {

}

class AdvancedRequest extends HttpServletRequestWrapper{

private List<String> dirtyWords = getDirtyWords();

//定义一个变量记住被增强对象(request对象是需要被增强的对象)
private HttpServletRequest request;
//定义一个构造函数,接收被增强对象
public AdvancedRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 覆盖需要增强的getParameter方法
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {
try{
//获取参数的值
String value= this.request.getParameter(name);
if(value==null){
return null;
}
//如果不是以get方式提交数据的,就直接返回获取到的值
if(!this.request.getMethod().equalsIgnoreCase("get")) {
//调用filter转义value中的html标签
value= filter(value);
}else{
//如果是以get方式提交数据的,就对获取到的值进行转码处理
value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
//调用filter转义value中的html标签
value= filter(value);
}

for(String dirtyWord : dirtyWords){
if(value.contains(dirtyWord)){
System.out.println("内容中包含敏感词:"+dirtyWord+",将会被替换成****");
//替换敏感字符
value = value.replace(dirtyWord, "****");
}
}
return value;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}

/**
* @Method: filter
* @Description: 过滤内容中的html标签
* @param value
* @return
*/
public String filter(String value) {
if (value == null){
return null;
}
char content[] = new char[value.length()];
value.getChars(0, value.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}

/**
* @Method: getDirtyWords
* @Description: 获取敏感字符
*
* @return
*/
private List<String> getDirtyWords(){
List<String> dirtyWords = new ArrayList<String>();
String dirtyWordPath = filterConfig.getInitParameter("dirtyWord");
InputStream inputStream = filterConfig.getServletContext().getResourceAsStream(dirtyWordPath);
InputStreamReader is = null;
try {
is = new InputStreamReader(inputStream,defaultCharset);
} catch (UnsupportedEncodingException e2) {
e2.printStackTrace();
}
BufferedReader reader = new BufferedReader(is);
String line;
try {
while ((line = reader.readLine())!= null) {//如果 line为空说明读完了
dirtyWords.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return dirtyWords;
}
}


在web.xml文件中配置AdvancedFilter



<filter>
<filter-name>AdvancedFilter</filter-name>
<filter-class>me.gacl.web.filter.AdvancedFilter</filter-class>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>dirtyWord</param-name>
<param-value>/WEB-INF/DirtyWord.txt</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>AdvancedFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


AdvancedFilter过滤器同时具有解决中文乱码,转义内容中的html标签,过滤内容中的敏感字符这些功能。

3、使用Decorator设计模式增强response对象

Servlet  API 中提供了response对象的Decorator设计模式的默认实现类HttpServletResponseWrapper ,HttpServletResponseWrapper类实现了response接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 response对象的对应方法,以避免用户在对response对象进行增强时需要实现response接口中的所有方法。

(1) response增强案例——压缩响应正文内容

应用HttpServletResponseWrapper对象,压缩响应正文内容。

具体思路:通过filter向目标页面传递一个自定义的response对象。在自定义的response对象中,重写getOutputStream方法和getWriter方法,使目标资源调用此方法输出页面内容时,获得的是我们自定义的ServletOutputStream对象。在我们自定义的ServletOuputStream对象中,重写write方法,使写出的数据写出到一个buffer中。当页面完成输出后,在filter中就可得到页面写出的数据,从而我们可以调用GzipOuputStream对数据进行压缩后再写出给浏览器,以此完成响应正文件压缩功能。

编写压缩过滤器,代码如下:



import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
* @ClassName: GzipFilter
* @Description: 压缩过滤器,将web应用中的文本都经过压缩后再输出到浏览器
*/
public class GzipFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;

BufferResponse myresponse = new BufferResponse(response);
chain.doFilter(request, myresponse);
//拿出缓存中的数据,压缩后再打给浏览器
byte out[] = myresponse.getBuffer();
System.out.println("原始大小:" + out.length);

ByteArrayOutputStream bout = new ByteArrayOutputStream();
//压缩输出流中的数据
GZIPOutputStream gout = new GZIPOutputStream(bout);
gout.write(out);
gout.close();

byte gzip[] = bout.toByteArray();
System.out.println("压缩后的大小:" + gzip.length);

response.setHeader("content-encoding", "gzip");
response.setContentLength(gzip.length);
response.getOutputStream().write(gzip);
}

public void destroy() {

}

public void init(FilterConfig filterConfig) throws ServletException {

}
}

class BufferResponse extends HttpServletResponseWrapper{

private ByteArrayOutputStream bout = new ByteArrayOutputStream();
private PrintWriter pw;
private HttpServletResponse response;
public BufferResponse(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new MyServletOutputStream(bout);
}
@Override
public PrintWriter getWriter() throws IOException {
pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
return pw;
}

public byte[] getBuffer(){
try{
if(pw!=null){
pw.close();
}
if(bout!=null){
bout.flush();
return bout.toByteArray();
}


return null;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}

class MyServletOutputStream extends ServletOutputStream{

private ByteArrayOutputStream bout;
public MyServletOutputStream(ByteArrayOutputStream bout){
this.bout = bout;
}

@Override
public void write(int b) throws IOException {
this.bout.write(b);
}
}


在web.xml中配置压缩过滤器



<filter>
<description>配置压缩过滤器</description>
<filter-name>GzipFilter</filter-name>
<filter-class>me.gacl.web.filter.GzipFilter</filter-class>
</filter>

<!--jsp文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<!-- 配置过滤器的拦截方式-->
<!-- 对于在Servlet中通过
request.getRequestDispatcher("jsp页面路径").forward(request, response)
方式访问的Jsp页面的要进行拦截 -->
<dispatcher>FORWARD</dispatcher>
<!--对于直接以URL方式访问的jsp页面进行拦截,过滤器的拦截方式默认就是 REQUEST-->
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<!--js文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<!--css文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
<!--html文件的输出的内容都经过压缩过滤器压缩后才输出 -->
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>


(2) response增强案例——缓存数据到内存

对于页面中很少更新的数据,例如商品分类,为避免每次都要从数据库查询分类数据,因此可把分类数据缓存在内存或文件中,以此来减轻数据库压力,提高系统响应速度。

编写缓存数据的过滤器,代码如下:



import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
* @ClassName: WebResourceCachedFilter
* @Description: Web资源缓存过滤器
*
*/
public class WebResourceCachedFilter implements Filter {
/**
* @Field: map
* 缓存Web资源的Map容器
*/
private Map<String,byte[]> map = new HashMap<String,byte[]>();

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//1.得到用户请求的uri
String uri = request.getRequestURI();
//2.看缓存中有没有uri对应的数据
byte b[] = map.get(uri);
//3.如果缓存中有,直接拿缓存的数据打给浏览器,程序返回
if(b!=null){
//根据字节数组和指定的字符编码构建字符串
String webResourceHtmlStr = new String(b,response.getCharacterEncoding());
System.out.println(webResourceHtmlStr);
response.getOutputStream().write(b);
return;
}
//4.如果缓存没有,让目标资源执行,并捕获目标资源的输出
BufferResponse myresponse = new BufferResponse(response);
chain.doFilter(request, myresponse);
//获取缓冲流中的内容的字节数组
byte out[] = myresponse.getBuffer();
//5.把资源的数据以用户请求的uri为关键字保存到缓存中
map.put(uri, out);
//6.把数据打给浏览器
response.getOutputStream().write(out);
}

@Override
public void destroy() {

}

class BufferResponse extends HttpServletResponseWrapper{
private ByteArrayOutputStream bout = new ByteArrayOutputStream();
//捕获输出的缓存
private PrintWriter pw;
private HttpServletResponse response;
public BufferResponse(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new MyServletOutputStream(bout);
}
@Override
public PrintWriter getWriter() throws IOException {
pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
return pw;
}

public byte[] getBuffer(){
try{
if(pw!=null){
pw.close();
}
return bout.toByteArray();
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}

class MyServletOutputStream extends ServletOutputStream{
private ByteArrayOutputStream bout;
public MyServletOutputStream(ByteArrayOutputStream bout){ //接收数据写到哪里
this.bout = bout;
}
@Override
public void write(int b) throws IOException {
bout.write(b);
}
}
}


在web.xml中配置Web资源缓存过滤器



<filter>
<description>Web资源缓存过滤器</description>
<filter-name>WebResourceCachedFilter</filter-name>
<filter-class>me.gacl.web.filter.WebResourceCachedFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>WebResourceCachedFilter</filter-name>
<!-- 映射需要缓存输出的JSP页面,这几个页面都只是单纯作为输入UI,不会有太多的变化,因此可以缓存输出 -->
<url-pattern>/login.jsp</url-pattern>
<url-pattern>/test.jsp</url-pattern>
<url-pattern>/test2.jsp</url-pattern>
</filter-mapping>


过滤器示例

1、统一全站字符编码

通过配置参数charset指明使用何种字符编码,以处理Html Form请求参数的中文问题。



import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName: CharacterEncodingFilter
* @Description: 此过滤器用来解决全站中文乱码问题
*
*/
public class CharacterEncodingFilter implements Filter {

private FilterConfig filterConfig = null;
//设置默认的字符编码
private String defaultCharset = "UTF-8";

public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String charset = filterConfig.getInitParameter("charset");
if(charset==null){
charset = defaultCharset;
}
request.setCharacterEncoding(charset);
response.setCharacterEncoding(charset);
response.setContentType("text/html;charset="+charset);

MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(request);
chain.doFilter(requestWrapper, response);
}

public void init(FilterConfig filterConfig) throws ServletException {
//得到过滤器的初始化配置信息
this.filterConfig = filterConfig;
}

public void destroy() {

}
}

/*
1.实现与被增强对象相同的接口
2、定义一个变量记住被增强对象
3、定义一个构造器,接收被增强对象
4、覆盖需要增强的方法
5、对于不想增强的方法,直接调用被增强对象(目标对象)的方法
*/

class MyCharacterEncodingRequest extends HttpServletRequestWrapper{

private HttpServletRequest request;
public MyCharacterEncodingRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
/* 重写getParameter方法
* @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
*/
@Override
public String getParameter(String name) {

try{
//获取参数的值
String value= this.request.getParameter(name);
if(value==null){
return null;
}
//如果不是以get方式提交数据的,就直接返回获取到的值
if(!this.request.getMethod().equalsIgnoreCase("get")) {
return value;
}else{
//如果是以get方式提交数据的,就对获取到的值进行转码处理
value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
return value;
}
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}


web.xml文件中的配置如下:



<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>me.gacl.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


2、禁止浏览器缓存所有动态页面

有3 个HTTP 响应头字段都可以禁止浏览器缓存当前页面,它们在 Servlet 中的示例代码如下:



response.setDateHeader("Expires",-1);
response.setHeader("Cache-Control","no-cache");
response.setHeader("Pragma","no-cache");


并不是所有的浏览器都能完全支持上面的三个响应头,因此最好是同时使用上面的三个响应头。

  • Expires数据头:值为GMT时间值,为-1指浏览器不要缓存页面
  • Cache-Control响应头有两个常用值:
  • no-cache指浏览器不要缓存当前页面。
  • max-age:xxx指浏览器缓存页面xxx秒。



import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName: NoCacheFilter
* @Description: 禁止浏览器缓存所有动态页面
*
*/
public class NoCacheFilter implements Filter {


public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
//把ServletRequest强转成HttpServletRequest
HttpServletRequest request = (HttpServletRequest) req;
//把ServletResponse强转成HttpServletResponse
HttpServletResponse response = (HttpServletResponse) resp;
//禁止浏览器缓存所有动态页面
response.setDateHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");

chain.doFilter(request, response);
}

public void init(FilterConfig filterConfig) throws ServletException {

}

public void destroy() {

}
}


web.xml文件中的配置如下:



<filter>
<filter-name>NoCacheFilter</filter-name>
<filter-class>me.gacl.web.filter.NoCacheFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>NoCacheFilter</filter-name>
<!--只拦截Jsp请求-->
<servlet-name>*.jsp</servlet-name>
</filter-mapping>


3、控制浏览器缓存页面中的静态资源

有些动态页面中引用了一些图片或css文件以修饰页面效果,这些图片和css文件经常是不变化的,所以为减轻服务器的压力,可以使用filter控制浏览器缓存这些文件,以提升服务器的性能。



import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName: CacheFilter
* @Description: 控制缓存的filter
*
*/
public class CacheFilter implements Filter {

private FilterConfig filterConfig;

public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;

//1.获取用户想访问的资源
String uri = request.getRequestURI();

//2.得到用户想访问的资源的后缀名
String ext = uri.substring(uri.lastIndexOf(".")+1);

//得到资源需要缓存的时间
String time = filterConfig.getInitParameter(ext);
if(time!=null){
long t = Long.parseLong(time)*3600*1000;
//设置缓存
response.setDateHeader("expires", System.currentTimeMillis() + t);
}

chain.doFilter(request, response);

}

public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}

public void destroy() {

}
}


web.xml文件中的配置如下:



<!-- 配置缓存过滤器 -->
<filter>
<filter-name>CacheFilter</filter-name>
<filter-class>me.gacl.web.filter.CacheFilter</filter-class>
<!-- 配置要缓存的web资源以及缓存时间,以小时为单位 -->
<init-param>
<param-name>css</param-name>
<param-value>4</param-value>
</init-param>
<init-param>
<param-name>jpg</param-name>
<param-value>1</param-value>
</init-param>
<init-param>
<param-name>js</param-name>
<param-value>4</param-value>
</init-param>
<init-param>
<param-name>png</param-name>
<param-value>4</param-value>
</init-param>
</filter>
<!-- 配置要缓存的web资源的后缀-->
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>*.jpg</url-pattern>
</filter-mapping>

<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>

<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>*.png</url-pattern>
</filter-mapping>


4、实现用户自动登陆

思路是这样的:

  • 在用户登陆成功后,发送一个名称为user的cookie给客户端,cookie的值为用户名和md5加密后的密码。
  • 编写一个AutoLoginFilter,这个filter检查用户是否带有名称为user的cookie来,如果有,则调用dao查询cookie的用户名和密码是否和数据库匹配,匹配则向session中存入user对象(即用户登陆标记),以实现程序完成自动登陆。

核心代码如下:

处理用户登录的控制器:LoginServlet



import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.harvey.dao.UserDao;
import com.harvey.domain.User;
import com.harvey.util.WebUtils;

public class LoginServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

String username = request.getParameter("username");
String password = request.getParameter("password");

UserDao dao = new UserDao();
User user = dao.find(username, password);
if(user==null){
request.setAttribute("message", "用户名或密码不对!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
request.getSession().setAttribute("user", user);
//发送自动登陆cookie给客户端浏览器进行存储
sendAutoLoginCookie(request,response,user);
request.getRequestDispatcher("/index.jsp").forward(request, response);
}

/**
* @Method: sendAutoLoginCookie
* @Description: 发送自动登录cookie给客户端浏览器
*
* @param request
* @param response
* @param user
*/
private void sendAutoLoginCookie(HttpServletRequest request, HttpServletResponse response, User user) {
if (request.getParameter("logintime")!=null) {
int logintime = Integer.parseInt(request.getParameter("logintime"));
//创建cookie,cookie的名字是autologin,值是用户登录的用户名和密码,用户名和密码之间使用.进行分割,密码经过md5加密处理
Cookie cookie = new Cookie("autologin",user.getUsername() + "." + WebUtils.md5(user.getPassword()));
//设置cookie的有效期
cookie.setMaxAge(logintime);
//设置cookie的有效路径
cookie.setPath(request.getContextPath());
//将cookie写入到客户端浏览器
response.addCookie(cookie);
}
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

doGet(request, response);
}

}


处理用户自动登录的过滤器:AutoLoginFilter



import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.harvey.dao.UserDao;
import com.harvey.domain.User;
import com.harvey.util.WebUtils;

public class AutoLoginFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//如果已经登录了,就直接chain.doFilter(request, response)放行
if(request.getSession().getAttribute("user")!=null){
chain.doFilter(request, response);
return;
}

//1.得到用户带过来的authlogin的cookie
String value = null;
Cookie cookies[] = request.getCookies();
for(int i=0;cookies!=null && i<cookies.length;i++){
if(cookies[i].getName().equals("autologin")){
value = cookies[i].getValue();
}
}

//2.得到 cookie中的用户名和密码
if(value!=null){
String username = value.split("\\.")[0];
String password = value.split("\\.")[1];

//3.调用dao获取用户对应的密码
UserDao dao = new UserDao();
User user = dao.find(username);
String dbpassword = user.getPassword();

//4.检查用户带过来的md5的密码和数据库中的密码是否匹配,如匹配则自动登陆
if(password.equals(WebUtils.md5(dbpassword))){
request.getSession().setAttribute("user", user);
}
}

chain.doFilter(request, response);
}

public void destroy() {

}

public void init(FilterConfig filterConfig) throws ServletException {

}
}


如果想取消自动登录,那么可以在用户注销时删除自动登录cookie,核心代码如下:



import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CancelAutoLoginServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//移除存储在session中的user
request.getSession().removeAttribute("user");
//移除自动登录的cookie
removeAutoLoginCookie(request,response);
//注销用户后跳转到登录页面
request.getRequestDispatcher("/login.jsp").forward(request, response);
}

/**
* @Method: removeAutoLoginCookie
* @Description: 删除自动登录cookie,
* JavaWeb中删除cookie的方式就是新创建一个cookie,新创建的cookie与要删除的cookie同名,
* 设置新创建的cookie的cookie的有效期设置为0,有效路径与要删除的cookie的有效路径相同
*
* @param request
* @param response
*/
private void removeAutoLoginCookie(HttpServletRequest request, HttpServletResponse response) {
//创建一个名字为autologin的cookie
Cookie cookie = new Cookie("autologin","");
//将cookie的有效期设置为0,命令浏览器删除该cookie
cookie.setMaxAge(0);
//设置要删除的cookie的path
cookie.setPath(request.getContextPath());
response.addCookie(cookie);
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


监听器

监听器是什么?

  • Listener是Servlet的监听器
  • 监听客户端的请求和服务器端的操作
  • 通过实现Listener接口的类可以在特定事件(Event)发生时,自动激发一些操作
  • 监听器是Web应用程序事件模型的一部分
  • Web应用中的某些状态发生改变时会产生相应的事件
  • 监听器可以接收这些事件,以便在事件发生时做出相关处理

web的监听器主要是用来监听ServletContext、HttpSession、ServletRequest三个域对象。

常用的监听器接口

  • javax.servlet.ServletContextListener:在Servlet上下文对象初始化或销毁时得到通知
  • javax.servlet.ServletContextAttributeListener:在Servlet上下文中的属性列表发生变化时得到通知
  • javax.servlet.http.HttpSessionListener:在session创建后或者失效前得到通知
  • javax.servlet.http.HttpSessionActivationListener:绑定到session中,当session被钝化或者激活时得到通知
  • javax.servlet.http.HttpSessionAttributeListener:在session中的属性列表发生变化时得到通知
  • javax.servlet.http.HttpSessionBindingListener:在绑定session或从session中删除时会得到通知
  • javax.servlet.ServletRequestListener:在请求对象初始化时或者被销毁时得到通知
  • javax.servlet.ServletRequestAttributeListener:在请求对象中的属性列表发生变化时得到通知

监听器分类

根据三个域对象上的具体操作,将监听器划分为三类:

1、监听三个域对象的创建和销毁事件的监听器

  • javax.servlet.ServletContextListener:监听ServletContext对象的初始化与销毁事件
  • javax.servlet.http.HttpSessionListener:监听HttpSession对象的创建与销毁事件
  • javax.servlet.ServletRequestListener:监听ServletRequest对象的初始化与销毁事件,分别对应请求到达Web应用和离开Web应用

2、监听域对象中属性的增加、删除、替换事件的监听器

  • javax.servlet.ServletContextAttributeListener:监听application作用域中变量的增加、移除、替换事件
  • javax.servlet.http.HttpSessionAttributeListener:监听session作用域中变量的增加、移除、替换事件
  • javax.servlet.ServletRequestAttributeListener:监听request作用域中变量的增加、移除、替换事件

3、监听绑定到HttpSession域中的某个对象的状态的监听器,又称为感知型监听器

  • javax.servlet.http.HttpSessionBindingListener:其实现类的实例可以感知自己被绑定到session中或从session中解绑的事件
  • javax.servlet.http.HttpSessionActivationListener:其实现类的实例绑定到session中后,容器在钝化和活化session时将通知该实例

HttpSessionListener和HttpSessionAttributeListener可以对session作用域中的JavaBean实现统一的事件处理,感知型监听器则可以实现只对特定类型的JavaBean进行针对性的事件处理

ServletContextListener

监听ServletContext对象的初始化与销毁事件;

常用于资源初始化加载、初始化创建等工作;

包含如下方法签名:

  • void contextInitialized( ServletContextEvent sce ):对应ServletContext对象的初始化事件;
  • void contextDestroyed( ServletContextEvent sce):对应ServletContext对象的销毁事件

参数javax.servlet.ServletContextEvent是代表Web应用上下文变化的事件类型,包含一个方法:

  • public ServletContext getServletContext( ):获得正在创建或销毁的ServletContent实例

HttpSessionListener

在WEB应用中,当一个session被创建或者销毁时启用这个监听器;session是一次会话。

关键点:

  • sessionCreated(HttpSessionEvent event):客户端第一次和服务器交互时候触发
  • sessionDestroyed(HttpSessionEventevent):销毁会话的时候触发
  • 必须在web.xmI中配置监听器
  • 监听范围:设置一次就可以监听所有session

session钝化与活化:

  • session钝化的本质就是把内存中的session对象序列化到存储设备中
  • 活化就是通过反序列化将session对象从存储设备上进行恢复
  • 可能用到钝化与活化的情况
  • 服务器资源不足
  • 重启服务器
  • Web应用被重新加载
  • session作用域中的变量只有实现了java.io. Serializable接口才能被钝化存储,否则将在钝化时被丢弃
  • 实现了HttpSessionActivationListener接口的JavaBean实例被添加到session后,能够感知session的钝化与活化事件,可以针对自身需要进行必要处理

HttpSessionBindingListener监听器和HttpSessionListener监听器的区别:

  • HttpSessionBindingListener不需要在web.xml里配置,是一对一的监听。
  • HttpSessionListener必须在web.xmI中配置,可以监听所有session。

HttpSessionAttributeListener

监听session作用域中变量的增加、移除、替换事件。

包含如下方法签名:

  • void attributeAdded(HttpSessionBindingEvent event ):对应变量被添加到session作用域的事件
  • void attributeRemoved(HttpSessionBindingEvent event ):对应session作用域中的变量被移除的事件
  • void attributeReplaced(HttpSessionBindingEvent event ):对应session作用域中的变量被替换的事件

参数javax.servlet.http.HttpSessionBindingEvent是代表session作用域中属性变化的事件类型,包含如下方法:

  • public HttpSession getSession():返回当前变化所对应的HttpSession对象
  • public String getName():返回发生变化的变量的名称
  • public Object getValue():对应不同的操作,可分别返回新添加的变量值、被移除的变量值、被替换的变量的旧值

HttpSessionActivationListener类型的监听器同样无须在web.xml文件中声明,将其实例放入session作用域即可。

HttpSessionBindingListener

其实例可感知自己与session作用域绑定和解绑的事件。

包含如下方法签名:

  • void valueBound(HttpSessionBindingEvent event ):对应监听器实例绑定到session作用域的事件
  • void valueUnbound(HttpSessionBindingEvent event ):对应监听器实例从session作用域解绑的事件

当一个实现了该接口的对象,被捆绑到session中或从session中被解放的时候启用此监听。

关键点:

  • 创建类实现HttpSessionBindingListener接口
  • valueBound ( )
  • valueUnbound( )
  • 不需要在web.xmI中配置监听器
  • 监听范围:一对一

监听器示例

监听器在JavaWeb开发中用得比较多,下面说一下监听器(Listener)在开发中的常见应用。

1、统计当前在线人数

在JavaWeb应用开发中,有时候我们需要统计当前在线的用户数,此时就可以使用监听器技术来实现这个功能了。



import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
* @ClassName: OnLineCountListener
* @Description: 统计当前在线用户个数
*/
public class OnLineCountListener implements HttpSessionListener {

@Override
public void sessionCreated(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("onLineCount");
if (onLineCount == null) {
context.setAttribute("onLineCount", 1);
} else {
onLineCount++;
context.setAttribute("onLineCount", onLineCount);
}
}

@Override
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer onLineCount = (Integer) context.getAttribute("onLineCount");
if (onLineCount == null) {
context.setAttribute("onLineCount", 1);
} else {
onLineCount--;
context.setAttribute("onLineCount", onLineCount);
}
}
}


2、自定义Session扫描器

当一个Web应用创建的Session很多时,为了避免Session占用太多的内存,我们可以选择手动将这些内存中的session销毁,那么此时也可以借助监听器技术来实现。



import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
* @ClassName: SessionScanerListener
* @Description: 自定义session扫描器
*
*/
public class SessionScanerListener implements HttpSessionListener,ServletContextListener {

/**
* @Field: list
* 定义一个集合存储服务器创建的HttpSession
* LinkedList不是一个线程安全的集合
*/
/**
* private List<HttpSession> list = new LinkedList<HttpSession>();
* 这样写涉及到线程安全问题,SessionScanerListener对象在内存中只有一个
* sessionCreated可能会被多个人同时调用,
* 当有多个人并发访问站点时,服务器同时为这些并发访问的人创建session
* 那么sessionCreated方法在某一时刻内会被几个线程同时调用,几个线程并发调用sessionCreated方法
* sessionCreated方法的内部处理是往一个集合中添加创建好的session,那么在加session的时候就会
* 涉及到几个Session同时抢夺集合中一个位置的情况,所以往集合中添加session时,一定要保证集合是线程安全的才行
* 如何把一个集合做成线程安全的集合呢?
* 可以使用使用 Collections.synchronizedList(List<T> list)方法将不是线程安全的list集合包装线程安全的list集合
*/
//使用 Collections.synchronizedList(List<T> list)方法将LinkedList包装成一个线程安全的集合
private List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());
//定义一个对象,让这个对象充当一把锁,用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
private Object lock = new Object();

@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("session被创建了!!");
HttpSession session = se.getSession();

synchronized (lock){
/**
*将该操作加锁进行锁定,当有一个thread-1(线程1)在调用这段代码时,会先拿到lock这把锁,然后往集合中添加session,
*在添加session的这个过程中假设有另外一个thread-2(线程2)来访问了,thread-2可能是执行定时器任务的,
*当thread-2要调用run方法遍历list集合中的session时,结果发现遍历list集合中的session的那段代码被锁住了,
*而这把锁正在被往集合中添加session的那个thread-1占用着,因此thread-2只能等待thread-1操作完成之后才能够进行操作
*当thread-1添加完session之后,就把lock放开了,此时thread-2拿到lock,就可以执行遍历list集合中的session的那段代码了
*通过这把锁就保证了往集合中添加session和变量集合中的session这两步操作不能同时进行,必须按照先来后到的顺序来进行。
*/
list.add(session);
}
}

@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session被销毁了了!!");
}

/* Web应用启动时触发这个事件
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("web应用初始化");
//创建定时器
Timer timer = new Timer();
//每隔30秒就定时执行任务
timer.schedule(new MyTask(list,lock), 0, 1000*30);
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("web应用关闭");
}
}

/**
* @ClassName: MyTask
* @Description:定时器要定时执行的任务
*
*/
class MyTask extends TimerTask {

//存储HttpSession的list集合
private List<HttpSession> list;
//存储传递过来的锁
private Object lock;
public MyTask(List<HttpSession> list,Object lock){
this.list = list;
this.lock = lock;
}
/* run方法指明了任务要做的事情
* @see java.util.TimerTask#run()
*/
@Override
public void run() {
//将该操作加锁进行锁定
synchronized (lock) {
System.out.println("定时器执行!!");
ListIterator<HttpSession> it = list.listIterator();
/**
* 迭代list集合中的session,在迭代list集合中的session的过程中可能有别的用户来访问,
* 用户一访问,服务器就会为该用户创建一个session,此时就会调用sessionCreated往list集合中添加新的session,
* 然而定时器在定时执行扫描遍历list集合中的session时是无法知道正在遍历的list集合又添加的新的session进来了,
* 这样就导致了往list集合添加的新的session和遍历list集合中的session这两个操作无法达到同步
* 那么解决的办法就是把"list.add(session)和while(it.hasNext()){//迭代list集合}"这两段代码做成同步,
* 保证当有一个线程在访问"list.add(session)"这段代码时,另一个线程就不能访问"while(it.hasNext()){//迭代list集合}"这段代码
* 为了能够将这两段不相干的代码做成同步,只能定义一把锁(Object lock),然后给这两步操作加上同一把锁,
* 用这把锁来保证往list集合添加的新的session和遍历list集合中的session这两个操作达到同步
* 当在执行往list集合添加的新的session操作时,就必须等添加完成之后才能够对list集合进行迭代操作,
* 当在执行对list集合进行迭代操作时,那么必须等到迭代操作结束之后才能够往往list集合添加的新的session
*/
while(it.hasNext()){
HttpSession session = (HttpSession) it.next();
/**
* 如果当前时间-session的最后访问时间>1000*15(15秒)
* session.getLastAccessedTime()获取session的最后访问时间
*/
if(System.currentTimeMillis()-session.getLastAccessedTime()>1000*30){
//手动销毁session
session.invalidate();
//移除集合中已经被销毁的session
it.remove();
}
}
}
}
}


模拟Servlet3.0注解方式配置Servlet

Servlet的传统配置方式 

在JavaWeb开发中, 每次编写一个Servlet都需要在web.xml文件中进行配置,如下所示:



<servlet>
<servlet-name>ActionServlet</servlet-name>
<servlet-class>com.harvey.web.controller.ActionServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ActionServlet</servlet-name>
<url-pattern>/servlet/ActionServlet</url-pattern>
</servlet-mapping>


每开发一个Servlet,都要在web.xml中配置Servlet才能够使用,这实在是很头疼的事情,所以Servlet3.0之后提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署描述,简化开发流程。本文所讲的基于注解方式配置Servlet不是针对Servlet3.0的,而是基于Servlet2.5的,通过开发自定义注解和注解处理器来实现类似于Servlet3.0的注解方式配置Servlet。

基于注解的方式配置Servlet

JDK1. 5版本之后, JAVA提供了一种叫做Annotation的新数据类型,中文译为注解或标注,它的出现为铺天盖地的XML配置文件提供了一个完美的解决方案,让 JAVA EE开发更加方便快速,也更加干净了。不过Servlet2.5默认情况下是不支持注解方式的配置的,但是我们可以开发自定义注解,然后将注解标注到Servlet上,再针对我们自定义的注解写一个注解处理器,具体的做法如下:

1、开发用于配置Servlet的相关注解

(1) 开发WebServlet注解,用于标注处理请求的Servlet类



import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义WebServlet注解,模拟Servlet3.0的WebServlet注解
* @Target 注解的属性值表明了 @WebServlet注解只能用于类或接口定义声明的前面,
* @WebServlet注解有一个必填的属性 value 。
* 调用方式为: @WebServlet(value="/xxxx") ,
* 因语法规定如果属性名为 value 且只填 value属性值时,可以省略 value属性名,即也可以写作:@WebServlet("/xxxx")
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebServlet {
//Servlet的访问URL
String value();
//Servlet的访问URL
String[] urlPatterns() default {""};
//Servlet的描述
String description() default "";
//Servlet的显示名称
String displayName() default "";
//Servlet的名称
String name() default "";
//Servlet的init参数
WebInitParam[] initParams() default {};
}


将Servlet在web.xml中的配置信息使用WebServlet注解来表示,使用注解后,只需要在相应Servlet 类的前面使用类似@WebServlet("/servlet/LoginServlet") 注解就可以达到和上述 web.xml 文件中配置信息一样的目的。注解@WebServlet中的属性值"/servlet/LoginServlet"表示了web.xml 配置文件中 <servlet-mapping> 元素的子元素 <url-pattern> 里的值。通过这样的注解能简化在 XML 文件中配置 Servlet 信息,整个配置文件将会非常简洁干净,开发人员的工作也将大大减少。

(2) 开发WebInitParam注解,用于配置Servlet初始化时使用的参数



import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @ClassName: WebInitParam
* @Description: 定义Servlet的初始化参数注解
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebInitParam {
//参数名
String paramName() default "";
//参数的值
String paramValue() default "";
}


2、编写处理注解的处理器

上面简要地介绍了注解的定义声明与使用方式,注解在后台需要一个处理器才能起作用,所以还得针对上面的注解编写处理器,在这里我们使用Filter作为注解的处理器,编写一个AnnotationHandleFilter,代码

如下:



import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @ClassName: AnnotationHandleFilter
* @Description: 使用Filter作为注解的处理器
*
*/
public class AnnotationHandleFilter implements Filter {

private ServletContext servletContext = null;

/* 过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("---AnnotationHandleFilter过滤器初始化开始---");
servletContext = filterConfig.getServletContext();
Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();
//获取web.xml中配置的要扫描的包
String basePackage = filterConfig.getInitParameter("basePackage");
//如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
if (basePackage.indexOf(",")>0) {
//按逗号进行分隔
String[] packageNameArr = basePackage.split(",");
for (String packageName : packageNameArr) {
addServletClassToServletContext(packageName,classMap);
}
}else {
addServletClassToServletContext(basePackage,classMap);
}
System.out.println("----AnnotationHandleFilter过滤器初始化结束---");
}

/**
* @Method: addServletClassToServletContext
* @Description:添加ServletClass到ServletContext中
* @Anthor:孤傲苍狼
*
* @param packageName
* @param classMap
*/
private void addServletClassToServletContext(String packageName,Map<String, Class<?>> classMap){
Set<Class<?>> setClasses = ScanClassUtil.getClasses(packageName);
for (Class<?> clazz :setClasses) {
if (clazz.isAnnotationPresent(WebServlet.class)) {
//获取WebServlet这个Annotation的实例
WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
//获取Annotation的实例的value属性的值
String annotationAttrValue = annotationInstance.value();
if (!annotationAttrValue.equals("")) {
classMap.put(annotationAttrValue, clazz);
}
//获取Annotation的实例的urlPatterns属性的值
String[] urlPatterns = annotationInstance.urlPatterns();
for (String urlPattern : urlPatterns) {
classMap.put(urlPattern, clazz);
}
servletContext.setAttribute("servletClassMap", classMap);
System.out.println("annotationAttrValue:"+annotationAttrValue);
String targetClassName = annotationAttrValue.substring(annotationAttrValue.lastIndexOf("/")+1);
System.out.println("targetClassName:"+targetClassName);
System.out.println(clazz);
}
}
}

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("---进入注解处理过滤器---");
//将ServletRequest强制转换成HttpServletRequest
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
Map<String, Class<?>> classMap = (Map<String, Class<?>>) servletContext.getAttribute("servletClassMap");
//获取contextPath
String contextPath = req.getContextPath();
//获取用户请求的URI资源
String uri = req.getRequestURI();
//如果没有指明要调用Servlet类中的哪个方法
if (uri.indexOf("!")==-1) {
//获取用户使用的请求方式
String reqMethod = req.getMethod();
//获取要请求的servlet路径
String requestServletName = uri.substring(contextPath.length(),uri.lastIndexOf("."));
//获取要使用的类
Class<?> clazz = classMap.get(requestServletName);
//创建类的实例
Object obj = null;
try {
obj = clazz.newInstance();
} catch (InstantiationException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
}
Method targetMethod = null;
if (reqMethod.equalsIgnoreCase("get")) {
try {
targetMethod = clazz.getDeclaredMethod("doGet",HttpServletRequest.class,HttpServletResponse.class);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}else {
try {
targetMethod = clazz.getDeclaredMethod("doPost",HttpServletRequest.class,HttpServletResponse.class);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}

try {
//调用对象的方法进行处理
targetMethod.invoke(obj,req,res);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}else {
//获取要请求的servlet路径
String requestServletName = uri.substring(contextPath.length(),uri.lastIndexOf("!"));
//获取要调用的servlet的方法
String invokeMethodName = uri.substring(uri.lastIndexOf("!")+1,uri.lastIndexOf("."));

//获取要使用的类
Class<?> clazz = classMap.get(requestServletName);
//创建类的实例
Object obj = null;
try {
obj = clazz.newInstance();
} catch (InstantiationException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
}
//获得clazz类定义的所有方法
Method[] methods = clazz.getDeclaredMethods();
//获取WebServlet这个Annotation的实例
WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
//获取注解上配置的初始化参数数组
WebInitParam[] initParamArr = annotationInstance.initParams();
Map<String, String> initParamMap = new HashMap<String, String>();
for (WebInitParam initParam : initParamArr) {
initParamMap.put(initParam.paramName(), initParam.paramValue());
}
//遍历clazz类中的方法
for (Method method : methods) {
//该方法的返回类型
Class<?> retType = method.getReturnType();
//获得方法名
String methodName = method.getName();
//打印方法修饰符
System.out.print(Modifier.toString(method.getModifiers()));
System.out.print(" "+retType.getName() + " " + methodName +"(");
//获得一个方法参数数组(getparameterTypes用于返回一个描述参数类型的Class对象数组)
Class<?>[] paramTypes = method.getParameterTypes();
for(int j = 0 ; j < paramTypes.length ; j++){
//如果有多个参数,中间则用逗号隔开,否则直接打印参数
if (j > 0){
System.out.print(",");
}
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
if (method.getName().equalsIgnoreCase("init")) {
try {
//调用Servlet的初始化方法
method.invoke(obj, initParamMap);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
//获取WebServlet这个Annotation的实例
System.out.println("invokeMethodName:"+invokeMethodName);
try {
try {
//利用反射获取方法实例,方法的签名必须符合:
//public void 方法名(HttpServletRequest request, HttpServletResponse response)
//例如:public void loginHandle(HttpServletRequest request, HttpServletResponse response)
Method targetMethod = clazz.getDeclaredMethod(invokeMethodName,HttpServletRequest.class,HttpServletResponse.class);
//调用对象的方法进行处理
targetMethod.invoke(obj,req,res);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

public void destroy() {

}
}


AnnotationHandleFilter过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类,然后将类存储到一个Map集合中,再将Map集合存储到servletContext对象中。

Servlet体系详解_下篇_初始化_04

在web.xml文件中配置AnnotationHandleFilter过滤器和需要扫描的包



<filter>
<description>注解处理过滤器</description>
<filter-name>AnnotationHandleFilter</filter-name>
<filter-class>me.gacl.web.filter.AnnotationHandleFilter</filter-class>
<init-param>
<description>配置要扫描包及其子包, 如果有多个包,以逗号分隔</description>
<param-name>basePackage</param-name>
<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
<!-- <param-value>me.gacl.web.controller</param-value> -->
</init-param>
</filter>

<filter-mapping>
<filter-name>AnnotationHandleFilter</filter-name>
<!-- 拦截后缀是.do的请求 -->
<url-pattern>*.do</url-pattern>
</filter-mapping>


AnnotationHandleFilter过滤器初始化方法init(FilterConfig filterConfig)使用到了一个用于扫描某个包下面的类的工具类ScanClassUtil,ScanClassUtil的代码如下:



import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ScanClassUtil {

/**
* 从包package中获取所有的Class
*
* @param pack
* @return
*/
public static Set<Class<?>> getClasses(String pack) {

// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(
packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
System.err.println("file类型的扫描");
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath,
recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
System.err.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection())
.getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx)
.replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class")
&& !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(
packageName.length() + 1, name
.length() - 6);
try {
// 添加到classes
classes.add(Class
.forName(packageName + '.'
+ className));
} catch (ClassNotFoundException e) {
// log
// .error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}

return classes;
}

/**
* 以文件的形式来获取包下的所有Class
*
* @param packageName
* @param packagePath
* @param recursive
* @param classes
*/
public static void findAndAddClassesInPackageByFile(String packageName,
String packagePath, final boolean recursive, Set<Class<?>> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory())
|| (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "."
+ file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
// 添加到集合中去
//classes.add(Class.forName(packageName + '.' + className));
//经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log.error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}


经过以上两步,我们的自定义注解和针对注解的处理器都开发好了。

3、WebServlet注解简单测试

编写一个用于跳转到Login.jsp页面的LoginUIServlet,LoginUIServlet就是一个普通的java类,不是一个真正的Servlet,然后使用WebServlet注解标注LoginUIServlet类,代码如下:



import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/servlet/LoginUI")
public class LoginUIServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
request.getRequestDispatcher("/Login.jsp").forward(request, response);
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


在浏览器中输入访问地址:http://localhost:8080/AnnotationConfigServlet/servlet/Login.do,根据web.xml文件的配置,所有后缀名为 .do请求,都会经过AnnotationHandleFilter过滤器的doFilter方法,在doFilter方法的实现代码中,从HttpServletRequest请求对象中得到请求的方式类型(GET/POST)和请求的URI 。如有请求http://localhost:8080/AnnotationConfigServlet/servlet/LoginUI.do,此时请求方法类型为GET, URI 值为/AnnotationConfigServlet/servlet/LoginUI.do。从ServletConext对象中获取到在过滤器中保存的Map结构,根据 URI 获得一个 Key=”/servlet/LoginUI” ,从 Map 结构中根据此Key得到Value ,此时Value就是要请求调用的那个Servlet类,根据Servlet类创建对象实例,再根据前面得到的请求方法类型,能决定调用此Servlet对象实例的 doGet 或 doPost 方法。最终客户端发生的后缀为.do请求,经由AnnotationHandleFilter对请求对象(HttpServletRequest)的分析,从而调用相应某Servlet的doGet或doPost方法,完成了一次客户端请求到服务器响应的过程。

使用注解后程序流程如下所示:

Servlet体系详解_下篇_ide_05

Servlet3.0是支持采用基于注解的方式配置Servlet的,在此我使用过滤器作为注解处理器模拟模拟出了类似Servlet3.0的注解处理方式,简化了Servlet的配置。这种使用自定义注解+注解处理器的方式山寨出来的Servlet3.0大家了解一下即可,了解一下这种处理思路,在实际应用中还是不要这么做了,要真想使用注解的方式配置Servlet还是直接用Servlet3.0吧。

web.xml配置文件

启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<listener>和<context-param>两个结点。 

紧急着,容器会创建一个ServletContext(servlet上下文),这个web项目的所有部分都将共享这个上下文。 

容器将<context-param>转换为键值对,并交给servletContext。

容器创建<listener>中的类实例,创建监听器。 

Load-on-startup

Load-on-startup 元素在web应用启动的时候指定了servlet被加载的顺序,它的值必须是一个整数。如果它的值是一个负整数或是这个元素不存在,那么容器会在该servlet被调用的时候,加载这个servlet 。如果值是正整数或零,容器在配置的时候就加载并初始化这个servlet,容器必须保证值小的先被加载。如果值相等,容器可以自动选择先加载谁。  

在servlet的配置当中,<load-on-startup>5</load-on-startup>的含义是: 

  • 标记容器是否在启动的时候就加载这个servlet。 
  • 当值为0或者大于0时,表示容器在应用启动时就加载这个servlet; 
  • 当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。 
  • 正数的值越小,启动该servlet的优先级越高。 

加载顺序

首先可以肯定的是,加载顺序与它们在 web.xml 文件中的先后顺序无关。即不会因为 filter 写在 listener 的前面而会先加载 filter。

最终得出的结论是:ServletContext -> listener -> filter -> servlet

同时还存在着这样一种配置节:context-param,它用于向 ServletContext 提供键值对,即应用程序上下文信息。我们的 listener, filter 等在初始化时会用到这些上下文中的信息,那么 context-param 配置节是不是应该写在 listener 配置节前呢?实际上 context-param 配置节可写在任意位置,因此真正的加载顺序为:context-param -> listener -> filter -> servlet

对于某类配置节而言,与它们出现的顺序是有关的。以 filter 为例,web.xml 中当然可以定义多个 filter,与 filter 相关的一个配置节是 filter-mapping,这里一定要注意,对于拥有相同 filter-name 的 filter 和 filter-mapping 配置节而言,filter-mapping 必须出现在 filter 之后,否则当解析到 filter-mapping 时,它所对应的 filter-name 还未定义。web 容器启动时初始化每个 filter 时,是按照 filter 配置节出现的顺序来初始化的,当请求资源匹配多个 filter-mapping 时,filter 拦截资源是按照 filter-mapping 配置节出现的顺序来依次调用 doFilter() 方法的。

servlet 同 filter 类似,此处不再赘述。

由此,可以看出,web.xml 的加载顺序是:ServletContext -> context-param -> listener -> filter -> servlet ,而同个类型之间的实际程序调用的时候的顺序是根据对应的 mapping 的顺序进行调用的。

web.xml文件详解 

web.xml首先是肯定要包含它的schema。



<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd"
version="2.5">

</web-app>


其它的元素都放在<web-app></web-app>之中。

<discription></discription> 是对站台的描述

<display-name></display-name> 定义站台的名称

<distributable/> 是指定该站台是否可分布式处理

context-param

<context-param></context-param> 用来设定web站台的环境参数,它包含两个子元素:

  • <param-name></param-name> 用来指定参数的名称
  • <param-value></param-value> 用来设定参数值

比如:



<context-param>
<param-name>my_param</param-name>
<param-value>hello</param-value>
</context-param>


在此设定的参数,可以在servlet中用 getServletContext().getInitParameter("my_param") 来取得。

filter

<filter></filter> 是用来声明filter的相关设定,它包含以下子元素:

  • <filter-name></filter-name> 指定filter的名字
  • <filter-class></filter-class> 定义filter的类的名称
  • <init-param></init-param> 用来定义参数,它有两个子元素:
  • <param-name></param-name> 用来指定参数的名称
  • <param-value></param-value> 用来设定参数值

比如:



<filter>
<filter-name>setCharacterEncoding</filter-name>
<filter-class>com.myTest.setCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GB2312</param-value>
</init-param>
</filter>


与<filter></filter>同时使用的是<filter-mapping></filter-mapping> 用来定义filter所对应的URL,它有两个子元素:

  • <filter-name></filter-name> 指定filter的名字
  • <url-pattern></url-pattern> 指定filter所对应的URL

比如:



<filter-mapping>
<filter-name>setCharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


listener

<listener></listener> 用来设定Listener接口,它的主要子元素为:

  • <listener-class></listener-class> 定义Listener的类名称

比如:



<listener>
<listener-class>com.myTest.ContextListener</listener-class>
</listener>


servlet

<servlet></servlet> 用来声明一个servlet的数据,主要有以下子元素:

  • <servlet-name></servlet-name> 指定servlet的名称
  • <servlet-class></servlet-class> 指定servlet的类名称
  • <jsp-file></jsp-file> 指定web站台中的某个JSP网页的完整路径,等同于<servlet-class>一样,只是现在是要用jsp来替代servlet的功能。本质上jsp就是servlet。
  • <init-param></init-param> 用来定义参数,和前面的<init-param>差不多

同样,与<servlet></servlet>一起使用的是<servlet-mapping></servlet-mapping> 用来定义servlet所对应的URL,包含两个子元素:

  • <servlet-name></servlet-name> 指定servlet的名称
  • <url-pattern></url-pattern> 指定servlet所对应的URL

比如:



<servlet>
<servlet-name>ShoppingServlet</servlet-name>
<servlet-class>com.myTest.ShoppingServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ShoppingServlet</servlet-name>
<url-pattern>/shop/ShoppingServlet</url-pattern>
</servlet-mapping>


session-config

<session-config></session-config> 用来定义web站台中的session参数,包含一个子元素:

  • <session-timeout></session-timeout> 用来定义这个web站台所有session的有效期限,单位为分钟

mime-mapping

<mime-mapping></mime-mapping> 定义某一个扩展名和某一个MIME Type做对映,包含两个子元素:

  • <extension></extension> 扩展名的名称
  • <mime-type></mime-type> MIME格式

welcome-file-list

<welcome-file-list></welcom-file-list> 用来定义首页的列单,包含一个子元素:

  • <welcome-file></welcome-file> 指定首页的文件名称

比如:



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


error-page

<error-page></error-page> 用来处理错误代码或异常的页面,有三个子元素:

  • <error-code></error-code> 指定错误代码
  • <exception-type></exception-type> 指定一个JAVA异常类型
  • <location></location> 指定在web站台内的相关资源路径

比如:



<error-page>
<error-code>404</error-code>
<location>/error404.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/exception.jsp</location>
</error-page>


taglib

<taglib></taglib> 用来设定JSP网页所用到的Tag Library路径,有两个子元素:

  • <taglib-uri></taglib-uri> 定义TLD文件的URI,在JSP网页中用taglib指令便可取得该URI的TLD文件
  • <taglib-location></taglib-location> 指定TLD文件相对于web站台的存放位置

比如:



<taglib>
<taglib-uri>myTaglib</taglib-uri>
<taglib-location>/WEB-INF/tlds/MyTaglib.tld</taglib-location>
</taglib>


resource-ref

<resource-ref></resource-ref> 定义利用JNDI取得站台可利用的资源,有五个子元素:

  • <description></description> 资源说明
  • <rec-ref-name></rec-ref-name> 资源名称
  • <res-type></res-type> 资源种类
  • <res-auth></res-auth> 资源经由Application或Container来许可
  • <res-sharing-scope></res-sharing-scope> 资源是否可以共享,有Shareable和Unshareable两个值,默认为Shareable

比如,配置数据库连接池就可在此配置:



<resource-ref>
<description>JNDI JDBC DataSource of shop</description>
<res-ref-name>jdbc/sample_db</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>


jsp-config

<jsp-config> 包括<taglib> 和<jsp-property-group> 两个子元素。

其中<taglib>元素在JSP 1.2时就已经存在;而<jsp-property-group>是JSP 2.0 新增的元素。

<jsp-property-group>元素主要有八个子元素,它们分别为:

  • <description>:设定的说明;
  • <display-name>:设定名称;
  • <url-pattern>:设定值所影响的范围,如:/CH2 或 /*.jsp;
  • <el-ignored>:若为true,表示不支持EL 语法;
  • <scripting-invalid>:若为true,表示不支持<% scripting %>语法;
  • <page-encoding>:设定JSP 网页的编码;
  • <include-prelude>:设置JSP 网页的抬头,扩展名为.jspf;
  • <include-coda>:设置JSP 网页的结尾,扩展名为.jspf。

 一个简单的<jsp-config>元素完整配置:



<jsp-config>
<taglib>
<taglib-uri>Taglib</taglib-uri>
<taglib-location>/WEB-INF/tlds/MyTaglib.tld</taglib-location>
</taglib>
<jsp-property-group>
<description>Special property group for JSP Configuration JSP example.</description>
<display-name>JSPConfiguration</display-name>
<url-pattern>/jsp/* </url-pattern>
<el-ignored>true</el-ignored>
<page-encoding>GB2312</page-encoding>
<scripting-invalid>true</scripting-invalid>
<include-prelude>/include/prelude.jspf</include-prelude>
<include-coda>/include/coda.jspf</include-coda>
</jsp-property-group>
</jsp-config>


security-constraint

有时我们只希望通过认证的用户才能请求某些servlet的话,就可以在web.xml中来进行相应的配置,来达到此目的。

这就要用到<security-constraint></security-constraint>元素。

对于tomcat,web.xml使用security-constraint元素需要在位于<Tomcat-installation-directory>/conf/tomcat-users.xml的XML文件中创建用户名和密码。比如下面的这个tomcat-users.xml文件:



<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="tomcat"/>
<role rolename="manager"/>
<role rolename="admin"/>
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="both" password="tomcat" roles="tomcat,manager"/>
<user username="admin" password="admin" roles="admin"/>
</tomcat-users>


此XML片段包括一个tomcat-users根元素,它包含一个或多个role和user元素。

然后在Web应用程序的web.xml中创建security-constraint、login-config和security-role元素。



<security-constraint>
<web-resource-collection>
<web-resource-name>HelloServlet</web-resource-name>
<url-pattern>/HelloServlet</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<description>This applies only to the "tomcat" security role</description>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>

<login-config>
<auth-method>BASIC</auth-method>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>


其中security-constraint元素包含一个或多个web-resource-collection元素,它是描述Web应用程序中的哪些web资源受到指定安全限制的保护。http-method元素指定安全限制覆盖的HTTP方法。上面的例子中,当我们对/HelloServlet的GET或POST请求时将触发配置的安全机制。

auth-constraint元素用于描述允许访问Web组件的安全角色。此例中安全角色的例子有tomcat、manager、admin。而只有当作为admin角色的用户才可以访问HelloServlet。

Web应用程序通过login-config元素来认证用户,并确认该用户是否为正确的角色。

longin-config包含的transport-guarantee子元素用来指定认证方法,BASIC是一种常见的Web认证方式,浏览器给用户提示一个对话框,要求输入用户名和密码,随后Tomcat将给出的用户名和密码与tomcat-users.xml中的用户名和密码进行比较,然后使用前面的security-constraint配置来确定用户是否可访问受保护的servlet。

(除BASIC外,还可以是FORM、CLIENT-CERT、DIGEST等)

其实这种认证方法实际上有两个步骤:

  • 检查提供的用户名和密码是否正确。
  • 判断用户是否映射到特定的安全角色。例如,用户可能提供了正确的用户名和密码,但没有映射到特定的安全角色,也将被禁止访问特定的Web资源。