Servlet技术之过滤器

在 Servlet 规范 2.3 中定义了过滤器,它能够对 Servlet 容器的请求和响应对象进行检查和修改。Servlet 过滤器本身并不生成请求和响应对象,只是提供过滤功能。
Servlet 过滤器能够在 Servlet 被调用之前检查 Request 对象,并修改 Request Header 和 Request 内容;在 Servlet 被调用之后检查 Response 对象,修改 Response Header 和 Response 的内容。

常用的应用如:设置统一编码、设置静态资源的缓存时间、控制权限访问、敏感词拦截等

Servlet 过滤器可以过滤的 Web 组件包括 Servlet,JSP 和 HTML 等文件。Filter 类似于 IO 中的过滤流,实现也类似于 Servlet。

了解Filter

使用过滤器非常的简单,新建一个类,实现 Filter 接口就可以了,也就是需要实现三个方法,从名字来看,有两个是和生命周期有关

  • init(FilterConfig filterConfig)
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  • destroy()

说说其生命周期,随服务器(或者说随 web 应用)的启动而执行 init 方法,随服务器的关闭而 执行 destroy 方法,也就是生命周期和服务器保持一致,某些只需要执行一次的方法可以放在 init 里面啦!

拦截处理的操作一般都写在 doFilter 方法中,在这里可以对 request 和 response 进行一些处理,不过 ServletRequest 和 ServletResponse 一般需要转换成具体的 Servlet 实现对于的对象,如:HttpServletRequest 和 HttpServletResponse。

web 服务器在调用 doFilter 方法时,会传递一个 filterChain 对象进来,filterChain 对象是 filter 接口中最重要的一个对象,它也提供了一个 doFilter 方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则 web 服务器就会调用 web 资源的 service 方法,即 web 资源就会被访问,否则 web 资源不会被访问。

在一个 web 应用中,可以开发编写多个 Filter,这些 Filter 组合起来称之为一个 Filter 链。不过需要注意下顺序的问题,web 服务器根据 Filter 在 web.xml 文件中的注册顺序,决定先调用哪个 Filter
比如如果有两个过滤器,那么顺序类似:用户发起请求 –> 请求被 Filter1 拦截 –> 请求被 Filter2 拦截 –> 响应被 Filter2 拦截 –> 响应被 Filter1 拦截 –> 响应返回给用户
一般把 Filter 配置在所有的 Servlet 之前。

通过注解配置 filter 时,没有专门的指令来配置 filter 执行顺序,确定 filter 执行的先后是根据 filter 类名的字母表顺序。
这非常不便于以后的维护,所以用不用,自己决定吧….

设置全站编码的栗子

这样是为了解决乱码的问题,我们的汉字太吊,乱码问题时常让我爆炸….
通过简单的栗子,应该是学习或者复习比较快速的一个方法
正好这个问题可以使用过滤器来解决,以后再也不用担心乱码的问题了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MainFilter implements Filter {
private FilterConfig mConfig;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
mConfig = filterConfig;
System.out.println("初始化成功!");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 获取配置文件中设置的编码
String charset = mConfig.getInitParameter("charset");
if (charset.isEmpty())
charset = "utf-8";

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

req.setCharacterEncoding(charset);
// 下面两句是设置输出的编码,不设置也可以,因为一般不会在 Servlet 中进行输出,而是跳到 JSP 进行输出
resp.setCharacterEncoding(charset);
resp.setContentType("text/html;charset=UTF-8");

// 放行
chain.doFilter(req, resp);
}

@Override
public void destroy() {
System.out.println("关闭....");
}
}

要想生效,还需要在 web.xml 文件中进行响应的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<filter>
<filter-name>MainFilter</filter-name>
<filter-class>filter.MainFilter</filter-class>
<!-- 配置初始化参数 -->
<init-param>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>

<!-- 一个过滤器可以设置多个 mapping -->
<filter-mapping>
<filter-name>MainFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

可以看到,在 XML 中配置的初始化参数可以在 init 方法中拿到,就是 FilterConfig 这个对象;可以通过 getInitParameter 来获取设置的值

但是呢,这样只能解决 Post 方式提交的乱码问题,Get 方式的乱码还是会有,想要完全的解决需要进行增强 Request 的方式,在获取参数的时候进行判断,分别对 Get 和 Post 进行不同的处理,参考下面增强 Request 目录下的内容,关键代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public String getParameter(String name) {
String value = mRequest.getParameter(name);
// 非 Get 方式直接返回,不处理
if (!mRequest.getMethod().equalsIgnoreCase("get")) {
return value;
}
if (value == null) {
return null;
}

try {
return value = new String(value.getBytes("iso-8859-1"),mRequest.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}

控制静态资源缓存

这次使用注解的方式来配置,如果配置多个过滤器的话,顺序方面会比较头疼,按名字的字母来排序,上面也提到过这个问题,但是使用注解确实方便了很多:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@WebFilter(filterName = "CacheFilter",
urlPatterns = {"*.jpg", "*.png", "*.css", "*.js"},
initParams = {
@WebInitParam(name = "jpg", value = "10"),
@WebInitParam(name = "css", value = "10"),
@WebInitParam(name = "js", value = "10")
})
public class CacheFilter implements javax.servlet.Filter {
private FilterConfig mConfig;

public void destroy() {}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletResponse resp = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;

// 获取到用户访问的资源
String uri = req.getRequestURI();
// 获取配置的资源的缓存时间
int expires = 0;
if (uri.endsWith(".jpg")) {
expires = Integer.parseInt(this.mConfig.getInitParameter("jpg"));
} else if (uri.endsWith(".js")) {
expires = Integer.parseInt(this.mConfig.getInitParameter("js"));
} else if (uri.endsWith(".css")) {
expires = Integer.parseInt(this.mConfig.getInitParameter("css"));
}
// 设置缓存时间,注意格式
resp.setDateHeader("expires", System.currentTimeMillis() + expires * 60 * 1000);
// 放行
chain.doFilter(req, resp);
}

public void init(FilterConfig config) throws ServletException {
mConfig = config;
}

}

这样就没有必要去设置 web.xml 文件了,上面的过滤器只会拦截设置的那些静态文件的请求,比如 JSP 中有个 img 标签,当加载 JSP 页面的时候,JSP 本身请求不会被拦截,但是发送的图片请求会被捕获到,然后设置个缓存时间
还有就是:刷新代表着重新执行上次操作,所以就算设置了缓存,刷新操作还是会重新进行获取

增强Request和Response

前面也说过,增强功能一般有三种方式:1、继承 2、装饰/包装 3、动态代理

第一种用的基本很少,因为在服务器相关的对象中往往都封装着很多的信息,如果使用继承的话这些信息就会丢失
第二种装饰模式确实可以,但是方法比较多的话会很复杂;即使 Apache 给提供了 HttpServletRequestWrapper 类来方便开发,这个类默认就是经过装饰了的,但是内部全部调用的是构造函数中传入的构件中的方法,我们用的时候直接继承自它,需要增强什么方法再单独覆盖,这样就能大大简化步骤了
第三种还没有看到,挖个坑,以后单独开一篇

关于 Request 的具体的应用可以是处理编码来解决乱码问题,敏感词过滤审核,进行数据转义防止 XXS 等
关于 Response 的具体应用最直接的就是用来搞压缩,大大节省流量啊,gzip 主要是压缩文本数据,还有就是缓存数据,在过滤器定义一个 Map 缓存那些不经常更新的数据(byte[])

具体代码就不写了,有点长,可以参考我的笔记:Github

其他补充

存下在过滤器中常用的一些对象使用的套路

获取ServletContext

通过 request 或者 filterConfig 都可以得到 ServletContext 对象:

1
2
3
4
HttpServletRequest req = (HttpServletRequest)request;
ServletContext context = req.getSession().getServletContext();

ServletContext context = filterConfig.getServletContext();

web.xml节点配置

除了上面那几个,补充两个比较常用的

  • <servlet-name>
    指定过滤器所拦截的Servlet名称。
  • <dispatcher>
    拦截方式,可以设定五个值(可以配置多个,默认 REQUEST):
    REQUEST:当用户直接访问页面时,Web 容器将会调用过滤器。如果目标资源是通过 RequestDispatcher 的 include()forward() 方法访问时,那么该过滤器就不会被调用。
    INCLUDE:如果目标资源是通过 RequestDispatcher 的 include() 方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
    FORWARD:如果目标资源是通过 RequestDispatcher 的 forward() 方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
    ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
    ASYNC:异步处理的请求

最后

一些代码可以参考我的笔记:Github

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~