版权声明:本博客所有文章均采用 CC BY 3.0 CN 许可协议。转载请注明出处! https://blog.csdn.net/u012228718/article/details/86688909
文章目录
过滤器详解
概述
Filter,过滤器,用于在servlet之外对request 和response 进行修改。Filter 有一个 FilterChain 的概念,一个FilterChain 包括多个 Filter。客户端请求 request在抵达servlet 之前会经过 FilterChain 里面所有的 Filter,服务器响应 response 从servlet 抵达客户端浏览器之前也会经过 FilterChain 里面所有的 Filter 。过程如图所示:
Filter 的实现
实现自定义的 Filter 需要满足一下条件:
- 实现 javax.servlet.Filter 接口,实现其 init、doFilter、destroy 三个方法
- 实现在web.xml中的配置
javax.servlet.Filter
接口
- Filter 接口有三个方法:这三个方法反应了 Filter 的生命周期
- init:只会在 web 程序加载的时候调用,即启动如tomcat等服务器时调用。一般负责加载配置的参数
- destroy:web程序卸载的时候调用。一般负责关闭某些容器等
- doFilter:每次客户端请求都会调用一次。Filter 的所有工作基本都集中在该方法中进行
- 自定义 Filter 实现
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String name = filterConfig.getInitParameter("name");
System.out.println("获取过滤器的初始化参数: " + name);
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
//上下文路径
String contextPath = request.getContextPath();
//得到访问的servlet或者jsp的路径
String servletPath = request.getServletPath();
System.out.println("上下文路径:" + contextPath);
System.out.println("访问的servlet或者jsp的路径 : " + servletPath);
chain.doFilter(req, resp);
}
@Override
public void destroy() {
}
}
Filter 的配置
-
配置 Filter:每个过滤器需要配置在web.xml中才能生效,一个 Filter 需要配置
filter
和filter-mapping
标签 -
Filter
标签:配置 Filter 名称,实现类以及初始化参数。可以同时配置多个初始化参数 -
filter-mapping
标签:配置什么规则下使用这个Filter
-
url-pattern
:配置url的规则,可以配置多个,也可以使用通配符*
。例如/jsp/*
适用于本ContextPath下以“/jsp/ ”开头的所有servlet路径,*.do
适用于所有以“ .do”结尾的servlet路径 -
dispatcher
:配置到达servlet的方式,可以同时配置多个。有四种取值:默认为REQUEST。它们的区别是- REQUEST :表示仅当直接请求servlet时才生效
- FORWARD :表示仅当某servlet通过forward转发到该servlet时才生效
- INCLUDE :Jsp中可以通过
<jsp:include/>
请求某 servlet, 只有这种情况才有效 - ERROR :Jsp中可以通过
<%@page errorPage="error.jsp" %>
指定错误处理页面,仅在这种情况下才生效
-
url-pattern
和dispatcher
是且的关系,只有满足两个条件配置,该Filter 才能生效
- 总结:一个Web程序可以配置多个Filter,访问有先后顺序,
filter-mapping
配置在前面的Filter 执行要早于配置在后面的Filter
<!--Filter-->
<filter>
<filter-name>CustomFilter</filter-name>
<filter-class>com.learning.servlet2x.filter.CustomFilter</filter-class>
<init-param>
<param-name>name</param-name>
<param-value>Sam</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CustomFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
常用过滤器
字符编码的 Filter
- 代码如下
public class CharacterEncodingFilter implements Filter {
private String characterEncoding;
/**
* 是否启用
*/
private boolean enabled;
@Override
public void init(FilterConfig config) throws ServletException {
// 获取配置好的参数,
// 配置好的字符编码
characterEncoding = config.getInitParameter("characterEncoding");
//是否启用
enabled = "true".equalsIgnoreCase(config.getInitParameter("enabled"));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//设置字符编码
if (enabled && characterEncoding != null) {
request.setCharacterEncoding(characterEncoding);
response.setCharacterEncoding(characterEncoding);
}
//调用下一个过滤器
chain.doFilter(request, response);
}
@Override
public void destroy() {
//注销的时候,设为空
characterEncoding = null;
}
}
- web.xml 配置
<!-- 编码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.learning.servlet2x.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>characterEncoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>enabled</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
防盗链 Filter
- 防盗链需要使用到请求头
Referer
,该Filter的配置仅对/referer/
下面的所有资源有效。 - 代码如下
public class RefererFilter implements Filter {
@Override
public void init(FilterConfig config) {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 必须的
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 禁止缓存
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
// 链接来源地址,通过获取请求头 referer 得到
String referer = request.getHeader("referer");
System.out.println("获取的来源--->: " + referer);
// 本站点访问,则有效
if (referer == null || !referer.contains(request.getServerName())) {
//如果 链接地址来自其他网站,则返回错误信息
request.getRequestDispatcher("/error.gif").forward(request, response);
} else {
// 正常访问
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
- 配置如下
<!--防盗链过滤器 -->
<filter>
<filter-name>RefererFilter</filter-name>
<filter-class>com.learning.servlet2x.filter.RefererFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RefererFilter</filter-name>
<url-pattern>/referer/*</url-pattern>
</filter-mapping>
权限校验 Filter
- 简单实现如下
public class PrivilegeFilter implements Filter {
private Properties properties = new Properties();
@Override
public void init(FilterConfig config) throws ServletException {
// 从 初始化参数 中获取权 限配置文件 的位置
String file = config.getInitParameter("file");
String realPath = config.getServletContext().getRealPath(file);
try {
properties.load(new FileInputStream(realPath));
} catch (Exception e) {
config.getServletContext().log("读取权限控制文件失败。", e);
}
}
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
// 获取访问的路径,例如:admin.jsp
String requestURI = request.getRequestURI().replace(request.getContextPath() + "/", "");
// 获取 action 参数,例如:add
String action = req.getParameter("action");
action = action == null ? "" : action;
// 拼接成 URI。例如:log.do?action=list
String uri = requestURI + "?action=" + action;
// 从 session 中获取用户权限角色。
String role = (String) request.getSession(true).getAttribute("role");
role = role == null ? "guest" : role;
boolean authEntificated = false;
// 开始检查该用户角色是否有权限访问 uri
for (Object obj : properties.keySet()) {
String key = ((String) obj);
// 使用正则表达式验证 需要将 ? . 替换一下,并将通配符 * 处理一下
if (uri.matches(key.replace("?", "\\?").replace(".", "\\.")
.replace("*", ".*"))) {
// 如果 role 匹配
if (role.equals(properties.get(key))) {
authEntificated = true;
break;
}
}
}
if (!authEntificated) {
System.out.println("您无权访问该页面。请以合适的身份登陆后查看。");
}
// 继续运行
chain.doFilter(req, res);
}
@Override
public void destroy() {
properties = null;
}
}
- 配置
<!-- 权限过滤器 -->
<filter>
<filter-name>privilegeFilter</filter-name>
<filter-class>com.learning.servlet2x.filter.PrivilegeFilter</filter-class>
<init-param>
<param-name>file</param-name>
<param-value>/filter/privilege.properties</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>privilegeFilter</filter-name>
<url-pattern>/privilege/*</url-pattern>
</filter-mapping>
- 权限暂时配置在文件中,方便测试
GZIP 压缩 Filter
Servlet 实现压缩返回
public class GzipServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//实现压缩
String tDate = "准备被压缩的数据";
System.out.println("压缩前的数据大小: " + tDate.getBytes().length);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(bout);
gout.write(tDate.getBytes());
gout.flush();
gout.finish();
gout.close();//写到字节数组流中
// 得到压缩后的数据
byte[] gzip = bout.toByteArray();
System.out.println("压缩后的数据大小: " + gzip.length);
// 通知浏览器数据采用压缩格式
// 压缩格式
resp.setHeader("Content-Encoding", "gzip");
// 压缩数据的长度
resp.setHeader("Content-Length", gzip.length + "");
resp.getOutputStream().write(gzip);
}
}
GZIP 压缩 Filter
- GZIP 压缩的核心是 JDK 自带的压缩数据的类,GZIPOutputStream
- 响应头:Content-Encoding 和 Content-Length
- GzipResponseWrapper 类为自定义的 Response 类,内部对输出的内容进行 GZIP 的压缩
- 代码如下
public class GzipFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 获取浏览器支持的压缩格式
String acceptEncoding = request.getHeader("Accept-Encoding");
System.out.println("Accept-Encoding: " + acceptEncoding);
if (acceptEncoding != null && acceptEncoding.toLowerCase().contains("gzip")) {
// 如果客户浏览器支持 GZIP 格式, 则使用 GZIP 压缩数据
GzipResponseWrapper gzipResponse = new GzipResponseWrapper(response);
chain.doFilter(request, gzipResponse);
// 输出压缩数据
gzipResponse.getOutputStream();
gzipResponse.finishResponse();
} else {
// 否则, 不压缩
chain.doFilter(request, response);
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void destroy() {
}
}
public class GzipResponseWrapper extends HttpServletResponseWrapper {
/**
* 默认的 response
*/
private HttpServletResponse response;
/**
* 自定义的 outputStream, 执行close()的时候对数据压缩,并输出
*/
private GzipOutputStream gzipOutputStream;
/**
* 自定义 printWriter,将内容输出到 GZipOutputStream 中
*/
private PrintWriter writer;
public GzipResponseWrapper(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (gzipOutputStream == null) {
gzipOutputStream = new GzipOutputStream(response);
}
return gzipOutputStream;
}
@Override
public PrintWriter getWriter() throws IOException {
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(new GzipOutputStream(response), "UTF-8"));
}
return writer;
}
/**
* 压缩后数据长度会发生变化 因此将该方法内容置空
*
* @param contentLength
*/
@Override
public void setContentLength(int contentLength) {
}
@Override
public void flushBuffer() throws IOException {
gzipOutputStream.flush();
}
public void finishResponse() throws IOException {
if (gzipOutputStream != null) {
gzipOutputStream.close();
}
if (writer != null) {
writer.close();
}
}
}
public class GzipOutputStream extends ServletOutputStream {
private HttpServletResponse response;
/**
* JDK 自带的压缩数据的类
*/
private GZIPOutputStream gzipOutputStream;
/**
* 将压缩后的数据存放到 ByteArrayOutputStream 对象中
*/
private ByteArrayOutputStream byteArrayOutputStream;
public GzipOutputStream(HttpServletResponse response) throws IOException {
this.response = response;
byteArrayOutputStream = new ByteArrayOutputStream();
gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
}
@Override
public void write(int b) throws IOException {
gzipOutputStream.write(b);
}
@Override
public void close() throws IOException {
// 压缩完毕 一定要调用该方法
gzipOutputStream.finish();
// 将压缩后的数据输出到客户端
byte[] content = byteArrayOutputStream.toByteArray();
// 设定压缩方式为 GZIP, 客户端浏览器会自动将数据解压
response.addHeader("Content-Encoding", "gzip");
response.addHeader("Content-Length", Integer.toString(content.length));
// 输出
ServletOutputStream out = response.getOutputStream();
out.write(content);
out.close();
}
@Override
public void flush() throws IOException {
gzipOutputStream.flush();
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
gzipOutputStream.write(b, off, len);
}
@Override
public void write(byte[] b) throws IOException {
gzipOutputStream.write(b);
}
}
- 配置
<!-- 压缩过滤器 -->
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>com.learning.servlet2x.filter.gzip.GzipFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>