Filters in java

一. 过滤器概述

定义: Filter,即过滤器,是JAVAEE技术规范之一,作为目标资源的请求进行过滤的一套技术规范,是JAVA WEB项目中最为实用的技术之一

  1. Filter 接口定义了过滤器的开发规范,所有的过滤器都要实现该接口(Filter)
  2. Filter 的工作位置是项目中所有目标资源之前,容器在创建HttpServletRequestHttpServletResponse对象后,会先调用FilterdoFilter方法
  3. FilterdoFilter方法可以控制请求是否继续,如果放行,则请求继续,如果拒绝,则请求到此为止,由过滤器本身做出响应
  4. Filter不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理
  5. Filter是GOF中责任链模式的典型案例
  6. Filter的常用应用包括但不限于: 登录权限检查,解决网站乱码,过滤敏感字符,日志记录,性能分析… …

二. 应用场景

  • 日志的记录
  • 性能的分析
  • 乱码的处理
  • 事务的控制
  • 登录的控制
  • 跨域的处理
  • ……

三. 过滤器工作位置图解

四. Filter接口API

1. 源码

1
2
3
4
5
6
7
8
9
10
11
package jakarta.servlet;
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() {
}
}

2. API目标

API目标
default public void init(FilterConfig filterConfig)初始化方法,由容器调用并传入初始配置信息filterConfig对象
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)过滤方法,核心方法,过滤请求,决定是否放行,响应之前的其他处理等都在该方法中
default public void destroy()销毁方法,容器在回收过滤器对象之前调用的方法

五. 过滤器的使用

目标:开发一个日志记录过滤器

步骤:

  • 用户请求到达目标资源之前,记录用户的请求资源路径
  • 响应之前记录本次请求目标资源运算的耗时
  • 可以选择将日志记录进入文件,为了方便测试,这里将日志直接在控制台打印

1. 定义一个过滤器类,编写功能代码

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
package com.nianxi.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");
@Override
public 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方法中的请求和响应对象是以父接口的形式声明的,实际传入的实参就是HttpServletRequestHttpServletResponse子接口级别的,可以安全强转
  • filterChain.doFilter(request,response); 这行代码的功能是放行请求,如果没有这一行代码,则请求到此为止
  • filterChain.doFilter(request,response);在放行时需要传入request和response,意味着请求和响应对象要继续传递给后续的资源,这里没有产生新的request和response对象

2. 配置过滤器以及过滤器的过滤范围

a. xml配置文件配置

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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子标签可以同时存在

过滤过程图解

b. 注解方式配置(@WebFilter)

1. 源码

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
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;
}

2. 一个比较完整的Filter的XML配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--配置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>

3. 将xml配置转换成注解方式实现

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
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方法中,可以用于定义一些其他初始化功能代码
* */
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 获取初始参数
String dateTimePattern = filterConfig.getInitParameter("dateTimePattern");
// 初始化成员变量
dateFormat=new SimpleDateFormat(dateTimePattern);
}
@Override
public 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);

}
}

六. 过滤器的生命周期

过滤器作为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次

Servlet的生命周期

实例化、初始化、处理请求、服务终止

实例化 —— 先创建servlet实例

  1. 当客户端首次发送第一次请求后,由Servlet容器去解析请求,根据请求找到是否有对应的servlet。
  2. 判断是否有Servlet实现类的对象存在?存在则直接使用,不存在则先创建一个servlet实现类的对象。

初始化 —— init()

调取init()方法进行初始化操作,可以在这一步中使用config.getInitParameter()方法调取配置文件中的参数,这一步在全生命周期内只执行一次。

处理请求 —— service()

初始化完成后调取service()方法,由service()判断客户端的请求方式。

  1. 如果是get请求,则执行doGet()方法。
  2. 如果是post请求,则执行doPost()。
  3. 处理方法完成后会作出相应的结果返回给客户端,单次请求处理完毕。

当用户发送第二次以后的请求时,会判断对象是否存在,但是不再执行init(),而直接执行service方法调取doGet() / doPost()方法。

服务终止 —— destroy()

当服务器关闭时Servlet调取destroy()方法进行销毁,宣告生命周期的结束。

过滤器test代码

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
package com.atguigu.filters;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;

import java.io.IOException;


@WebServlet("/*")
public class LifeCycleFilter implements Filter {
public LifeCycleFilter(){
System.out.println("LifeCycleFilter constructor method invoked");
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("LifeCycleFilter init method invoked");

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("LifeCycleFilter doFilter method invoked");
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {
System.out.println("LifeCycleFilter destory method invoked");
}
}

七. 过滤器链

一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链

  • 过滤器链中的过滤器的顺序由filter-mapping顺序决定
  • 每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的
  • 如果某个Filter是使用ServletName进行匹配规则的配置,那么这个Filter执行的优先级要更低

图解过滤器链

过滤器链功能测试

  • 定义三个过滤器,对目标资源Servlet的请求进行过滤

  • 目标Servlet资源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.atguigu.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/servletC")
public class ServletC extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("servletC service method invoked");
}
}

  • 三个过滤器代码
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
public class Filter1  implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter1 before chain.doFilter code invoked");

filterChain.doFilter(servletRequest,servletResponse);

System.out.println("filter1 after chain.doFilter code invoked");

}
}


public class Filter2 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter2 before chain.doFilter code invoked");

filterChain.doFilter(servletRequest,servletResponse);

System.out.println("filter2 after chain.doFilter code invoked");

}
}


public class Filter3 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter3 before chain.doFilter code invoked");

filterChain.doFilter(servletRequest,servletResponse);

System.out.println("filter3 after chain.doFilter code invoked");

}
}
  • 过滤器配置代码
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
<?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-name>filter1</filter-name>
<filter-class>com.atguigu.filters.Filter1</filter-class>
</filter>

<filter>
<filter-name>filter2</filter-name>
<filter-class>com.atguigu.filters.Filter2</filter-class>
</filter>

<filter>
<filter-name>filter3</filter-name>
<filter-class>com.atguigu.filters.Filter3</filter-class>
</filter>

<!--filter-mapping的顺序决定了过滤器的工作顺序-->
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/servletC</url-pattern>
</filter-mapping>

<filter-mapping>
<filter-name>filter2</filter-name>
<url-pattern>/servletC</url-pattern>
</filter-mapping>

<filter-mapping>
<filter-name>filter3</filter-name>
<url-pattern>/servletC</url-pattern>
</filter-mapping>

</web-app>

工作流程图解