过滤器(Filter)
过滤器是对数据进行过滤和预处理。开发人员可以对客户端提交的数据进行过滤处理,也可以对服务器返回的数据进行处理。
比如验证用户的登录情况,权限验证,对静态资源进行访问权限控制等...
过滤器使用的两种方式
注解配置
-
定义一个JwtFilter 实现Filter接口。重写里面的三个方法。(按Ctrl+I 一键重写)
package com.smile.blog.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(filterName = "jwtFilter",urlPatterns = "/*") public class jwtFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { //jwt 过滤器开启 System.out.println("jwt 过滤器开始"); Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("开始验证 ..."); filterChain.doFilter(servletRequest,servletResponse); System.out.println("验证结束 ...."); } @Override public void destroy() { //jwt过滤器结束 System.out.println("jwt 过滤器结束"); Filter.super.destroy(); } }
解释说明:
-
@WebFilter
注解,用于将类声明为过滤器
,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解具有下表给出的一些常用属性(以下所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值) -
@Order
注解,用于定义组件的加载顺序,值越小,加载的优先级越高 -
init()
方法只会执行一次,初始化过滤器 -
destory()
只会在项目停止或项目重新部署执行 -
doFilter()
核心方法,配置过滤器的逻辑代码@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //预处理 System.out.println("开始验证 ..."); filterChain.doFilter(servletRequest,servletResponse); //后处理(处理servletResponse) System.out.println("验证结束 ...."); }
对每个请求及响应增加额外的预处理和后处理逻辑。执行该方法之前,即对用户请求进行预处理,执行该方法后,即对服务器进行后处理。
-
-
在Application上添加注解
@ServletComponentScan
解释说明:
@ServletComponentScan
注解后,Servlet
,Filter
,Listener
可以直接通过@WebServlet
,@WebFilter
,@WebListener
注解自动注册package com.smile.blog; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; @SpringBootApplication @ServletComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
-
结果验证
java配置
-
定义java配置文件
package com.smile.blog.config; import com.smile.blog.filter.JwtFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean jwtFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new JwtFilter()); registration.addUrlPatterns("/*"); registration.setName("JwtFilter"); registration.setOrder(1); return registration; } }
解释说明:
@Bean
是一个方法级别上的注解,主要用在@Configuration
注解的类里,也可以用在@Component
注解的类里。添加的bean的id为方法名 -
定义过滤器
package com.smile.blog.filter; import javax.servlet.*; import java.io.IOException; public class JwtFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { //jwt 过滤器开启 System.out.println("jwt 过滤器开始"); Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("开始验证 ..."); filterChain.doFilter(servletRequest,servletResponse); System.out.println("验证结束 ...."); } @Override public void destroy() { //jwt过滤器结束 System.out.println("jwt 过滤器结束"); Filter.super.destroy(); } }
拦截器(interceptor)
拦截器和过滤器一样,他们都是面向切面编程AOP的具体实现。
拦截器的使用
拦截器是链式调用,一个应用可以同时存在多个拦截器Interceptor
,一个请求也可以触发多个拦截器,而每个拦截器的调用会依据它的声明顺序依次执行
-
定义一个AuthInterceptors实现HandlerInterceptor接口,重写里面的三个方法(按Ctrl+I一键重写)
package com.smile.blog.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Notes * @Date 2022/3/11 * @Time 20:06 * @Author smile */ public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("开始拦截"); return HandlerInterceptor.super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("正在拦截"); HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("结束拦截"); HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
解释说明:
@Configuration
注解:标注在类上,配置Spring容器(应用上下文)。相当于把该类作为spring的xml配置文件中的, @Configuration
注解的类中,使用@Bean
注解标注的方法,返回类型都会直接注册为bean
preHandler
预先处理方法,用来进行一些前置初始化操作或对当前请求做预处理,返回boolean
类型,true
放行请求,会继续调用下一个Interceptor
如果已经是最后一个Interceptor
会调用当前请求的Controller
,false
终止请求。postHandler
过程中处理方法,controller
执行完毕后,会返回到postHandler
这个方法执行,但是它会在DispatcherServlet
进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller
处理后的ModelAndView
对象进行操作,执行完成后开始响应浏览器。afterCompletion
结束后处理方法,当浏览器结束之后,会返回到afterCompletion
这个方法,主要用来进行资源清理。流程图:
-
结果验证
过滤器和触发器的区别
1、实现原理不同
过滤器和拦截器 底层实现方式大不相同,过滤器
是基于函数回调的,拦截器
则是基于Java的反射机制(动态代理)实现的。
这里重点说下过滤器!
在我们自定义的过滤器中都会实现一个 doFilter()
方法,这个方法有一个FilterChain
参数,而实际上它是一个回调接口。ApplicationFilterChain
是它的实现类, 这个实现类内部也有一个 doFilter()
方法就是回调方法。
public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
ApplicationFilterChain
里面能拿到我们自定义的xxxFilter
类,在其内部回调方法doFilter()
里调用各个自定义xxxFilter
过滤器,并执行 doFilter()
方法。
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
...//省略
internalDoFilter(request,response);
}
private void internalDoFilter(ServletRequest request, ServletResponse response){
if (pos < n) {
//获取第pos个filter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
...
filter.doFilter(request, response, this);
}
}
}
而每个xxxFilter
会先执行自身的 doFilter()
过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse)
,也就是回调ApplicationFilterChain
的doFilter()
方法,以此循环执行实现函数回调。
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
2、使用范围不同
我们看到过滤器 实现的是 javax.servlet.Filter
接口,而这个接口是在Servlet
规范中定义的,也就是说过滤器Filter
的使用要依赖于Tomcat
等容器,导致它只能在web
程序中使用。 而拦截器(Interceptor
) 它是一个Spring
组件,并由Spring
容器管理,并不依赖Tomcat
等容器,是可以单独使用的。不仅能应用在web
程序中,也可以用于Application
、Swing
等程序中。
3、触发时机不同
过滤器
和 拦截器
的触发时机也不同,我们看下边这张图。
过滤器Filter
是在请求进入容器后,但在进入servlet
之前进行预处理,请求结束是在servlet
处理完以后。
拦截器 Interceptor
是在请求进入servlet
后,在进入Controller
之前进行预处理的,Controller
中渲染了对应的视图之后请求结束。
4、拦截的请求范围不同
在上边我们已经同时配置了过滤器和拦截器,再建一个Controller
接收请求测试一下。
@Controller
@RequestMapping()
public class Test {
@RequestMapping("/test1")
@ResponseBody
public String test1(String a) {
System.out.println("我是controller");
return null;
}
}
项目启动过程中发现,过滤器的init()
方法,随着容器的启动进行了初始化。 此时浏览器发送请求,F12 看到居然有两个请求,一个是我们自定义的 Controller
请求,另一个是访问静态图标资源的请求。 看到控制台的打印日志如下:
执行顺序 :Filter 处理中
-> Interceptor 前置
-> 我是controller
-> Interceptor 处理中
-> Interceptor 处理后
Filter 处理中
Interceptor 前置
Interceptor 处理中
Interceptor 后置
Filter 处理中
过滤器Filter
执行了两次,拦截器Interceptor
只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller
中请求或访问static
目录下的资源请求起作用。
5、注入Bean情况不同
在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些service
服务。
下边我们分别在过滤器和拦截器中都注入service
,看看有什么不同?
@Component
public class TestServiceImpl implements TestService {
@Override
public void a() {
System.out.println("我是方法A");
}
}
过滤器中注入service
,发起请求测试一下 ,日志正常打印出“我是方法A”
。
@Autowired
private TestService testService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter 处理中");
testService.a();
filterChain.doFilter(servletRequest, servletResponse);
}
复制代码
Filter 处理中
我是方法A
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor 后置
在拦截器中注入service
,发起请求测试一下 ,竟然TM的报错了,debug
跟一下发现注入的service
怎么是Null
啊? 这是因为加载顺序导致的问题,拦截器
加载的时间点在springcontext
之前,而Bean
又是由spring
进行管理。
?
拦截器:老子今天要进洞房; Spring:兄弟别闹,你媳妇我还没生出来呢!
?
解决方案也很简单,我们在注册拦截器之前,先将Interceptor
手动进行注入。「注意」:在registry.addInterceptor()
注册的是getMyInterceptor()
实例。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Bean
public MyInterceptor getMyInterceptor(){
System.out.println("注入了MyInterceptor");
return new MyInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
}
}
6、控制执行顺序不同
实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。
过滤器用@Order
注解控制执行顺序,通过@Order
控制过滤器的级别,值越小级别越高越先执行。
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {
拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order
手动设置控制,值越小越先执行。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
}
看到输出结果发现,先声明的拦截器 preHandle()
方法先执行,而postHandle()
方法反而会后执行。
postHandle()
方法被调用的顺序跟 preHandle()
居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。
Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后
「那为什么会这样呢?」 得到答案就只能看源码了,我们要知道controller
中所有的请求都要经过核心组件DispatcherServlet
路由,都会执行它的 doDispatch()
方法,而拦截器postHandle()
、preHandle()
方法便是在其中调用的。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
...........
try {
// 获取可以执行当前Handler的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 注意: 执行Interceptor中PreHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
...........
}
看看两个方法applyPreHandle()
、applyPostHandle()
具体是如何被调用的,就明白为什么postHandle()
、preHandle()
执行顺序是相反的了。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if(!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = interceptors.length - 1; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
发现两个方法中在调用拦截器数组 HandlerInterceptor[]
时,循环的顺序竟然是相反的。。。,导致postHandle()
、preHandle()
方法执行的顺序相反。