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. 教你如何破解虚拟机root密码

    一.开机时按e键,即可进入grub编辑界面,光标选择第一条,e表示进入编辑模式. 当机器开机出现如图1所示的kernel菜单后,按e键. 二.将光标移动到以linux开头的行,对图2中标注的位置进行修 ...

  2. JS实现下拉框切换和tab标签切换

    现在商城网页上会有下拉框切换内容,是如何实现的呢,研究了一天,在调整js代码和查找bug.最终完成了自己想要的效果,我没有写CSS样式,只是实现了基本功能,如果对你有所帮助,可以自己写css,使其更加 ...

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

    本篇参考: Salesforce LWC学习(七) Navigation & Toast https://developer.salesforce.com/docs/platform/lwc/ ...

  4. es命令大全,elasticsearch命令详解

    参考链接 Relational DB Elasticsearch 数据库(database) 索引(indices) 表(tables) types 行(rows) documents 字段(colu ...

  5. 线程安全使用 HashMap 的四种技巧

    这篇文章,我们聊聊线程安全使用 HashMap 的四种技巧. 1方法内部:每个线程使用单独的 HashMap 如下图,tomcat 接收到到请求后,依次调用控制器 Controller.服务层 Ser ...

  6. XML Schema 字符串数据类型及约束详解

    字符串数据类型用于包含字符字符串的值.字符串数据类型可以包含字符.换行符.回车符和制表符. 以下是模式中字符串声明的示例: <xs:element name="customer&quo ...

  7. 解决:ModuleNotFoundError: No module named ‘urllib3.packages.six.moves问题

    解决方案: 第一步:把selenium版本降到3.3.1 配合urllib3版本1.26.2使用,成功解决 &在python interpreter安装availablePackages se ...

  8. 改造 Kubernetes 自定义调度器

    原文出处:改造 Kubernetes 自定义调度器 | Jayden's Blog (jaydenchang.top) Overview Kubernetes 默认调度器在调度 Pod 时并不关心特殊 ...

  9. 类的阐述 package(包)

    类的阐述 同一个文件中可以定义很多类 编译后,每个类都会生成独立的.class文件 一个类中,只能有一个主函数,每个类都可以有自己的主函数 public修饰的类称为公开类,要求类名必须与文件名称完全相 ...

  10. Cage 字符串听课笔记

    困困困! KMP 注意到 KMP 的复杂度是均摊的,那么是否可以绕开? 注意到 KMP 实际上一个串的 ACAM,那么考虑可以类似的,在加入一个字符的同时维护 ACAM(考虑 ACAM 的构建过程,前 ...