JavaWeb——Filter过滤器
定义:Filter是拦截Request请求的对象,在用户的请求访问资源前处理ServletRequest以及ServletResponse,它可用于日志记录,加解密,Session检查,图像文件保护。通过Filter可以拦截处理某个资源或者某些资源
Filter的概念虽然说了这么一大堆,但是简单的理解,就是在浏览器(客户端)与服务端(Servlet)之间交互时加上一道"屏障"(即过滤器),又有点难以理解了。
话不多说,直接上图
在没有过滤器前,浏览器可以与Servlet(服务器端)直接交互
这样直接交互有很多的问题产生,例如:字符编码不统一导致乱码、敏感词汇过滤、日志记录等比较"公共性"的事件。此处的公共性是指:多个Servlet都会处理的事情。因此,在浏览器与服务器中间加上Filter,这个Filter可以对应多个Servlet,已处理这些比较"共性"的问题
。
定义:Filter的实现类必须实现javax.servlet.Filter接口,该接口包含Filter的三个周期(init,doFilter,destroy)
init():Filter的初始化方法,Servlet容器初始化Filter时,会触发该方法,该方法只会被调用一次。
doFilter():Filter的过滤方法,Servlet容器每次处理Filter相关资源时都会调用该方法。
destroy():Filter的销毁方法,Servlet容器销毁Filter时触发,应用停止时调用
init()与destroy()比较好理解,主要说一说doFilter()的一些细节和注意事项。
1、doFilter()存在两个参数,ServletRequest和ServletResponse,在doFilter()中可以对这两个对象进行修改。而chain用作对Filter链的后续进行访问。
2、一个资源可能被多个Filter关联,成为Filter链
,此时必须在doFilter()方法的最后使用chain.doFilter(request, response);
继续执行接下来的过滤器/Servlet(Filter链的最后一个Filter再调用该函数即为调用Servlet
),因此,即便是只有一个Filter
,也需要在doFilter()的最后调用chain.doFilter(request, response);
,这里上个图便于理解。
总结一下
,Filter是夹在浏览器请求
与Servlet
之间的"屏障",若存在多个Filter,组成过滤器链。
1、当浏览器初次向服务端发送Servlet请求时,每经过一个Filter时,先调用Filter的init(),再调用doFilter(),并在doFilter()方法中调用chain.doFilter(request, response);
访问过滤器链的下一个"结点",直到过滤器链的最后一个"结点"时,再调用chain.doFilter(request, response);就会直接调用Servlet,当Servlet执行完后,按照相反的顺序执行过滤链中Filter的destroy()方法。
2、当浏览器正常情况下发送请求,就直接调用过滤链中各Filter的doFilter()方法,并按照过滤链的顺序最终访问到对应的Servlet中。
3、当浏览器最后一次发送请求时,即此时Servlet服务已经停止,此时会按照过滤链的相反顺序,逆着执行各Filter的destroy(),直到遍历完整个过滤链。
用一张图来概括:
Filter业务通常是相互独立的,如果Filter的执行顺序有限制
,只能通过配置文件(web.xml)方式配置
,写在前面的Filter先执行。(此时不能使用注解的方式
)
以上便是Filter的一些定义及理解了,下面举个例子看一下过滤器的具体流程。
举个栗子:
每次请求除了记住uri外,添加请求计数器(牵扯到多线程访问)
1、定义FIlter过滤类,实现Filter接口,由于牵扯到多线程的问题,因此需要开一个单线程线程池来保证每次只会有一个线程对共享资源(次数)进行+操作。
public class CounterFilter implements Filter {
private File logFile;
// 创建一个单线程线程池——保证任务有序且线程安全的执行
private ExecutorService executorService = Executors.newSingleThreadExecutor();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String filename = filterConfig.getInitParameter("filter_filename");
String webAppRoot = filterConfig.getServletContext().getRealPath("/");
this.logFile = new File(webAppRoot,filename);
if (!this.logFile.exists()){
try {
this.logFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String uri = ((HttpServletRequest)request).getRequestURI();
final File propertiesFile = this.logFile;
executorService.execute(new Runnable() {
/**
* 利用Properties类的key-value属性,先将文件内容读到Properties对象中
* 进行key-value的相关操作后,再将Properties对象中的内容写回到文件
*/
@Override
public void run() {
// 使用Properties类保存key-value形式的数据
Properties properties = new Properties();
try {
properties.load(new FileInputStream(propertiesFile));
} catch (IOException e) {
e.printStackTrace();
}
String value = properties.getProperty(uri);
if(value==null){
value = "1";
}else {
value = String.valueOf((Integer.parseInt(value)+1));
}
properties.setProperty(uri,value);
// 再将该Properties形式写回到文件中
try {
properties.store(new FileOutputStream(propertiesFile),"update");
} catch (IOException e) {
e.printStackTrace();
}
}
});
// 继续执行接下来的过滤器/Servlet
chain.doFilter(request, response);
}
@Override
public void destroy() {
// do nothing
}
}
2、在web.xml对Filter进行配置,这里需要注意,过滤器拦截的请求可以使用<url-pattern>标签拦截具体的url地址("/*"代表拦截所有请求
),也可以使用<servlet-name>标签拦截具体的Servlet请求。
也可以在配置文件中设置初始化参数,并在程序中进行获取。
<!--配置Counter过滤器-->
<filter>
<filter-name>CounterFilter</filter-name>
<filter-class>com.xiaoaxiao.myfirst.filter.CounterFilter</filter-class>
<!--配置初始化参数-->
<init-param>
<param-name>filter_filename</param-name>
<param-value>counter.logging</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CounterFilter</filter-name>
<!--拦截指定的请求地址-->
<!--/*代表拦截所有地址-->
<url-pattern>/*</url-pattern>
<!--拦截指定的Servlet-->
<!--<url-pattern>SessionServlet</url-pattern>-->
</filter-mapping>