一、概念
Filter
是Java Web
的三大组件之一。Java Web
三大组件分别是Servlet
、Filter
、Listener
。
Filter
的作用一般用于完成通用的操作。例如:登录验证、统一编码处理、敏感字符过滤…在实际开发中,过滤器就是对Web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理,通常都是用来拦截request
进行处理,或者对返回的response
进行拦截处理。其处理流程见下图:
拦截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
中的参数就是request
,response
;带着请求消息和响应消息。
2.1 执行流程
在doFilter
放行前,对request
请求进行增强,然后带着增强后的requeset
进入到doFilter
;从doFilter
方法出来后,将后台返回的response
进行增强,返回给前端。
2.2 过滤器生命周期方法
init
:在服务器启动后,会创建Filter
对象,然后调用init
方法。只执行一次。用于加载资源。
doFilter
:每一次请求被拦截资源时,会执行。执行多次。
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 拦截路径设置
- 具体资源路径: /index.jsp 只有访问index.jsp资源时,过滤器才会被执行
- 拦截目录: /user/* 访问/user下的所有资源时,过滤器都会被执行
- 后缀名拦截: *.jsp 访问所有后缀名为jsp资源时,过滤器都会被执行
- 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
3.3 注解配置
通过设置dispatcherTypes属性,设置拦截方式,即资源被访问的方式。
REQUEST
:默认值。浏览器直接请求资源
FORWARD
:转发访问资源(只有在转发访问资源时,才会被拦截器所拦截,直接访问反而不会了)
INCLUDE
:包含访问资源
ERROR
:错误跳转资源
ASYNC
:异步访问资源
以上是数组的形式,可以同时同时填写多个条件。
在web.xml
中的配置在:
设置<dispatcher></dispatcher>
标签即可
3.4 过滤器链
即同时配置多个过滤器,多个过滤器对同一路径进行拦截时。
类似于栈的形式先进后出,例如有两个过滤器 A和B。
执行顺序即为:过滤器A –> 过滤器B –> 执行资源 –> 过滤器B –> 过滤器A
执行资源前的是对request
的增强,执行资源后的是对response
的增强
过滤器执行的先后顺序:
- 注解配置:按照类名的字符串比较规则比较,值小的先执行.
- 如: AFilter 和 BFilter,AFilter就先执行了。
- web.xml配置: <filter-mapping>谁定义在上边,谁先执行.
四、过滤器基本案例实战
4.1 登录验证(权限控制)
对这一讲中–Java初试MVC及三层架构的登录进行验证。
要求:
- 如果已经登录,则直接放行。
- 如果没有登录,则跳转到登录页面,并提示信息。
思路:
设置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 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(); Object user = session.getAttribute("adminUser"); 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 设计模式:一些通用的解决固定问题的方式。
代理模式:
概念
- 真实对象:被代理的对象(可以理解为原厂商)
- 代理对象:代理真实对象的对象。(可以理解为经销商–中间厂商)
- 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
实现方式
静态代理:有一个类文件描述代理模式
动态代理:在内存中形成代理类
实现步骤:
- 代理对象和真实对象实现相同的接口
- 代理对象 =
Proxy.newProxyInstance();
- 使用代理对象调用方法
- 增强方法
增强方式:
- 增强参数列表:获取具体的参数,对参数进行增强(替换参数等操作)
- 增强返回值类型:对返回值进行增强(即对返回值的操作)
- 增强方法体执行逻辑:在方法体中增强具体的逻辑操作
4.2.1.2 动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
1. 基于接口的动态代理
涉及的类:Proxy
提供者:JDK
官方
- 如何创建代理对象:
- 使用
Proxy
类中的newProxyInstance
方法
创建代理对象的要求:
newProxyInstance
方法的参数:
ClassLoader
:类加载器
- 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。(固定写法)
Class[]
:字节码数组
- 它是用于让代理对象和被代理对象有相同方法。(固定写法)
InvocationHandler
:用于提供增强的代码
- 它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
2. 基于子类的动态代理
涉及的类:Enhancer
提供者:第三方cglib
库
如何创建代理对象:
创建代理对象的要求:
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();
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 {
if (method.getName().equals("sale")) { double money = (double) args[0]; money = money * 0.85; String obj = (String) method.invoke(lenovo, money); return obj+"_鼠标垫"; } else { Object obj = method.invoke(lenovo, args); return obj; }
} });
String computer = proxy_lenovo.sale(8000);
} }
|
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 {
public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+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() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object returnValue = null; Float money = (Float)args[0]; if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(12000f); } }
|
4.2.2 敏感词汇过滤代码
逻辑:获取request
中带有请求参数的方法,例如:getParameter
、getParameterMap
、getParameterValue
等。对其方法的返回值进行判定,是否存在敏感值。如果存在,则对返回值进行增强。
敏感词汇过滤代码
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 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")) { String value = (String) method.invoke(req, args); if (value != null) { for (String word : list) { if (value.contains(word)) { value = value.replaceFirst(word, "**"); } } } return value; } else if (method.getName().equals("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; } return method.invoke(req, args); } });
chain.doFilter(request, resp); }
private List<String> list = new ArrayList<String>();
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] 黑马讲义