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. cesium教程8-官方示例翻译-图层亮度对比度调整

    完整示例代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="u ...

  2. 80x86汇编—80x86架构

    文章目录 计算机如何工作 存储器 逻辑地址到物理地址 寄存器 数据寄存器使用细节 其他知识点细节 堆栈Stack 标志寄存器 中断 汇编入门简单,深入难 使用8086架构进行学习,本章节如果没有学过计 ...

  3. kubeadm部署的k8s证书过期问题 k8s问题排查:the existing bootstrap client certificate in /etc/kubernetes/kubelet.conf is expired

    解决问题: 估计跟移动有关,下面那个没解决问题,是因为在原有文件的基础上修改的吧?而这里直接是移走,重新生成了新的.不太清楚是不是这个原因. $ cd /etc/kubernetes/pki/ $ m ...

  4. ansible功能实现

    模糊匹配远程主机文件并拉取到本地服务器 又熬夜加班了.花很长时间研究出来.如何实现模糊匹配到的远程文件批量拉取到本地的剧本.使用copy模块的*,shll模块的* ls|grep XX都没有实现,貌似 ...

  5. 一款基于C#开发的通讯调试工具(支持Modbus RTU、MQTT调试)

    前言 今天大姚给大家分享一款基于C#.WPF.Prism.MaterialDesign.HandyControl开发的通讯调试工具(支持Modbus RTU.MQTT调试,界面色彩丰富):Wu.Com ...

  6. WPF自定义控件,如何使得xaml涉及器中的修改能立即反应到预览

    这是我无意中发现的,xaml中设置的是依赖属性而不是包装器,所以我们可以直接在注册依赖属性那里设置回调,触发某个控件重绘,比如本身或父控件重绘. xaml设计器就会实时更新 1 // !!!由于xam ...

  7. salesforce零基础学习(一百三十八)零碎知识点小总结(十)

    本篇参考: https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_5level_SOQLqueries.htm& ...

  8. 可视化学习:使用极坐标参数方程和SDF绘制有趣的图案

    前言 本文将介绍如何使用极坐标参数方程和上一篇文章提到的距离场SDF来绘制有趣的图案. 说到曲线和几何图形的绘制,我们知道图形系统默认支持的是通过直角坐标绘制,但是有些曲线呢,不太容易使用直角坐标系来 ...

  9. 资源编排ROS之模块:实现模板代码复用(基础篇)

    背景 资源编排服务(Resource Orchestration Service, 简称ROS)是阿里云提供的一项简化云计算资源管理的服务.您可以遵循ROS定义的模板规范编写资源栈模板,在模板中定义所 ...

  10. 容器化部署wordpress个人博客系统lnmp环境[自定义网络]

    容器化部署个人博客系统lnmp环境 #告警: WARNING: IPv4 forwarding is disabled. Networking will not work. 96c083a8b5811 ...