Filter
一、概念
Javaweb三大组件(Servlet、Filter、Listener)之一,Filter就是过滤器,当访问服务器资源时,Filter可以将请求拦截下来,完成一些特定的功能,也就是过滤特定的请求资源、请求信息、响应信息;当一个请求到来,Web服务器首先判断是否有过滤器与请求资源相关联,如果有那么将请求交给过滤器处理,再由过滤器决定是否交给请求资源,响应则相反。过滤器一般用于完成通用的操作,比如:登录验证、统一编码处理、敏感字符过滤……
过滤器的基本原理:
- Filter就是一个实现了接口Filter的java类,与Servlet类似的由Tomcat执行
- 当为一个Filter配置了拦截资源,当请求该资源时,可以由该Filter决定是否放行该请求到请求的资源,以及决定是否对请求消息、响应消息作出修改
- 当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。
- 但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。
- 只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能。
- 如果在 Filter.doFilter 方法中没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法不会被执行,这样通过 Filter 就可以阻止某些非法的访问请求。
二、编写Filter的步骤
-
定义一个类,实现接口
Filter
-
复写接口的方法
-
配置拦截路径(访问什么资源,过滤器会生效)
@WebFilter("/*")// /*表示访问所有资源之前都会执行该过滤器,如果是/demo.jsp就是表示访问demo.jsp之前执行该过滤器 public class FilterDemo1 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } // doFilter业务处理的核心代码区,相当于Servlet的service方法 @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("FilterDemo1----------------------"); filterChain.doFilter(servletRequest, servletResponse);// 过滤器放行请求,可以访问到index.jsp } @Override public void destroy() { } }
三、过滤器的一些细节
1. web.xml配置
<!--先为FilterDemo1这个类配置一个过滤器的名字demo1,然后配置该过滤器应用的资源范围-->
<filter>
<filter-name>demo1</filter-name>
<filter-class>cn.zhuobo.web.filter.FilterDemo1</filter-class>
</filter>
<filter-mapping>
<filter-name>demo1</filter-name>
<url-pattern>/*</url-pattern> <!-- 这里配置的是拦截路径 -->
</filter-mapping>
2. 过滤器的执行流程
-
执行过滤器
-
执行过滤器放行后的请求资源
-
执行过滤器放行后的代码
// 对request对象做一些增强 System.out.println("filter11111111111111111"); // 放行 chain.doFilter(req, resp); //回程,对response对象做一些增强 System.out.println("filter222222222222222222");
3. 过滤器的生命周期方法
- init:服务器启动后创建Filter对象,调用init方法,init方法只执行一次,一般用来加载资源
- doFilter:每次请求被拦截的资源时都会执行,可以执行多次
- destroy:服务器关闭后Filter对象被销毁,如果服务器是正常关闭,就会执行destroy方法
4. 过滤器配置细节
-
拦截路径的配置
- 拦截具体资源:index.jsp,表示只有访问该资源时对应的过滤器才会被执行
- 拦截目录:/dir/*,表示访问dir目录下的所有资源过滤器都会被执行
- 拦截后缀名:*.jsp,表示访问jsp资源时过滤器会被执行
- 拦截全部资源:/*,表示访问所有资源都会执行过滤器
-
拦截方式的配置(资源访问的方式,比如浏览器直接请求,转发请求),使用dispatcherTypes属性配置,下面为该属性的5个值:
- DispatcherType.REQUEST:默认值,只有直接请求该资源才会执行过滤器
- DispatcherType.FORWARD:转发访问该资源才执行过滤器
- DispatcherType.INCLUDE:包含访问资源
- DispatcherType.ERROR:错误跳转资源
- DispatcherType.ASYNC:异步访问该资源
注意dispatcherTypes的值可以是一个数组,也就是可以配置多个值,这种情况下,无论是直接访问,还是请求转发都会执行过滤器,配置了两个值
@WebFilter(value = "/index.jsp", dispatcherTypes = {DispatcherType.FORWARD, DispatcherType.REQUEST}) // 直接访问或者转发
5. 过滤器链(配置多个过滤器)
可以配置多个过滤器,而且可以都生效,但是过滤器有执行顺序的问题,上一个的doFilter方法激活下一个的doFilter方法,最后一个doFilter方法激活访问Servlet的service方法,过滤器链的中间任意一个filter没有调用doFilter方法,那么最终的Servlet的service方法都不会被执行:如果有两个过滤器分别是Filter1和Filter2,那么执行顺序如下:
- Filter1执行
- Filter2执行
- 被请求的资源执行
- Filter2执行
- Filter1执行
还有就是过滤器的先后问题:
- 注解配置方式:按照类名的字符串比较顺序,值小的先执行
- web.xml配置方式:< filter-mapping >标签先定义者先执行
四、过滤器的应用(登录验证、敏感词过滤)
1. 登录验证
-
登录验证需求:
- 访问资源,验证是否已经登录
- 如果登录了就直接放行
- 否则就直接跳转发到登录页面,提示“请您先登录”
-
登录验证的过滤器的核心逻辑:
-
判断请求的资源是否登录相关的资源(登录页面相关的资源),如果是,就直接放行;否则就要判断是否已经登录
-
判断是否已经登录,因为如果登录了都会在session中存储user键,判断session中是否有user即可判断是否已经登录;
- 如果已经登录,直接放行
- 否则跳转到登录页面,提示“请您先登录”
-
具体代码实现:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { // 将req强制转换为HttpServletRequest,因为一般都是HTTP协议的 HttpServletRequest request = (HttpServletRequest) req; //1. 获取访问资源的URI,就是那个很长的 String uri = request.getRequestURI(); // 2. 判断uri是否包含有登录相关的资源,包括login.jsp,相关的验证码、css样式、js、字体样式等 if(uri.contains("/login.jsp") || uri.contains("/loginServlet") || uri.contains("checkCodeServlet") || uri.contains("/css/")|| uri.contains("/js/") || uri.contains("/fonts/")) { // 含有登录相关的资源,表示用户就是要去登录的,直接放行 chain.doFilter(req, resp); } else { // 否则就判断使用是否已经登录了 Object user = request.getSession().getAttribute("user"); if (user != null) { // 已经登录的,就直接放行 chain.doFilter(req, resp); } else { // 否则就跳转到登录页面,并且提示要先登录 request.setAttribute("login_msg", "您还没有登录,请您先登录!"); request.getRequestDispatcher("/login.jsp").forward(request, resp); } } }
-
2. 敏感词汇过滤
-
敏感词汇过滤需求:
- 对录入数据进行敏感词汇过滤
- 敏感词汇参考《敏感词汇.txt》
- 将敏感词替换为***
-
分析:
- 请求参数是封装在request对象的
- Filter过滤器doFilter的参数req对象事实上和封装请求参数的request对象时同一个
- 在doFilter中将请求参数中的敏感词汇替换为***,重新封装request对象
- 放行,chain.doFilter的参数传入重新封装的request对象
-
增强对象的功能,采用设计模式中的装饰模式、代理模式可增强对象的功能
-
装饰模式
-
代理模式:代理对象代理真实对象,达到增强真实对象功能的模式
- 真实对象:被代理的对象
- 代理对象:
实现方式:
- 静态代理:有一个类文件描述代理模式
- 动态代理:在内存中形成代理
-
动态代理的实现步骤
- 代理对象和真实对象实现相同的接口(代理对象和真实对象时兄弟);
- 代理对象 = Proxy.newProxyInstance();
- 使用代理对象调动方法;
- 增强方法。
-
增强方法的方式 :
- 增强参数列表
- 增强返回值类型
- 增强方法体执行逻辑
public class ProxyTest { public static void main(String[] args) { Huawei huawei = new Huawei(); /** *Proxy.newProxyInstance方法的三个参数: * 1. 真实对象的类加载器:真实对象.getClass().getClassLoader() * 2. 接口数组:代理对象实现的接口,真实对象.getClass().getClassLoader() * 3. 处理器:new InvocationHandler() */ SaleComputer proxy_huawei = (SaleComputer) Proxy.newProxyInstance(huawei.getClass().getClassLoader(), huawei.getClass().getInterfaces(), new InvocationHandler() { /** *invoke方法是代理逻辑编写的方法,代理对象调用的所有方法都会触发该方法的执行 * * @param proxy:代理对象 * @param method:代理对象调用的方法,被封装成的对象 * @param args:代理对象调用方法时,传递的参数封装的数组 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 判断执行的方法是不是sale方法,如果是就增强参数(修改参数) if("sale".equals(method.getName())) { double money = (double) args[0]; money = money * 0.8;// 改变参数的值 // 使用真实对象调用方法 String obj = (String) method.invoke(huawei, money); // 增强返回值 return obj + "、电脑包、鼠标垫......"; } else {// 如果不是sale方法,那么就原样执行 // 使用真实对象调用sale方法 Object obj = method.invoke(huawei, args); return obj; } } }); // 代理对象调用sale方法 String computer = proxy_huawei.sale(7788); System.out.println(computer); proxy_huawei.show(); } }
-