我们搭建一个简单demo:
1、自定义一个Filter类
public class LoginAuthFilter implements Filter {
Logger logger = LoggerFactory.getLogger(LoginAuthFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//在Filter生命周期只会调用一次,可以读取配置文件
logger.info("【过滤器】初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("【过滤器】开始执行");
filterChain.doFilter(servletRequest, servletResponse);
logger.info("【过滤器】执行结束");
}
}
2、注册过滤器
@Configuration
public class LoginAuthConfig {
/**
* 注册过滤器
* @return
*/
@Bean
public FilterRegistrationBean getLoginAuthFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
LoginAuthFilter filter = new LoginAuthFilter();
registrationBean.setFilter(filter);
//urlPatterns配置哪些路径的请求进入过滤器,/*表示所有
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
3、项目演示
@RestController
public class SysUserController {
Logger logger = LoggerFactory.getLogger(SysUserController.class);
@GetMapping(value = "/sysUser/getSysUserById")
public String getSysUserById(String userId) {
logger.info("Controller:进入方法getSysUserById()");
return "嘻嘻嘻";
}
}
启动项目,SpringBoot初始化的时候,我们可以看到LoginAuthFilter类中的init()方法执行了一次
接着我们使用postman请求接口,在控制台看到:
在接口请求进入controller层前后,可以在过滤器Filter中处理一些前置后置逻辑。
1、自定义一个Interceptor类
@Component
public class MyInterceptor implements HandlerInterceptor {
Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("【拦截器】控制器方法调用前");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("【拦截器】控制器方法调用后,视图渲染前");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("【拦截器】整个请求完成后调用");
}
}
2、注册拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer{
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor);
}
}
3、项目演示
@RestController
public class SysUserController {
Logger logger = LoggerFactory.getLogger(SysUserController.class);
@GetMapping(value = "/sysUser/getSysUserById")
public String getSysUserById(String userId) {
logger.info("Controller:进入方法getSysUserById()");
return "嘻嘻嘻";
}
}
启动项目,使用postman请求接口,控制台输出如下:
在接口请求进入controler层的前后,拦截器Interceptor中,都有对应的方法,可以处理二开逻辑。
1、触发时机不同
2、过滤器Filter中无法注入Bean,但是拦截器Interceptor中可以
当我们的项目中,既有过滤器Filter,又有拦截器Interceptor的时候,又出现什么的现象呢?
通过断点和日志输出,引出过滤器Filter和拦截器Interceptor一个区别:
1、触发时机不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入DispathServlet后,在进入Controller之前进行预处理的,视图渲染之后请求结束。我们在自定义类Interceptor的方法中打上断点,就可以看到都是在什么时机进入。
2、过滤器Filter中无法注入Bean,但是拦截器Interceptor中可以
我们写一个Service类,分别在过滤器Filter和拦截器Interceptor中注入,看下结果:
@Service
public class TestServiceImp {
public void test(){
System.out.println("测试方法");
}
}
可以看到拦截器Interceptor中,是可以成功注入的,过滤器Filter中,testServiceImpl的值为null,无法注入。
这是因为我们在注册过滤器Filter的时候,使用的是new LoginAuthFilter的方式,过滤器是Servlet规范的一部分,而不是Spring框架的组件,需要通过new 关键字创建实例。Servlet容器并不了解Spring容器的上下文,因此它无法自动注入Spring的Bean到过滤器中。
而实例化拦截器Interceptor的时候,使用的@Autowired注解的方式。拦截器是Spring MVC框架的一部分,由Spring容器管理,因此可以通过@Autowired注解直接注入。
那如果我在LoginAuthFilter上,同样加上注解@Component,在配置类中使用@Autowired注入过滤器类,那LoginAuthFilter中的bean 可以成功注入吗?
答案是肯定的,可以成功注入bean。
这是为什么呢?不是说过滤器Filter不能注入bean吗?
在一些特定情况下,你确实可以使用@Autowired注解来将过滤器(Filter)注入到Spring中,类似于拦截器(HandlerInterceptor)。
这是因为在Spring Boot等现代Web应用中,Spring容器和Servlet容器通常是集成的,而且Spring Boot提供了一些方便的工具来简化这个集成过程。
尽管在某些情况下使用@Autowired注解是可能的,但需要注意的是,这种方式可能导致在一些纯Servlet容器环境下无法正常工作,因为过滤器的生命周期是由Servlet容器管理的。
这是我在网上查询到的答案。同时我还发现一个有趣的现象。
如果我们在LoginAuthFilter上,加上注解@Component,那么按理说testServiceImp 的bean应该可以成功注入,并且在后续逻辑中,可以正常使用才对。
但是如果你的注册过滤器的类LoginAuthConfig中,还是通过new LoginAuthFilter方法而不是@Autowired注入LoginAuthFilter,你会发现在拦截请求进入LoginAuthFilter中的时候,testServiceImp的bean还是为null 。
@Configuration
public class LoginAuthConfig {
/**
* 注册过滤器
* @return
*/
@Bean
public FilterRegistrationBean getLoginAuthFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//exclusionsList 可以根据配置设置白名单
LoginAuthFilter filter = new LoginAuthFilter();
registrationBean.setFilter(filter);
//urlPatterns配置哪些路径的请求进入过滤器,/*表示所有
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
百思不得其解吧。不过好在springBoot启动的时候,我还发现一个情况:LoginAuthFilter中的init()方法,打印了两次日志,那也就意味着创建了两次实例。
初步猜测,是不是请求拦截的时候,使用的LoginAuthFilter是注册过滤器的时候new LoginAuthFilter的,而不是通过注解扫描创建出来的loginAuthFilter呢?
为了验证猜测,我们在init中打上断点,看下两个实例的情况。
springBoot启动完成后,请求接口:
果然,此时请求进入的是:LoginAuthFilter@6189,对应的是new LoginAuthFilter创建的实例。
为什么拦截请求的时候,选择了使用new LoginAuthFilter的实例,而不是Spring注入的实例呢?这是我产生的第二个疑问。
通过debug查看源码,发现事情并不是我想象的这样必须是2选择1的,实际上注册的所有Filter实例,都会链式调用执行的。
而我们在代码中所写的filterChain.doFilter(servletRequest, servletResponse),则是触发下一个Filter执行的逻辑。
new 方法创建的实例,只是在通过注解扫描创建的bean之前执行了,异常则中断了后续Filter的执行。
适用场景:
过滤器:广泛用于对请求和响应进行预处理和后处理的场景。例如,可以在过滤器中进行日志记录、字符编码转换、权限验证等操作。过滤器在请求进入Servlet容器或在响应返回客户端之前起作用。
拦截器:主要用于在请求处理的不同阶段进行拦截和处理。在Spring等框架中,拦截器常用于对控制器的方法进行前后处理,如日志记录、权限检查、性能监控等。