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. WEB服务与NGINX(6)-location使用详解

    目录 1. location的详细用法 1.1 精确匹配 1.2 区分大小写 1.3 不区分大小写 1.4 匹配URI开始 1.5 测试location的优先级 1.6 location的生产使用示例 ...

  2. JavaScript前端时间库moment.js

    1.获取当前时间 moment().format("YYYY-MM-DD HH:mm:ss"); moment().format("YYYY-MM-DD"); ...

  3. grads读取nc格式文件

    一.通常: 1.grads读取grd和ctl:open  ****.ctl 2.执行gs脚本:run   ****.gs d命令,display展示数据,常用来显示变量,比如rh,rain等 q命令, ...

  4. 在 Chromebook 上使用 Word 的最佳方法

    Splashtop 允许您从 Chromebook 远程控制 Windows 和 Mac 计算机,从而可以访问 Word 的桌面版本和所有文件. 对于远程工作者和学生,Chromebook 可以是一种 ...

  5. C语言:学生成绩排名----冒泡排序

    题目: /*     输入10个学生成绩.     计算总分以及平均分数.     求出高于等于平均分的人数.     再按降序(由大到小)排序成绩并输出      */ 用到的冒泡算法代码模板: / ...

  6. Dapr 与 .NET Aspire 结合使用获得无与伦比的本地开发体验

    Dapr 提供了一组构建块,用于抽象分布式系统中常用的概念.这包括服务.缓存.工作流.复原能力.机密管理等之间的安全同步和异步通信.不必自己实现这些功能,可以消除样板,降低复杂性,并允许您专注于开发业 ...

  7. AIRIOT答疑第6期|如何使用二次开发引擎?

    ​​灵活扩展,满足客户定制化需求   AIRIOT物联网低代码平台提供丰富的前端.后台服务二次开发接口,具备灵活的组件服务部署与管理能力,对任何功能模块进行二次开发,满足客户各类二次开发需求.支持多种 ...

  8. C# npoi追加写入时报错 因为文件格式或文件扩展名无效。

    造成原因:workbook对象打开后,没有手动close造成的. 使用的npoi版本:2.6.0 ,环境 win10 .net core 5.0    

  9. Python RabbitMQ Demo

    fanout消息订阅模式 生产者 # 生产者代码 import pika credentials = pika.PlainCredentials('guest', 'guest') # mq用户名和密 ...

  10. FlexBox 行间距

    问题背景 在Flex布局方式下, 父容器约定是换行的方式, 不足以容纳一行子元素的时候, 会单独进行折行, 那么折行的行间距如何处理呢? 解决办法 通过在子Item上面设置margin-top可以模拟 ...