一、概念

FilterJava Web的三大组件之一。Java Web三大组件分别是ServletFilterListener

Filter的作用一般用于完成通用的操作。例如:登录验证、统一编码处理、敏感字符过滤…在实际开发中,过滤器就是对Web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理,通常都是用来拦截request进行处理,或者对返回的response进行拦截处理。其处理流程见下图:

Filter执行原理

拦截request可以简单理解为,在客户端向后端发送请求时,我们需要对其请求加一些”修饰”,将”修饰”后的请求带到后端。其中这个”修饰”是需要在这个请求的过程中完成的,这里因为是通用操作,可能是对所有的request进行”修饰”,所以并没有在客户端进行编写,否则当再加入一个request请求时,我们又要编写对应的规则,因此我们借用过滤器在请求过程中,对我们需要改写的request进行”修饰”。

其中,这里的”修饰”就可以理解为在原有的request请求中,再加入一些”修改”。例如:在Servlet中过多字符集编码发生变化需要修改时,你是选择对每个Servlet都进行修改,还是会选择一个通用的”规则”,来自动判断帮我们进行修改呢?而这里通用的”规则”就是Filter,我们可以把这些通用的字符集编码配置等工作放在Filter中,由Filter在请求过程中或返回过程中帮我们来实现。

二、过滤器的快速使用

其中过滤增强的方法写在doFilter中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@WebFilter("/*") //访问所有资源之前,都会执行该过滤器
public class FilterDemo1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

'''对request请求消息进行增强'''
filterChain.doFilter(servletRequest,servletResponse); // 修饰完以后进行放行
'''放行回来后,对response响应消息的增强'''

}

@Override
public void destroy() {

}
}

filterChain.doFilter(servletRequest,servletResponse) 用于对我们的请求进行放行。doFilter中的参数就是requestresponse;带着请求消息和响应消息。

2.1 执行流程

doFilter放行前,对request请求进行增强,然后带着增强后的requeset进入到doFilter;从doFilter方法出来后,将后台返回的response进行增强,返回给前端。

2.2 过滤器生命周期方法

  1. init:在服务器启动后,会创建Filter对象,然后调用init方法。只执行一次。用于加载资源。
  2. doFilter:每一次请求被拦截资源时,会执行。执行多次。
  3. destroy:在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源。

三、过滤器配置

3.1 web.xml配置

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>demo1</filter-name>
<filter-class>cn.itcast.web.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
<filter-name>demo1</filter-name>
<!-- 拦截路径 -->
<url-pattern>/*</url-pattern>
</filter-mapping>

3.2 拦截路径设置

  1. 具体资源路径: /index.jsp 只有访问index.jsp资源时,过滤器才会被执行
  2. 拦截目录: /user/* 访问/user下的所有资源时,过滤器都会被执行
  3. 后缀名拦截: *.jsp 访问所有后缀名为jsp资源时,过滤器都会被执行
  4. 拦截所有资源:/* 访问所有资源时,过滤器都会被执行

3.3 注解配置

通过设置dispatcherTypes属性,设置拦截方式,即资源被访问的方式。

  1. REQUEST:默认值。浏览器直接请求资源
  2. FORWARD:转发访问资源(只有在转发访问资源时,才会被拦截器所拦截,直接访问反而不会了)
  3. INCLUDE:包含访问资源
  4. ERROR:错误跳转资源
  5. ASYNC:异步访问资源

以上是数组的形式,可以同时同时填写多个条件。

web.xml中的配置在:

设置<dispatcher></dispatcher>标签即可

3.4 过滤器链

即同时配置多个过滤器,多个过滤器对同一路径进行拦截时。

类似于栈的形式先进后出,例如有两个过滤器 A和B。

执行顺序即为:过滤器A –> 过滤器B –> 执行资源 –> 过滤器B –> 过滤器A

执行资源前的是对request的增强,执行资源后的是对response的增强

过滤器执行的先后顺序:

  1. 注解配置:按照类名的字符串比较规则比较,值小的先执行.
    • 如: AFilter 和 BFilter,AFilter就先执行了。
  2. web.xml配置: <filter-mapping>谁定义在上边,谁先执行.

四、过滤器基本案例实战

4.1 登录验证(权限控制)

对这一讲中–Java初试MVC及三层架构的登录进行验证。

要求:

  1. 如果已经登录,则直接放行。
  2. 如果没有登录,则跳转到登录页面,并提示信息。

思路:

设置LoginFilter。判断当前用户是否登录(判断Session中是否有User)。

  • 如果用户已经登录,则对其进行放行
  • 如果没有登录,提示用户进行先进行登录。

注意:先排除是否是登录相关的资源。

  • 如果是,直接放行;
  • 不是,判断是否登录。

代码实现:

登录权限的案例代码实现
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
@WebFilter("/*")
public class LoginFilter implements Filter {
public void destroy() {
}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 强转 -- 转换成子接口HttpServletRequest;也可以将ServletResponse转换成子接口HttpServletResponse
HttpServletRequest request = (HttpServletRequest) req;
String uri = request.getRequestURI(); // 获取请求路径
// 排除不需要过滤的资源路径
if (uri.contains("/login.jsp") || uri.contains("/css/") || uri.contains("/fonts/") || uri.contains("/img/") || uri.contains("/js/") || uri.contains("/checkCode") || uri.contains("/loginUser")) {
chain.doFilter(req, resp);
} else {
HttpSession session = request.getSession(); // 获取session
Object user = session.getAttribute("adminUser"); // 从session中获取用户登录信息
if (user != null) { // 不为空说明已经登录
chain.doFilter(req, resp);
} else { // 否则提示用户进行登录,并转发至登录首页
request.setAttribute("adminUser_error", "请先登录");
request.getRequestDispatcher("/login.jsp").forward(request, resp);
}
}

}

public void init(FilterConfig config) throws ServletException {

}

}

4.2 敏感字符替换

对数据进行敏感词汇过滤,然后用*进行替换。

重点:

  • 将修改完的文字再设置回request域中,放行,继而传递至doFilter中,将请求数据传递给后台。使用新的request对象,图中是蓝色的request对象。通过代理模式实现对象的增强。

敏感词汇过滤

4.2.1 代理模式介绍

4.2.1.1 设计模式:一些通用的解决固定问题的方式。

代理模式:

  • 概念

    1. 真实对象:被代理的对象(可以理解为原厂商)
    2. 代理对象:代理真实对象的对象。(可以理解为经销商–中间厂商)
    3. 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
  • 实现方式

    • 静态代理:有一个类文件描述代理模式

    • 动态代理:在内存中形成代理类

  • 实现步骤:

    1. 代理对象和真实对象实现相同的接口
    2. 代理对象 = Proxy.newProxyInstance();
    3. 使用代理对象调用方法
    4. 增强方法
  • 增强方式:

    1. 增强参数列表:获取具体的参数,对参数进行增强(替换参数等操作)
    2. 增强返回值类型:对返回值进行增强(即对返回值的操作)
    3. 增强方法体执行逻辑:在方法体中增强具体的逻辑操作
4.2.1.2 动态代理

特点:字节码随用随创建,随用随加载

作用:不修改源码的基础上对方法增强

分类:

  • 基于接口的动态代理
  • 基于子类的动态代理
1. 基于接口的动态代理

涉及的类:Proxy

提供者:JDK官方

  1. 如何创建代理对象:
  • 使用Proxy类中的newProxyInstance方法

创建代理对象的要求:

  • 被代理类最少实现一个接口,如果没有则不能使用
  1. newProxyInstance方法的参数:
  • ClassLoader:类加载器
    • 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。(固定写法)
  • Class[]:字节码数组
    • 它是用于让代理对象和被代理对象有相同方法。(固定写法)
  • InvocationHandler:用于提供增强的代码
    • 它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
2. 基于子类的动态代理

涉及的类:Enhancer

提供者:第三方cglib

如何创建代理对象:

  • 使用Enhancer类中的create方法

创建代理对象的要求:

  • 被代理类不能是最终类

create方法的参数:

  • Class:字节码

    • 它是用于指定被代理对象的字节码
  • Callback:用于提供增强的代码

    它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类。

    此接口的实现类都是谁用谁写(我们自己用,那便是我们自己写)

    我们一般写的都是该接口的子接口的实现类:MethodInterceptor

4.2.1.3 案例
1. 基于接口的动态代理实例

定义接口:

1
2
3
4
public interface SaleComputer {
String sale(double money);
void show();
}

定义真实对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 真实类 -- 作为真实对象
*/
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {

System.out.println("花了" + money + "元买了一条电脑");
return "联想电脑";
}
@Override
public void show() {
System.out.println("展示电脑");
}
}

代理对象的使用逻辑:

代理对象的使用逻辑
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
39
40
41
42
43
44
45
46
47
48
49
50
public class ProxyTest {

public static void main(String[] args) {
Lenovo lenovo = new Lenovo();

//2.动态代理增强Lenovo对象
/**
* 三个参数:
* 1.类加载器:真实对象.getClass().getClassLoader()
* 2.接口数组(保证代理对象和真实对象实现相同的接口):真实数组.getClass().getInterfaces() -- 真实对象的接口
* 3.处理器:new InvocationHandler() 这是所关心的,即我们的代理方式
* 4.这里的proxy_lenovo即为代理对象
*/

// 创建代理对象 -- 使代理对象和真实对象实现相同的接口(相同接口类型)
SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
/*
代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
参数:
1.proxy:代理对象
2.method:代理对象调用的方法。被封装为的对象
3.args:代理对象调用的方法时,传递的实际参数(调用方法中的参数列表)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println("该方法被执行了...");
// System.out.println(method.getName());
// System.out.println(args[0]);
// 1.增强参数
if (method.getName().equals("sale")) {
double money = (double) args[0];
money = money * 0.85; // 对参数列表进行增强
// 使用真实对象调用该方法
String obj = (String) method.invoke(lenovo, money);
// 2. 增强返回值
return obj+"_鼠标垫";
} else {
Object obj = method.invoke(lenovo, args);
return obj;
}

}
});

// 3.调用方法
String computer = proxy_lenovo.sale(8000); // 确定了 方法名 -- sale;参数列表 -- {8000}
// System.out.println(computer);
// proxy_lenovo.show();
}
}
2. 基于子类的动态代理实例
被代理对象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 一个生产者
*/
public class Producer {

/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}

/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
代理对象类
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
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售 -- 若是的话,则对方法进行增强
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}

4.2.2 敏感词汇过滤代码

逻辑:获取request中带有请求参数的方法,例如:getParametergetParameterMapgetParameterValue等。对其方法的返回值进行判定,是否存在敏感值。如果存在,则对返回值进行增强。

敏感词汇过滤代码
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
public void destroy() {
}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 真实对象和代理对象都实现 ServletRequest 接口
ServletRequest request = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (method.getName().equals("getParameter")) { // 存在getParameter方法
String value = (String) method.invoke(req, args);
if (value != null) {
for (String word : list) {
if (value.contains(word)) {
value = value.replaceFirst(word, "**"); // 获取新的value
}
}
}
return value;
} else if (method.getName().equals("getParameterMap")) { // 存在getParameterMap方法
Map<String, String[]> parameterMap = (Map<String, String[]>) method.invoke(req, args);
if (parameterMap != null && !parameterMap.isEmpty()) {
Set<String> keySet = parameterMap.keySet();
for (String key : keySet) {
String[] values = parameterMap.get(key);
for (int i = 0; i < values.length; i++) {
for (String word : list) {
if (values[i].contains(word)) {
parameterMap.get(key)[i] = values[i].replaceAll(word, "**");
}
}
}

}
}
return parameterMap;
} else if (method.getName().equals("getParameterValues")) {
String[] values = (String[]) method.invoke(req, args);
if (values != null && values.length > 0) {

for (int i = 0; i < values.length; i++) {
for (String word : list) {
if (values[i].contains(word)) {
values[i] = values[i].replaceAll(word, "**");
}
}
}
}
return values;
}

// 如果不是以上方法,就返回客户端传递的req对象(旧对象)原来的方法返回值即可,而以上方法传递的是新的req对象方法返回值(经过增强后的返回值) -- 对返回值进行了增强
return method.invoke(req, args);
}
});

chain.doFilter(request, resp);
}

private List<String> list = new ArrayList<String>();

/*
用于加载敏感词汇表,存放在列表中。init初始时加载 -- 只加载一次
*/
public void init(FilterConfig config) throws ServletException {
try {
// 获取真实路径
ServletContext servletContext = config.getServletContext();
String realPath = servletContext.getRealPath("/WEB-INF/classes/word");
// 读取文件
BufferedReader br = new BufferedReader(new FileReader(realPath));
// 将文件添加到集合当中
String line = null;
while ((line = br.readLine()) != null) {
list.add(line);
}
br.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

}

参考文献

[1] Java Web之过滤器(Filter)

[2] Java过滤器–百度百科

[3] 黑马讲义


Comment