Spring拦截器

拦截器简介

Spring拦截器是一种基于AOP的技术,本质也是使用一种代理技术,它主要作用于接口请求中的控制器,也就是Controller。

因此它可以用于对接口进行权限验证控制。

创建拦截器

创建一个DemoInterceptor类实现HandlerInterceptor接口,重写preHandle(),postHandle(),afterCompletion() 三个方法,如下代码:

@Component
public class DemoInterceptor implements HandlerInterceptor { @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("在处理器执行之前执行......");
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 当处理器发生异常返回时,postHandle() 将不会执行
System.out.println("在处理器执行之后执行......");
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在处理器执行完成后执行,相当于try catch finally中的finally
System.out.println("afterCompletion......");
}
}

创建拦截器之后,我们还需要将其注册到Spring程序中,以便启用它。

注册拦截器

创建一个Spring配置类实现WebMvcConfigurer接口,并重写addInterceptors()方法,用于将拦截器添加到程序中。

@Configuration
public class MvcConfig implements WebMvcConfigurer { @Autowired
private DemoInterceptor demoInterceptor; /**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//addPathPatterns 用于添加需要拦截的路径
//excludePathPatterns 用于排除不需要被拦截的路径
registry.addInterceptor(demoInterceptor).addPathPatterns("/**");
} /**
* 此方法用于配置静态资源路径
**/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
/** 本地文件上传路径 */
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + ProjectConfig.getProfile() + "/"); /**
* 使用跨域拦截器时可能会导致Swagger2页面无法访问,需要重新配置静态资源访问
**/
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
} /**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter()
{
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
}

原理架构图

拦截器应用案例

防止重复提交拦截器

@Component
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
if (handler instanceof HandlerMethod)
{
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 验证当前访求的方法是否使用 @RepeatSubmit 注解
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
if (annotation != null)
{
if (this.isRepeatSubmit(request, annotation))
{
AjaxResult ajaxResult = AjaxResult.error(annotation.message());
ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
return false;
}
}
return true;
}
else
{
return true;
}
} /**
* 验证是否重复提交由子类实现具体的防重复提交的规则
*
* @param request
* @return
* @throws Exception
*/
public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
}

这里到自定义注解 @RepeatSubmit

/**
* 自定义注解防止表单重复提交
*
* @author hviger
*
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{
/**
* 间隔时间(ms),小于此时间视为重复提交
*/
public int interval() default 5000; /**
* 提示消息
*/
public String message() default "不允许重复提交,请稍候再试";
}

Spring过滤器

过滤器简介

过滤器 Filter 由 Servlet 提供,基于函数回调实现链式对网络请求响应的拦截与修改。由于基于 Servlet ,其可以对web服务器管理的几乎所有资源进行拦截(JSP、图片文件、HTML 文件、CSS文件等)。

定义一个过滤器,需要实现 javax.servlet.Filter 接口。

Filter 并不是一个 Servlet,它不能直接向客户端生成响应,只是拦截已有的请求,对不需要或不符合的信息资源进行预处理。

过滤器可以定义多个,按照过滤器链顺序调用:

概念和作用

概念:过滤器位于客户端和web应用程序之间,用于检查和修改两者之间经过的请求;在请求到达Servlet/JSP之前,用过滤器截获请求;

作用:在客户端的请求访问后端资源之前,拦截这些请求(添加处理)。

场景:实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

Filter 的生命周期

init(): 初始化Filter 实例,Filter 的生命周期与 Servlet 是相同的,也就是当 Web 容器(tomcat)启动时,调用 init() 方法初始化实例,Filter只会初始化一次。需要设置初始化参数的时候,可以写到init()方法中。

doFilter(): 业务处理,拦截要执行的请求,对请求和响应进行处理,一般需要处理的业务操作都在这个方法中实现

destroy() : 销毁实例,关闭容器时调用 destroy() 销毁 Filter 的实例。

过滤器的使用方式

首先要实现 javax.servlet.Filter 接口,之后将 Filter 声明为 Bean 交由 Spring 容器管理。

传统javaEE增加Filter是在web.xml中配置

<filter>
<filter-name>TestFilter</filter-name>
<filter-class>com.cppba.filter.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/*</url-pattern>
<init-param>
<param-name>paramName</param-name>
<param-value>paramValue</param-value>
</init-param>
</filter-mapping>

方式一:@WebFilter注解

通过 @WebFilter 注解,将类声明为 Bean 过滤器类,在启动类添加注解 @ServletComponentScan ,让 Spring 可以扫描到。

@WebFilter
public class WebVisitFilter implements Filter { @Override
public void init(FilterConfig filterConfig) throws ServletException {
} /**
* 输出访问 ip
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
//获取访问 ip 地址
HttpServletRequest req = (HttpServletRequest) request;
String visitIp = req.getRemoteAddr();
visitIp = "0:0:0:0:0:0:0:1".equals(visitIp) ? "127.0.0.1" : visitIp;
// 每次拦截到请求输出访问 ip
System.out.println("访问 IP = " + visitIp);
chain.doFilter(req, response);
} @Override
public void destroy() {
}
} @SpringBootApplication
@ServletComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@WebFilter作用:

Tomcat 的 servlet 包下的注解,通过 @WebFilter 注解可以将指定类声明为过滤器。

@WebFilter 属性中没有配置顺序的,其执行顺序和 Filter 类名称字符排序有关,如果需要设置执行顺序,可以在命名的时候注意一下。

方式二:@Component注解

使用 @Component 将类声明为 Bean ,配合使用 @Order 注解可以设置过滤器执行顺序。

@Order(1)
@Component
public class WebVisitFilter implements Filter { @Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/**
* 输出访问 IP
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 业务处理
}
@Override
public void destroy() {
}
}

方式三:Java Config 配置类

使用 @Configuration + @Bean 配置类,注解声明Bean,交由 Spring 容器管理。

Java Config 的方式可以通过 @Bean 配置顺序或 FilterRegistrationBean.setOrder() 决定 Filter 执行顺序。

public class WebVisitFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 业务处理
}
@Override
public void destroy() {
}
} @Configuration
public class WebVisitFilterConfig { /**
* 注册 过滤器 Filter
*/
@Bean
public FilterRegistrationBean<Filter> webVisitFilterConfigRegistration() {
//匹配拦截 URL
String urlPatterns = "/admin/*,/system/*";
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<Filter>();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new WebVisitFilter());
registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
//设置名称
registration.setName("webVisitFilter");
//设置过滤器链执行顺序
registration.setOrder(3);
//启动标识
registration.setEnabled(true);
//添加初始化参数
registration.addInitParameter("enabel", "true");
return registration;
}
}

FilterChain 的作用:

过滤器链是一种责任链模式的设计实现,在一个Filter 处理完成业务后,通过 FilterChain 调用过滤器链中的下一个过滤器。

关于OncePerRequestFilter

OncePerRequestFilter:顾名思义,它能够确保在一次请求中只通过一次filter,而需要重复的执行。

此方法是为了兼容不同的web container,也就是说并不是所有的container都是我们期望的只过滤一次,servlet版本不同,执行过程也不同。

简单的说就是去适配了不同的web容器,以及对异步请求,也只过滤一次的需求。

在servlet2.3中,Filter会经过一切请求,包括服务器内部使用的forward转发请求和<%@ include file=”/login.jsp”%>的情况

servlet2.4中的Filter默认情况下只过滤外部提交的请求,forward和include这些内部转发都不会被过滤

因此建议在Spring环境下使用Filter的话,个人建议继承OncePerRequestFilter吧,而不是直接实现Filter接口。这是一个比较稳妥的选择。

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
@Autowired
private TokenService tokenService; @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
{
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
} @Override
protected void initFilterBean() throws ServletException {
System.out.println("Filter初始化...");
}
}

常见应用场景

  • 登录验证
  • 统一编码处理
  • 敏感字符过滤

过滤器链抛出异常处理方式

在过滤器进行拦截操作时,如发生异常,与业务类相同需要捕获异常进行记录并处理。

如果想继续执行业务,可以通过 chain.doFilter(req, response);

对之后的过滤器进行调用。

拦截器和过滤器的区别

过滤器 和 拦截器 均体现了AOP的编程思想,都可以实现诸如日志记录、登录鉴权等功能。

1、实现原理不同

  • 过滤器 是基于函数回调的,doFilter()方法实际上是一个回调接口。
  • 拦截器 则是基于Java的反射机制(动态代理)实现的。

2、使用范围不同

  • 过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
  • 拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。

3、触发时机不同

  • 过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
  • 拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

4、拦截的请求范围不同

执行顺序 :Filter 处理中 -> Interceptor 前置 -> controller -> Interceptor 处理中 -> Interceptor 处理后 -> Filter 处理中

由此可见,过滤器Filter执行了两次,拦截器Interceptor只执行了一次。

  • 这是因为过滤器几乎可以对所有进入容器的请求起作用;
  • 而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。

5、注入Bean情况不同

在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些service服务。

  • 过滤器中注入service,发起请求可以正常调用。
  • 在拦截器中注入service,发起请求会报错,debug跟一下会发现注入的service是Null。

这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。

拦截器:老子今天要进洞房;

Spring:兄弟别闹,你媳妇我还没生出来呢!

解决方案也很简单,我们在注册拦截器之前,先将Interceptor 手动进行注入。也可以直接在类上使用 @Component 注解注入。

注意:在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("/**");
}
}

扩展知识

@Component(@Controller、@Service、@Repository)通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中。

@Component和@Bean都是用来注册Bean并装配到Spring容器中,但是Bean比Component的自定义性更强。可以实现一些Component实现不了的自定义加载类。

  • 1、@Component: 注解表明一个类会作为组件类,并告知Spring要为这个类创建bean,使用 @Component注解在一个类上,表示将此类标记为Spring容器中的一个Bean。(相当于创建对象)

  • 2、@Bean是将组件注册到Bean,让IOC容器知道这个组件存在。类上必须加上@Configuration注解。(相当于创建对象)

  • 3、@Autowire:是组件和组件相互调用的时候,自动从ioc中取出来需要用的组件。(调用对象)

6、控制执行顺序不同

实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。

过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter implements Filter {}

拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor3()).addPathPatterns("/**").order(2);
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(1);
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(3);
}

看到输出结果发现,postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的!

如果实际开发中严格要求执行顺序,那就需要特别注意这一点。

Interceptor2 前置
Interceptor3 前置
Interceptor1 前置 controller Interceptor1 处理中
Interceptor3 处理中
Interceptor2 处理中 Interceptor1 处理后
Interceptor3 处理后
Interceptor2 处理后

拦截器和过滤器的应用场景

如果一个请求在经过Spring框架处理之前就要处理(或者说能够/可以被处理),那么这种情况一般选择用过滤器。

  • 对请求URL做限制,限制某些URL的请求不被接受,这个动作是没有必要经过Spring的,直接过滤器初始化规则过滤即可;
  • 对请求数据做字符转换,请求的是密文,转换成明文,顺便再校验一下数据格式,这个也不需要经过Spring;

总之,与详细业务不相关的请求处理都可以用过滤器来做;而与业务相关的自然就用拦截器来做

  • 对业务处理前后的数据做日志的记录;

Spring之拦截器和过滤器的更多相关文章

  1. Spring boot 拦截器和过滤器

    1. 过滤器 Filter介绍 Filter可以认为是Servlet的一种“加强版”,是对Servlet的扩展(既可以对请求进行预处理,又可以对处理结果进行后续处理.使用Filter完整的一般流程是: ...

  2. 面试题:struts 拦截器和过滤器

    拦截器和过滤器的区别 过滤器是servlet规范中的一部分,任何java web工程都可以使用. 拦截器是struts2框架自己的,只有使用了struts2框架的工程才能用. 过滤器在url-patt ...

  3. Spring Boot2(七):拦截器和过滤器

    一.前言 过滤器和拦截器两者都具有AOP的切面思想,关于aop切面,可以看上一篇文章.过滤器filter和拦截器interceptor都属于面向切面编程的具体实现. 二.过滤器 过滤器工作原理 从上图 ...

  4. Spring拦截器和过滤器

    什么是拦截器 拦截器(Interceptor): 用于在某个方法被访问之前进行拦截,然后在方法执行之前或之后加入某些操作,其实就是AOP的一种实现策略.它通过动态拦截Action调用的对象,允许开发者 ...

  5. springMVC拦截器和过滤器总结

    拦截器: 用来对访问的url进行拦截处理 用处: 权限验证,乱码设置等 spring-mvc.xml文件中的配置: <beans xmlns="http://www.springfra ...

  6. SpringMVC学习笔记:拦截器和过滤器

    首先说明一下二者的区别: 1. 拦截器基于java的反射机制,而过滤器是基于函数回调 2. 拦截器不依赖于servlet容器,过滤器依赖servlet容器 3. 拦截器只能对action请求起作用,而 ...

  7. SpringMVC的拦截器和过滤器的区别

    一 简介 (1)过滤器: 依赖于servlet容器.在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次.使用过滤器的目的是用来做一些过滤操作,获取我们 ...

  8. java 拦截器和过滤器区别(转载)

    1.拦截器是基于java的反射机制的,而过滤器是基于函数回调 2.过滤器依赖与servlet容器,而拦截器不依赖与servlet容器 3.拦截器只能对action请求起作用,而过滤器则可以对几乎所有的 ...

  9. struts2拦截器和过滤器区别

    1.拦截器是基于java反射机制的,而过滤器是基于函数回调的.2.过滤器依赖于servlet容器,而拦截器不依赖于servlet容器.3.拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请 ...

  10. java 中的拦截器和过滤器

    区别: 1.拦截器是基于java的反射机制的,而过滤器是基于函数回调 2.过滤器依赖与servlet容器,而拦截器不依赖与servlet容器 3.拦截器只能对action请求起作用,而过滤器则可以对几 ...

随机推荐

  1. 1.简单的C语言程序

    简单的C语言程序 什么是计算机程序? 什么是计算机语言? 所谓程序,就是一组计算机能识别和执行的指令. 什么是计算机语言? 机器语言(0,1 '低级语言'),汇编语言(符号化 '低级语言'),高级语言 ...

  2. 一个网格合并(weld)小工具

    在日常开发中会有需求合并多个Mesh网格,并且它们重合处的顶点也要合并,而并非合并成两个subMesh. 而近期刚好在学习Geomipmap的细分,需要把多个mesh块进行合并,于是写了这个脚本 (简 ...

  3. CentOS7配置NFS服务并设置客户端自动挂载

    在CentOS7中配置NFS服务并设置客户端自动挂载的步骤如下: NFS服务端配置 安装NFS服务: 首先,你需要在CentOS 7服务器上安装NFS服务.你可以使用yum命令来安装: yum ins ...

  4. 微信小程序校园跑腿系统怎么做,如何做,要做多久

    ​ 在这个互联网快速发展.信息爆炸的时代,人人都离不开手机,每个人都忙于各种各样的事情,大学生也一样,有忙于学习,忙于考研,忙着赚学分,忙于参加社团,当然也有忙于打游戏的(还很多),但生活中的一些琐事 ...

  5. # [NOIP2011 提高组] 铺地毯

    传送锚点:https://www.luogu.com.cn/problem/P1003 题目描述 为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形 ...

  6. 设置 ASP.NET Core Web API 中响应数据的格式 AddNewtonsoftJson 使用NewtonsoftJson替换掉默认的System.Text.Json序列化组件

    #region 使用NewtonsoftJson替换掉默认的json序列化组件 .AddNewtonsoftJson(options => { 修改属性名称的序列化方式,首字母小写 //opti ...

  7. 在Mac上运行Rainbond,10分钟快速安装

    前言 以往安装部署 Rainbond 的方式都无法绕过 Kubernetes 集群的搭建,无论是作为开发环境还是用于生产交付,部署的过程都非常依赖于服务器或云主机.这在体验 Rainbond 云原生应 ...

  8. Jenkins通过脚本进行自动发布

    编写以下脚本: ------------------------------------------------------------------------------------- #!/bin ...

  9. Stable Diffusion 解析:探寻 AI 绘画背后的科技神秘

    AI 绘画发展史 在谈论 Stable Diffusion 之前,有必要先了解 AI 绘画的发展历程. 早在 2012 年,华人科学家吴恩达领导的团队训练出了当时世界上最大的深度学习网络.这个网络能够 ...

  10. vm ware 虚拟WIN XP 时卡

    主机是I7 9700,SN750 NVME 1T SSD, 32G 内存,VM WARE 14,理论上虚拟WIN XP 不会卡,但实际情况就是很卡. 虚拟WIN7 ,WIN 10,UBUNTU LIN ...