JSP Filters(过滤器)
Filter是拦截Request请求的对象:在用户的请求访 问资源前处理ServletRequest以及ServletResponse,它可 用于日志记录、加解密、Session检查、图像文件保护 等。通过Filter可以拦截处理某个资源或者某些资源。 Filter的配置可以通过Annotation或者部署描述来完成。 当一个资源或者某些资源需要被多个Filter所使用到, 且它的触发顺序很重要时,只能通过部署描述来配置。
一.Filter API
1. Filter 相关接口,包含Filter, FilterConfig, FilterChain
Filter的实现必须继承javax.servlet.Filter接口。这个 接口包含了Filter的3个生命周期:init、doFilter、 destroy。
Servlet容器初始化Filter时,会触发Filter的init方 法,一般来说是在应用开始时。也就是说,init方法并 不是在该Filter相关的资源使用到时才初始化的,而且 这个方法只调用一次,用于初始化Filter。init方法的定 义如下:
oid init(FilterConfig filterConfig)
注意: FilterConfig实例是由Servlet容器传入init方法中的.
当Servlet容器每次处理Filter相关的资源时,都会调 用该Filter实例的doFilter方法。Filter的doFilter方法包含 ServletRequest、ServletResponse、FilterChain这3个参 数。
doFilter的定义如下:
void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain)
接下来,说明一下doFilter的实现中访问 ServletRequet、ServletResponse。这也就意味着允许给 ServletRequest增加属性或者增加Header。当然也可以修 饰ServletRequest或者ServletRespone来改变它们的行 为。
在Filter的doFilter的实现中,最后一行需要调用 FilterChain中的doFilter方法。注意Filter的doFilter方法 里的第3个参数,就是filterChain的实例:
filterChain.doFilter(request, response)
一个资源可能需要被多个Filter关联到(更专业一 点来说,这应该叫作Filter链条),这时Filter.doFilter() 的方法将触发Filter链条中下一个Filter。只有在Filter链 条中最后一个Filter里调用的FilterChain.doFilter(),才会 触发处理资源的方法。
如果在Filter.doFilter()的实现中,没有在结尾处调 用FilterChain.doFilter()的方法,那么该Request请求中 止,后面的处理就会中断。
注意: FilterChain接口中,唯一的方法就是doFilter。该方法与Filter中的 doFilter的定义是不一致的:在FilterChaing中,doFilter方法只有两个参 数,但在Filter中,doFilter方法有三个参数。
Filter接口中,最后一个方法是destroy,它的定义 如下:
Void destroy()
该方法在Servlet容器要销毁Filter时触发,一般在应 用停止的时候进行调用。
除非Filter在部署描述中被多次定义到,否则Servlet 窗口只会为每个Filter创建单一实例。由于Serlvet/JSP的 应用通常要处理用户并发请求,此时Filter实例需要同 时被多个线程所关联到,因此需要非常小心地处理多线 程问题。
三. Filter配置
当完成Filter的实现后,就可以开始配置Filter了。 Filter的配置需要如下步骤:
- 确认哪些资源需要使用这个Filter拦截处理。
- 配置Filter的初始化参数值,这些参数可以在Filter的 init方法中读取到;
- 给Filter取一个名称。一般来说,这个名称没有什么 特别的含义,但在一些特殊的情况下,这个名字十 分有用。例如,要记录Filter的初始化时间,但这个 应用中有许多的Filter,这时它就可以用来识别Filter 了。
FilterConfig接口允许通过它的getServletContext的 方法来访问ServletContext:
ServletContext getServletContext(
如果配置了Filter的名字,在FilterConfig的 getFilterName中就可以获取Filter的名字。getFilterName 的定义如下:
java.lang.String getFilterName()
当然,最重要的还是要获取到开发者或者运维给 Filter配置的初始化参数。为了获取这些初始化参数, 需要用到FilterConfig中的两个方法,第一个方法是 getParameterNames:
java.util.Enumeration<java.lang.String> getInitParameterNames()
这个方法返回Filter参数名字的Enumeration对象。 如果没有给这个Filter配置任何参数,该方法返回的是 空的Enumeration对象。
第二个方法是getParameter:
java.lang.String getInitParameter(java.lang.String parameterName)
有两种方法可以配置Filter:一种是通过WebFilter 的Annotation来配置Filter,另一种是通过部署描述来注 册。使用@WebFilter的方法,只需要在Filter的实现类 中增加一个注解即可,不需要重复地配置部署描述。当 然,此时要修改配置参数,就需要重新构建Filter实现 类了。换句话说,使用部署描述意味着修改Filter配置 只要修改一下文本文件就可以了。
使用@WebFilter,你需要熟悉下表中所列出来的 参数,这些参数是在WebFilter的Annotation里定义的。 所有参数都是可选的。
属性 | 描述 |
asyncSupported | Filter是否支持异步操作 |
description | Filter的描述 |
dispatcerTypes | Filter所生效范围 |
displayName | Filter的显示名 |
filterName | Filter的名称 |
initParams | Filter的初始化参数 |
largeIcon | Filter的大图名称 |
servletName | Filter所生效的Servlet名称 |
smallIcon | Filter的小图名称 |
urlPatterns | Filter所生效的URL路径 |
value | Filter所生效的URL路径 |
三. 示例1: 日志 Filter
作为第1个例子,将做一个简单的Filter:在app09a 的应用中把Request请求的URL记录到日志文本文件 中。日志文本文件名通过Filter的初始化参数来配置。 此外,日志的每条记录都会有一个前缀,该前缀也由 Filter初始化参数来定义。通过日志文件,可以获得许 多有用的信息,例如在应用中哪些资源访问最频繁; Web站点在一天中的哪个时间段访问量最多。
这个Filter的类名叫LoggingFilter。 一般情况下,Filter的类名都以*Filter结尾。
package filter; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
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.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest; @WebFilter(filterName = "LoggingFilter", urlPatterns = { "/ *" }, initParams = {
@WebInitParam(name = "logFileName", value = "log.txt"), @WebInitParam(name = "prefix", value = "URI: ") })
public class LoggingFilter implements Filter {
private PrintWriter logger;
private String prefix; @Override
public void init(FilterConfig filterConfig) throws ServletException {
prefix = filterConfig.getInitParameter("prefix"); //得到URI
String logFileName = filterConfig.getInitParameter("logFileName");
String appPath = filterConfig.getServletContext().getRealPath("/"); //得到项目路径
// without path info in logFileName, the log file will be
// created in $TOMCAT_HOME/bin
System.out.println("logFileName:" + logFileName);
try {
logger = new PrintWriter(new File(appPath, logFileName)); //打开文件
logger.println("I have the output");
logger.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new ServletException(e.getMessage());
}
} @Override
public void destroy() {
System.out.println("destroying filter");
if (logger != null) {
logger.close();
}
} @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("LoggingFilter.doFilter");
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
logger.println(new Date() + " " + prefix + httpServletRequest.getRequestURI()); //写入数据
logger.flush(); //刷新缓冲
filterChain.doFilter(request, response); //如果没有doFilter方法,后面发Filter处理就会中断
} }
下面来仔细分析一下Filter类。 首先,该Filter的类实现了Filter的接口并声明两个 变量:PrintWriter类型的logger和String类型的prefix。
其中PrintWriter用于记录日志到文本文件,prefix的 字符串用于每条日志的前缀。 Filter的类使用了@WebFilter的Annotation,将两个 参数(logFilteName、prefix)传入到该Filter中.
在Filter的init方法中,通过FilterConfig里传入的 getInitParameter方法来获取prefix和getFileName的初始 化参数。其中把prefix参数中赋给了类变量prefix, logFileName则用于创建一个PrintWriter
如果Servlet/JSP应用是通过Servlet/JSP容器启动 的,那么当前应用的工作目录是当前JDK所在的目录。 如果是在Tomcat中,该目录是Tomcat的安装目录。在 应用中创建日志文件,可以通过 ServletContext.getRealPath来获取工作目录,结合应用 工作目录以及初始化参数中的logFilterNmae,就可以得 到日志文件的绝对路径.
当Filter的init方法被执行时,日志文件就会创建出 来。如果在应用的工作目录中该文件已经存在,那么该 日志文件的内容将会被覆盖。 当应用关闭时,PrintWriter需要被关闭。因此在 Filter的destroy方法中,需要:关闭日志
Filter的doFilter实现中记录着所有从ServletRequest 到HttpServletRequest的Request,并调用了它的 getRequestURI方法,该方法的返回值将记录通过 PrintWriter的pringln记录下来
每条记录都有一个时间戳以及前缀,这样可以很方 便地标识每条记录。接下来 Filter的doFilter实现调用 PrintWriter的flush方法以及FilterChain.doFilter,以唤起 资源的调用:
如果使用Tomcat,Filter的初始化并不会等到第一 个Request请求时才触发进行。这点可以在控制台中打 印出来的logFileName参数值中可以看到。在app09a应 用中通过URL调用test.jsp页面,就可以测试该Filter了
通过检查日志文件的内容,就可以验证这个Filter 是否运行正常。
四. 示例2:图像文件保护Filter
本例中的图像文件保护Filter用于在浏览器中输入 图像文件的URL路径时,防止下载图像文件。应用中的 图像文件只有当图像链接在页面中被点击的时候才会显 示。该Filter的实现原理是检查HTTP Header的referer 值。如果该值为null,就意味着当前的请求中没有 referer值,即当前的请求是直接通过输入URL来访问该 资源的。如果资源的Header值为非空,将返回Request 语法的原始页面作为referer值。注意Header的referer的 属性名中,在第2个e以及第3个e中仅有一个r。
ImageProtectorFilter的Filter实现类,如清单所 示。从WebFilter的Annotation中,可以看到该Filter应用 于所有的.png、.jpg、.gif文件后缀。
ImageProtectorFilter实现类
package 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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest; //过滤所有以.png , .jpg .gif 为后缀的访问
@WebFilter(filterName = "ImageProtetorFilter", urlPatterns = { "*.png", "*.jpg", "*.gif" })
public class ImageProtectorFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
} @Override
public void destroy() {
} @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
//直接输入图片地址访问http://localhost:8080/app09/图片.jpg会被拦截
System.out.println("ImageProtectorFilter");
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String referrer = httpServletRequest.getHeader("referer");
System.out.println("referrer:" + referrer);
if (referrer != null) {
filterChain.doFilter(request, response);
} else {
throw new ServletException("Image not available");
}
}
}
这里并没有init和destroy方法。其中doFilter方法读 取到Header中的referer值,要确认是要继续处理这个资 源还是给个异常.
测试该Filter,可以在浏览器中输入如下ULR路 径,尝试访问logo.png图像:
http://localhost:8080/app09/图片.jpg
接下来,通过image.jsp的页面来访问该图像:
http://localhost:8080/app09/test.jsp
五.示例3:下载计数Filter
本例子中,下载计数Filter将会示范如何在Filter中 计算资源下载的次数。这个示例特别有用,它将会得到 文档、音频文件的受欢迎程度。作为简单的示例,这里 将数值保存在属性文件中,而不保存在数据库中。其中 资源的ULR路径将作为属性名保存在属性文件中。
因为我们把值保存在属性文件中,并且Filter可以 被多线程访问,因此涉及线程安全问题。用户访问一个 资源时,Filter需要读取相应的属性值加1,然后保存该 值。如果第二个用户在第一个线程完成前同时访问该资 源,将会发生什么呢?计算值出错。在本例中,读写的 同步锁并不是一个好的解决这个问题的方法,因为它会 导致扩展性问题。
本示例中,解决这个线程安全问题是通过Queue以 及Executor。
简而言之,进来的Request请求将会保存在单线程 Executor的队列中。替换这个任务十分方便,因为这是 一个异步的方法,因此你不需要等待该任务结束。 Executor一次从队列中获取一个对象,然后做相应属性 值的增加。由于Executor只在一个线程中使用,因此可 以消除多个线程同时访问一个属性文件的影响。
DownloadCounterFilter实现类
package filter; import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest; // 拦截所有URL
//书上的是 urlPatterns={"/"},亲测无效
@WebFilter(filterName = "DownloadCounterFilter", urlPatterns = { "*.JPG" })
public class DownloadCounterFilter implements Filter {
// 获取单个线程池的Executor 由于Executor只在一个线程中使用,因此可 以消除多个线程同时访问一个属性文件的影响。
ExecutorService executorService = Executors.newSingleThreadExecutor();
// propeties 属性集
Properties downloadLog;
File logFile; @Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("DownloadCounterFilter");
//获取路径
String appPath = filterConfig.getServletContext().getRealPath("/");
// 获取downLoadLog.txt文件
logFile = new File(appPath, "downloadLog.txt");
if (!logFile.exists()) {
try {
logFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
} downloadLog = new Properties();
try {
downloadLog.load(new FileReader(logFile));
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void destroy() {
executorService.shutdown();
} @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final String uri = httpServletRequest.getRequestURI();
executorService.execute(new Runnable() {
@Override
public void run() {
String property = downloadLog.getProperty(uri);
if (property == null) {
downloadLog.setProperty(uri, "");
} else {
int count = ;
try {
count = Integer.parseInt(property);
} catch (NumberFormatException e) {
// silent
}
count++;
downloadLog.setProperty(uri, Integer.toString(count));
}
try {
downloadLog.store(new FileWriter(logFile), "");
} catch (IOException e) {
}
}
});
filterChain.doFilter(request, response);
}
}
如果在当前应用的工作目录中不存在 downloadLog.txt文件,这个Filter的init方法就会创建 它
接着创建Properties对象,并读取该文件.
注意,Filter的实现类中引用到了 ExecutorService(Executor的子类).
且当Filter销毁时,会调用ExecutorService的 shutdown方法.
Filter的doFilter实现中大量地使用到这个Job。每次 URL请求都会调用到ExecutorService的execute方法,然 后才调用FilterChaing.doFilter()。该任务的execute实现 非常好理解:它将URL作为一个属性名,从Properties 实例中获取该属性的值,然后加1,并调用flush方法写 回到指定的日志文件中
这个Filter可在许多资源上生效,但也可以非常简 单地配置,限定为PDF或者AVI文件资源。
properties文件
六. Filter顺序
如果多个Filter应用于同一个资源,Filter的触发顺 序将变得非常重要,这时就需要使用部署描述来管理 Filter:指定哪个Filter先被触发。例如:Filter 1需要在 Filter 2前被触发,那么在部署描述中,Filter 1需要配置 在Filter 2之前:
<filter>
<filter-name>Filter1</filter-name>
<filter-class>
the fully-qualified name of the filter class
</filter-class>
</filter>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>
the fully-qualified name of the filter class
</filter-class>
</filter>
通过部署描述之外的配置来指定Filter触发的顺序 是不可能的
JSP Filters(过滤器)的更多相关文章
- Liferay7 BPM门户开发之36: 使用Portlet filters过滤器做切面AOP
使用Portlet filters过滤器做切面AOP Portlet Filters定义于JSR286 Java Portlet Specification 2.0 Portlet Filters是为 ...
- Asp.Net MVC 3【Filters(过滤器)】
这里分享MVC里的Filters(过滤器),什么是MVC里的过滤器,他的作用是什么? 过滤器的请求处理管道中注入额外的逻辑.他们提供了一个简单而优雅的方式来实现横切关注点.这个术语是指所有对应用程序的 ...
- vue filters过滤器
vue filters过滤器 vue.js允许我们自定义过滤器,可被使用于一些常见的文本格式化,过滤器可以用在两个地方,双花括号插值和 v-bind表达式.最常见的就是双花括号插值. 比如如下代码:{ ...
- Vue.js Cookbook: 添加实例属性; 👍 axios(4万➕✨)访问API; filters过滤器;
add instance properties //加上$,防止和已经定义的data,method, computed的名字重复,导致被覆写.//可以自定义添加其他符号. Vue.prototype. ...
- .net core MVC 通过 Filters 过滤器拦截请求及响应内容
前提: 需要nuget Microsoft.Extensions.Logging.Log4Net.AspNetCore 2.2.6: Swashbuckle.AspNetCore 我暂时用的是 ...
- JSP中过滤器的设置
JSP中过滤器的设置 package com.filter; import java.io.IOException; import java.net.URLDecoder; import java.u ...
- JSP使用过滤器防止SQL注入
什么是SQL注入攻击?引用百度百科的解释: sql注入_百度百科: 所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令.具 ...
- mvc Filters 过滤器
项目需要控制controller和action的访问权限. 看了下资料,发觉还是很方便的. 首先在mvc项目下创建一个文件夹 Filters, 然后在Filters中创建一个类.代码如下 namesp ...
- servlet和jsp页面过滤器Filter的作用及配置
刚刚有个朋友问我,Servlet的过滤器有什么作用? 现在发个帖子说明一下, 过滤器是一个对象,可以传输请求或修改响应.它可以在请求到达Servlet/JSP之前对其进行预处理, ...
随机推荐
- 5-24 css内容的补充
1,标准文档流 宏观的将,我们的web页面和ps等设计软件有本质的区别,web 网页的制作,是个“流”,从上而下 ,像 “织毛衣”.而设计软件 ,想往哪里画东西,就去哪里画 标准文档流下 有哪些微观现 ...
- python 的基础 学习第十天函数的初始
1,什么是函数,函数就是封装一个功能. 怎么定义函数. # def my_len():#def 是关键字,定义一个一个函数.#my_len():就是函数名,必须和关键字加一个空格,后面加括号和冒号.d ...
- Linux故障:linux中使用ifconfig命令查看网卡信息时显示为eth1,但是在network-scripts中只有ifcfg-eth0的配置文件,并且里面的NAME="eth0"。
linux中使用ifconfig命令查看网卡信息时显示为eth1,但是在network-scripts中只有ifcfg-eth0的配置文件,并且里面的NAME="eth0". ...
- 转:Java项目开发规范参考
Java项目开发规范参考 - KevinLee的博客 - 博客频道 - CSDN.NEThttp://blog.csdn.net/u011383131/article/details/51227860 ...
- jquery禁用a标签
jquery禁用a标签方法1 01 02 03 04 05 06 07 08 09 10 11 12 $(document).ready(function () { $("a ...
- Hadoop配置文件参数详解
core-site.xml <configuration> <property> <name>hadoop.tmp.dir</name> <val ...
- ueditor取消文本编辑器的自动拉伸高度、宽度。
1.首先引入富文本编辑器 <script type="text/javascript" src="<%=basePath%>js/ueditor/ued ...
- OGG初始化之使用数据库实用程序加载数据
Loading Data with a Database Utility 要使用数据库复制实用程序建立目标数据,您需要启动更改同步提取组,以便在数据库实用程序创建并应用数据的静态副本时提取正在进行的数 ...
- linux服务器ntp客户端配置【转】
转自:https://www.cnblogs.com/kerrycode/archive/2015/08/20/4744804.html 在Linux系统中,为了避免主机时间因为在长时间运行下所导致的 ...
- C++ 读取字符串中的数字
今天真是试了各种方法,笨方法聪明方法都有了 方法1:一个字符一个字符的读取 方法2:借助strtok实现split 适用于char 方法3:借助istringstream实现split 适用于stri ...