Filter 案例
# 140.Filter 案例
我们做一些小案例,加深对 Filter 的学习
# 过滤器案例 1_登录验证:
# 需求
- 访问资源时,要验证其是否登录
- 如果登录了,则直接放行。
- 如果没有登录,则跳转到登录页面,提示 "您尚未登录,请先登录"。
登录验证是最基础的权限控制,后续我们会学习权限框架。
# 分析
- 我们应该用过滤器,来实现验证登录的功能
- 新建一个 LoginFilter 过滤器,首先判断要访问的资源是否要登录后才能访问,如果不是则直接返回,如果是则需要判断是否登录了
- 怎么判断是否登录呢?判断 Session 中是否有 User 属性(登录后会将 User 对象放到 Session 中)
注意,有些资源是不需要登录才能访问的,不然就变成套娃了:如果登录页面需要登录后才能访问,那么没有登录的情况下,会自动跳转到登录页面,然后一直跳转,死循环了
示意图:
# 实现
package com.peterjxl.filter;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 完成登录验证的过滤器
*/
@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 0. 强转为HttpServlet,这样才能获取请求的资源路径
HttpServletRequest req = (HttpServletRequest) request;
// 1. 获取请求的资源路径
String uri = req.getRequestURI();
if (uri.contains("/login.jsp")
|| uri.contains("/loginServlet")
|| uri.contains("/css/")
|| uri.contains("/js/")
|| uri.contains("/fonts/")
|| uri.contains("/checkCodeServlet/")
){
chain.doFilter(request, response);
}else {
// 不包含,需判断是否登录
// 3. 从Session获取user
Object user = req.getSession().getAttribute("user");
if (null == user){
System.out.println("已登录");
chain.doFilter(request, response);
}else {
// 没有登录
req.setAttribute("login_msg", "您尚未登录,请登录");
req.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
}
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
}
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
# 过滤器案例 2_敏感词汇过滤
# 需求
- 对录入的数据进行敏感词汇过滤
- 敏感词汇参考《敏感词汇.txt》
- 如果是敏感词汇,替换为 ***
敏感词汇.txt:
笨蛋
坏蛋
2
# 分析
- 从 request 对象中取出参数,然后过滤掉敏感词汇;但是在 request 对象中,没有重新设置 parameter 的方法,因此我们得考虑,因此我们得对 request 对象做一个“增强”,形成一个新的、过滤好敏感词汇的 request 对象
- 放行,传递新的对象给 Servlet。
所以过滤比较简单,麻烦的是如何增强。
什么叫增强对象的功能?其实增强是一种设计模式,就是原始的对象并不能满足我们的需要,我们得给他加上一些功能。可以使用如下设计模式:
- 装饰模式,我们在学习 IO 的时候讲过了:Decorator 模式 (opens new window)
- 代理模式。我们在讲解 Nginx 的时候,已经说过什么是代理了,而设计模式中的代码模式,就相当于创建一个代理对象,这个代理对象比之前的对象加强了一些功能。这里介绍几个名词:
代理模式有两种实现模式:
- 静态代理:有一个类文件描述代理模式
- 动态代理:在内存中形成代理类,用的更多,也是本文要使用的
代理模式实现步骤:
- 代理对象和真实对象,需要实现相同的接口
- 获取代理对象:
Proxy.newProxyInstance();
,这里需要几个参数,我们后续介绍 - 使用代理对象调用方法
- 编写代码,增强方法
增强的方式:
- 增强参数列表
- 增强返回值类型
- 增强方法体执行逻辑
# 代理的实现
我们写一些代码来演示代理模式。
首先是一个接口:
package com.peterjxl.proxy;
public interface SaleComputer {
String sale(double money);
void show();
}
2
3
4
5
6
7
然后是真实对象:(Lenovo 是联想的意思,一个知名的品牌)
package com.peterjxl.proxy;
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("花了"+money+"元买了一台联想电脑...");
return "联想电脑";
}
@Override
public void show() {
System.out.println("展示电脑....");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Proxy.newProxyInstance()
接下来我们一步步演示创建代理对象,并增强方法。首先在代理模式出现之前,我们想要卖电脑,直接调用 sale 方法即可;
package com.peterjxl.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//1.创建真实对象
Lenovo lenovo = new Lenovo();
//2.调用方法
String computer = lenovo.sale(8000);
System.out.println(computer);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行结果:
花了8000.0元买了一台联想电脑...
联想电脑
2
假设我们现在是代理商,帮 Lenovo 卖电脑,这样客户不用去美国的 Lenovo 里买了,直接在国内买即可。我们从 Lenovo 里买了电脑并卖出,我们得抽成的,得怎么做?此时我们就可以用到代理对象了,增强 sale 方法
我们使用 Proxy.newProxyInstance()
来创建代理对象,该方法需要 3 个参数:
- 类加载器:
真实对象.getClass().getClassLoader()
,固定写法 - 接口数组:
真实对象.getClass().getInterfaces()
,获取真实对象实现的所有接口,也是固定写法,这样能保证代理对象和真实对象实现一样的接口 - 处理器:
new InvocationHandler()
,该InvocationHandler
接口内部有一个方法,叫做invoke
,我们就是在里面写具体如何增强对象的代码,这也算创建代理对象的重点。
至此,我们可以写出下面的代码,获取代理对象:
Object proxy_lenovo = Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
2
3
4
5
6
由于代理对象和真实对象实现了同样的接口,我们可以强制转换一下:
SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
2
3
4
5
6
将来,我们就可以调用代理对象的 sale 方法了:
String computer = proxy_lenovo.sale(8000);
System.out.println(computer);
2
# invoke
那么接下来就是重点了,如何编写增强的代码?这就涉及到 invoke 方法怎么编写了。invoke 方法是什么?简单来说就是代理对象调用的所有方法都会先执行这个方法后,再执行。什么意思呢?我们来实践下,在里面加个输出语句:
package com.peterjxl.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//1.创建真实对象
Lenovo lenovo = new Lenovo();
// 2. 动态代理增强 Lenovo对象
SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy_lenovo对象的invoke方法被执行了.....");
return null;
}
});
//3.调用方法
String computer = proxy_lenovo.sale(8000);
proxy_lenovo.show();
System.out.println(computer);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
运行结果:
proxy_lenovo对象的invoke方法被执行了.....
proxy_lenovo对象的invoke方法被执行了.....
null
2
3
可以看到我们调用了 2 个代理对象的方法,然后输出了 2 次 proxy_lenovo对象的invoke方法被执行了.....
,这说明 invoke 方法被执行了 2 次。
接下来我们讲讲 invoke 方法的 3 个参数:
public Object invoke(Object proxy, Method method, Object[] args)
每个参数的含义:
- proxy:代理对象,就是指本次创建的代理对象,一般不会用到这个参数
- method:代理对象调用的方法,被封装为的对象。例如上例中 method 分别是
sale
和show
方法 - args:代理对象调用的方法时,传递的实际参数,例如
sale
传了参数 8000.
我们可以打印下这几个参数:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.print("proxy_lenovo对象的invoke方法被执行了..... ");
System.out.print("method名字: " + method.getName());
System.out.println(" method参数: " + Arrays.toString(args));
return null;
}
2
3
4
5
6
运行结果:
proxy_lenovo对象的invoke方法被执行了..... method名字: sale method参数: [8000.0]
proxy_lenovo对象的invoke方法被执行了..... method名字: show method参数: null
null
2
3
需要注意的是,虽然传递了 Method 对象,但是具体的方法是没有被调用的!我们真实对象 Lenovo 里,sale 方法调用的时候会输出 System.out.println("花了"+money+"元买了一台联想电脑...");
,但我们之前测试代理对象的时候,这句输出语句一直没有被执行。
如果想要调用真实对象的方法,还得这样做:
metnod.invoke(lenovo, args);
并且,invoke
方法的返回值就是调用真实对象方法的返回值,所以我们可以返回该结果,这样调用代理对象的方法就能有返回值了:
Object result = method.invoke(lenovo, args);
return result;
2
# 增强方法
接下来我们就可以增强方法了,有如下增强的方式:
- 增强参数列表
- 增强返回值类型
- 增强方法体执行逻辑
# 增强参数列表
我们之前是花了 8000 从联想买了一台电脑,但我们现在是从代理商买的,代理商得涨价,所以得对参数做调整,例如涨价 10%:
if(method.getName().equals("sale")){
double money = (double) args[0];
money *= 1.1;
Object result = method.invoke(lenovo, money);
return result;
}else {
Object result = method.invoke(lenovo, args);
return result;
}
2
3
4
5
6
7
8
9
此时我们调用代理对象的话,就得话更多的钱买:
proxy_lenovo.sale(8000);
运行结果:
花了8800.0元买了一台联想电脑...
# 增强返回值
我们刚刚花了 8800 才买了一台电脑,代理商为了平衡我们的心情,想多送键盘和鼠标给我们,可以怎么做呢?修改返回值:
if(method.getName().equals("sale")){
double money = (double) args[0];
money *= 1.1;
Object result = method.invoke(lenovo, money);
return result + "键盘和鼠标";
}else {
Object result = method.invoke(lenovo, args);
return result;
}
2
3
4
5
6
7
8
9
此时返回值就是 联想电脑键盘和鼠标
# 方法体增强
比如我们想要在执行真实对象的方法前,先执行一些输出语句(或者记录一些日志),可以在调用方法前写代码:
if(method.getName().equals("sale")){
System.out.println("专车接送买电脑.....");
double money = (double) args[0];
money *= 1.1;
Object result = method.invoke(lenovo, money);
System.out.println("免费送货.....");
return result + "键盘和鼠标";
}
2
3
4
5
6
7
8
# 完整代码
package com.peterjxl.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//1.创建真实对象
Lenovo lenovo = new Lenovo();
//2.动态代理增强lenovo对象
SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断是否是sale方法
if(method.getName().equals("sale")){
//1.增强参数
double money = (double) args[0];
money = money * 0.85;
System.out.println("专车接你....");
//使用真实对象调用该方法
String obj = (String) method.invoke(lenovo, money);
System.out.println("免费送货...");
//2.增强返回值
return obj+"_鼠标垫";
}else{
Object obj = method.invoke(lenovo, args);
return obj;
}
}
});
//3.调用方法
proxy_lenovo.show();
}
}
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
# 案例 2_敏感词汇过滤实现
至此,我们就可以增强 request 方法了
- 在 init 方法里加载敏感词汇
- 创建一个代理对象,从 request 对象中取出参数,然后过滤掉敏感词汇;
- 放行,传递新的对象给 Servlet。
# 在 init 方法里加载敏感词汇
在 src 目录下新建一个《敏感词汇.txt》,注意修改文件编码格式为 GBK(因为 Windows 下默认是这个)
然后在 Filter 的 init 方法里加载敏感词汇,并放到一个 List 数组里:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
try {
// 1. 加载文件
ServletContext servletContext = filterConfig.getServletContext();
String realPath = servletContext.getRealPath("/WEB-INF/classes/敏感词汇.txt");
BufferedReader bufferedReader = null;
// 2. 读取文件
bufferedReader = new BufferedReader(new FileReader(realPath));
// 3. 将文件的每一行放到list
String line = null;
while( (line = bufferedReader.readLine()) != null) {
list.add(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 增强 getParameter
方法
我们增强 getParameter
方法:
- 创建代理对象
- 判断方法名是不是
getParameter
,如果不是则执行方法并返回return method.invoke(servletRequest, args);
- 如果是,则获取该值,并做过滤处理,将过滤后的值返回
代码:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest proxy_req = (ServletRequest) Proxy.newProxyInstance(servletRequest.getClass().getClassLoader(), servletRequest.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强getParameter方法
if (method.getName().equals("getParameter")){
String value = (String) method.invoke(servletRequest, args);
if( null != value){
for(String str : list){
if(value.contains(str)){
value = value.replaceAll(str, "***");
}
}
}
return value; //返回过滤后的字符
}
return method.invoke(servletRequest, args);
}
});
filterChain.doFilter(proxy_req, servletResponse);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 完整代码
package com.peterjxl.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
/**
* 敏感词汇过滤器
*/
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
private List<String> list = new ArrayList<>(); //敏感词汇集合
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest proxy_req = (ServletRequest) Proxy.newProxyInstance(servletRequest.getClass().getClassLoader(), servletRequest.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强getParameter方法
if (method.getName().equals("getParameter")){
String value = (String) method.invoke(servletRequest, args);
if( null != value){
for(String str : list){
if(value.contains(str)){
value = value.replaceAll(str, "***");
}
}
}
return value; //返回过滤后的字符
}
return method.invoke(servletRequest, args);
}
});
filterChain.doFilter(proxy_req, servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
try {
// 1. 加载文件
ServletContext servletContext = filterConfig.getServletContext();
String realPath = servletContext.getRealPath("/WEB-INF/classes/敏感词汇.txt");
BufferedReader bufferedReader = null;
// 2. 读取文件
bufferedReader = new BufferedReader(new FileReader(realPath));
// 3. 将文件的每一行放到list
String line = null;
while( (line = bufferedReader.readLine()) != null) {
list.add(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
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
# 测试
我们新建一个 SensitiveWordsFilterTest
类,用来测试 getParameter 方法
package com.peterjxl.filter;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet("/sensitiveWordsFilterTest")
public class SensitiveWordsFilterTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
System.out.println("name = " + name);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
重启 Tomcat,访问
http://localhost:8080/hello/sensitiveWordsFilterTest?name=张三大坏蛋
可以看到控制台输出了
name = 张三大***
这里只增强了 getParameter
方法,其实还有一些获取参数的方法要增强,就不一一实现了:
req.getParameterMap();
req.getParameterValues()
2
# 总结
代理是一个非常重要的概念,后续我们学习很多框架,其底层都是用了代理的方式。