过滤器概述
Filter,即过滤器,是JAVAEE技术规范之一,作用目标资源的请求进行过滤的一套技术规范,是Java Web项目中
最为实用的技术之一
-
Filter接口定义了过滤器的开发规范,所有的过滤器都要实现该接口
-
Filter的工作位置是项目中所有目标资源之前,容器在创建HttpServletRequest和HttpServletResponse对象后,会先调用Filter的doFilter方法
-
Filter的doFilter方法可以控制请求是否继续,如果放行,则请求继续,如果拒绝,则请求到此为止,由过滤器本身做出响应
-
Filter不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理
-
Filter是GOF中责任链模式的典型案例
-
Filter的常用应用包括但不限于: 登录权限检查,解决网站乱码,过滤敏感字符,日志记录,性能分析... ...
生活举例: 公司前台,停车场安保,地铁验票闸机
-
公司前台对来访人员进行审核,如果是游客则拒绝进入公司,如果是客户则放行 . 客户离开时提醒客户不要遗忘物品
-
停车场保安对来访车辆进行控制,如果没有车位拒绝进入,如果有车位,发放停车卡并放行,车辆离开时收取请车费
-
地铁验票闸机在人员进入之前检查票,没票拒绝进入,有票验票后放行,人员离开时同样验票
过滤器开发中应用的场景
-
日志的记录
-
性能的分析
-
乱码的处理
-
事务的控制
-
登录的控制
-
跨域的处理
-
... ...
过滤器工作位置图解
Filter接口API
import java.io.IOException;public interface Filter {default public void init(FilterConfig filterConfig) throws ServletException {}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException;default public void destroy() {}
}
-
API目标
API | 目标 |
---|---|
default public void init(FilterConfig filterConfig) | 初始化方法,由容器调用并传入初始配置信息filterConfig对象 |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | 过滤方法,核心方法,过滤请求,决定是否放行,响应之前的其他处理等都在该方法中 |
default public void destroy() | 销毁方法,容器在回收过滤器对象之前调用的方法 |
过滤器使用
目标:开发一个日志记录过滤器
-
用户请求到达目标资源之前,记录用户的请求资源路径
-
响应之前记录本次请求目标资源运算的耗时
-
可以选择将日志记录进入文件,为了方便测试,这里将日志直接在控制台打印
定义一个过滤器类,编写功能代码
package com.atguigu.filters;import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class LoggingFilter implements Filter {private SimpleDateFormat dateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 参数父转子HttpServletRequest request =(HttpServletRequest) servletRequest;HttpServletResponse response =(HttpServletResponse) servletResponse;// 拼接日志文本String requestURI = request.getRequestURI();String time = dateFormat.format(new Date());String beforeLogging =requestURI+"在"+time+"被请求了";// 打印日志System.out.println(beforeLogging);// 获取系统时间long t1 = System.currentTimeMillis();// 放行请求filterChain.doFilter(request,response);// 获取系统时间long t2 = System.currentTimeMillis();// 拼接日志文本String afterLogging =requestURI+"在"+time+"的请求耗时:"+(t2-t1)+"毫秒";// 打印日志System.out.println(afterLogging);}
}
-
说明
-
doFilter方法中的请求和响应对象是以父接口的形式声明的,实际传入的实参就是HttpServletRequest和HttpServletResponse子接口级别的,可以安全强转
-
filterChain.doFilter(request,response); 这行代码的功能是放行请求,如果没有这一行代码,则请求到此为止
-
filterChain.doFilter(request,response);在放行时需要传入request和response,意味着请求和响应对象要继续传递给后续的资源,这里没有产生新的request和response对象
-
定义两个Servlet作为目标资源
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 处理器请求System.out.println("servletA处理请求的方法,耗时10毫秒");// 模拟处理请求耗时try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 处理器请求System.out.println("servletB处理请求的方法,耗时15毫秒");// 模拟处理请求耗时try {Thread.sleep(15);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
配置过滤器以及过滤器的过滤范围
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"version="5.0"><!--配置filter,并为filter起别名--><filter><filter-name>loggingFilter</filter-name><filter-class>com.atguigu.filters.LoggingFilter</filter-class></filter><!--为别名对应的filter配置要过滤的目标资源--><filter-mapping><filter-name>loggingFilter</filter-name><!--通过映射路径确定过滤资源--><url-pattern>/servletA</url-pattern><!--通过后缀名确定过滤资源--><url-pattern>*.html</url-pattern><!--通过servlet别名确定过滤资源--><servlet-name>servletBName</servlet-name></filter-mapping>
</web-app>
说明
-
filter-mapping标签中定义了过滤器对那些资源进行过滤
-
子标签url-pattern通过映射路径确定过滤范围
-
/servletA 精确匹配,表示对servletA资源的请求进行过滤
-
*.html 表示对以.action结尾的路径进行过滤
-
/* 表示对所有资源进行过滤
-
一个filter-mapping下可以配置多个url-pattern
-
-
子标签servlet-name通过servlet别名确定对那些servlet进行过滤
-
使用该标签确定目标资源的前提是servlet已经起了别名
-
一个filter-mapping下可以定义多个servlet-name
-
一个filter-mapping下,servlet-name和url-pattern子标签可以同时存在
-
过滤器生命周期
过滤器作为web项目的组件之一,和Servlet的生命周期类似,略有不同,没有servlet的load-on-startup的配置,默认就是系统启动立刻构造
阶段 | 对应方法 | 执行时机 | 执行次数 |
---|---|---|---|
创建对象 | 构造器 | web应用启动时 | 1 |
初始化方法 | void init(FilterConfig filterConfig) | 构造完毕 | 1 |
过滤请求 | void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) | 每次请求 | 多次 |
销毁 | default void destroy() | web应用关闭时 | 1次 |
过滤器链的使用
一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链
-
过滤器链中的过滤器的顺序由filter-mapping顺序决定
-
每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的
-
如果某个Filter是使用ServletName进行匹配规则的配置,那么这个Filter执行的优先级要更低
图解过滤器链
注解方式配置过滤器
@WebFilter注解的使用
-
源码
package jakarta.servlet.annotation;import jakarta.servlet.DispatcherType;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebFilter {String description() default "";String displayName() default "";WebInitParam[] initParams() default {};String filterName() default "";String smallIcon() default "";String largeIcon() default "";String[] servletNames() default {};String[] value() default {};String[] urlPatterns() default {};DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};boolean asyncSupported() default false;
}
一个比较完整的Filter的XML配置
<!--配置filter,并为filter起别名-->
<filter><filter-name>loggingFilter</filter-name><filter-class>com.atguigu.filters.LoggingFilter</filter-class><!--配置filter的初始参数--><init-param><param-name>dateTimePattern</param-name><param-value>yyyy-MM-dd HH:mm:ss</param-value></init-param>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping><filter-name>loggingFilter</filter-name><!--通过映射路径确定过滤资源--><url-pattern>/servletA</url-pattern><!--通过后缀名确定过滤资源--><url-pattern>*.html</url-pattern><!--通过servlet别名确定过滤资源--><servlet-name>servletBName</servlet-name>
</filter-mapping>
将xml配置转换成注解方式实现
package com.atguigu.filters;import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;@WebFilter(filterName = "loggingFilter",initParams = {@WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH:mm:ss")},urlPatterns = {"/servletA","*.html"},servletNames = {"servletBName"}
)
public class LoggingFilter implements Filter {private SimpleDateFormat dateFormat ;/*init初始化方法,通过filterConfig获取初始化参数* init方法中,可以用于定义一些其他初始化功能代码* */@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 获取初始参数String dateTimePattern = filterConfig.getInitParameter("dateTimePattern");// 初始化成员变量dateFormat=new SimpleDateFormat(dateTimePattern);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 参数父转子HttpServletRequest request =(HttpServletRequest) servletRequest;HttpServletResponse response =(HttpServletResponse) servletResponse;// 拼接日志文本String requestURI = request.getRequestURI();String time = dateFormat.format(new Date());String beforeLogging =requestURI+"在"+time+"被请求了";// 打印日志System.out.println(beforeLogging);// 获取系统时间long t1 = System.currentTimeMillis();// 放行请求filterChain.doFilter(request,response);// 获取系统时间long t2 = System.currentTimeMillis();String afterLogging =requestURI+"在"+time+"的请求耗时:"+(t2-t1)+"毫秒";// 打印日志System.out.println(afterLogging);}
}
ps:来源尚硅谷