1、认识拦截器

  SpringMVC的拦截器(Interceptor)不是Filer,同样可以实现请求的预处理、后处理。使用拦截器仅需要两个步骤

  实现拦截器

  注册拦截器

1.1实现拦截器

  实现拦截器可以自定义实现HandleInterceptor接口,也可以继承HandleInterceptorAdatper类,后者是前者的实现类。

  下面是拦截器实现的一个例子,目的是判断用户是否登录。如果preHandle方法return true ,则后续方法继续执行。

  1 package zsjmsdemo.interceptor;
2
3 import java.io.PrintWriter;
4 import java.util.Set;
5 import java.util.concurrent.TimeUnit;
6
7 import javax.servlet.http.Cookie;
8 import javax.servlet.http.HttpServletRequest;
9 import javax.servlet.http.HttpServletResponse;
10
11 import org.springframework.data.redis.core.RedisTemplate;
12 import org.springframework.util.ObjectUtils;
13 import org.springframework.web.servlet.ModelAndView;
14 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
15
16 import com.alibaba.fastjson.JSON;
17 import com.ezhiyang.xxxb.common.DataResult;
18 import com.ezhiyang.xxxb.utils.MobileUtil;
19
20 public class LoginInterceptor extends HandlerInterceptorAdapter {
21
22 private RedisTemplate<String, Object> redisTemplate;
23
24 /**
25 * 预处理回调方法,实现处理器的预处理(如登陆检查/判断同一对象短时间内是否重复调用接口等) 第三个参数为相应的处理器即controller
26 * f返回true表示流程继续,调用下一个拦截器或者处理器,返回false表示流程中断,通过response产生响应
27 */
28 @Override
29 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
30 throws Exception {
31 String token = getToken(request);
32 if (!ObjectUtils.isEmpty(token)) {
33 //判断同一用户短时间内是否重复请求接口
34 String method = request.getMethod();
35 if (method!=null&&("POST".equalsIgnoreCase(method)||"GET".equalsIgnoreCase(method))) {
36 String requestUri = request.getRequestURI();
37 String url = requestUri.substring(request.getContextPath().length());//获取此次请求访问的是哪个接口
38 String ip=MobileUtil.getIpAddr(request);//获取当前的ip地址值
39 String requestKey=ip+"_"+token+"_"+url;
40 Object object = redisTemplate.opsForValue().get("requestKey");
41 //若重复请求则提示
42 if (!ObjectUtils.isEmpty(object)) {
43 DataResult dataResult=new DataResult();
44 dataResult.setCode("4001");
45 dataResult.setMsg("请求太频繁,请稍后再试");
46 response.setCharacterEncoding("utf-8");
47 response.setContentType("text/html; charset=utf-8");
48 PrintWriter writer=response.getWriter();
49 writer.print(JSON.toJSON(dataResult));
50 writer.close();
51 response.flushBuffer();
52 return false;
53 }
54 //若是第一次请求则记录请求标记,在处理器执行完成后删除标记
55 redisTemplate.opsForValue().set(requestKey, url, 5, TimeUnit.SECONDS);//设置5秒,只是为了防止拦截器的后置处理方法没执行到(比如突然断电),导致后续的同类请求都不能执行
56 //如果请求允许,就记住key,请求处理完后,还要删除标识
57 request.setAttribute("ACCESS_KEY", requestKey);
58 }
59
60 //根据token从redis中获取登录信息
61 Set<String> keys = redisTemplate.keys(token);
62 if (!ObjectUtils.isEmpty(keys) && keys.size() == 1) {
63 Object nimitokenvalue = redisTemplate.opsForValue().get(keys.iterator().next());
64 if (ObjectUtils.isEmpty(nimitokenvalue)) {
65 return true;
66 }
67 } else {
68 Object loginInfo = redisTemplate.boundValueOps(token)
69 .get();
70 if (loginInfo != null) {
71 return true;
72 }
73 }
74 }else{
75 response.setContentType("text/html; charset=utf-8");
76 PrintWriter writer = response.getWriter();
77 writer.print(new DataResult("4001", "没登陆", new Object()));
78 writer.close();
79 response.flushBuffer();
80 return false;
81 }
82 return super.preHandle(request, response, handler);
83 }
84
85
86 /**
87 * 当请求进行处理之后,也就是controller方法调用之后执行,但是他会在DispatcherServlet进行视图渲染之前被调用
88 * 此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理
89 */
90 @Override
91 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
92 ModelAndView modelAndView) throws Exception {
93 System.out.println("-------------------postHandle");
94 String requestKey = (String)request.getAttribute("ACCESS_KEY");
95 if (requestKey!=null) {
96 redisTemplate.delete(requestKey);
97 }
98 }
99
100 /**
101 * 方法将在整个请求结束之后,也就是DispatcheServlet进行视图渲染之后执行,这个方法的主要作用是对资源的清理工作
102 */
103 @Override
104 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
105 throws Exception {
106 System.out.println("-------------------afterCompletion");
107 }
108
109 @Override
110 public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
111 throws Exception {
112 }
113
114
115 /**
116 * 获取token
117 * @param req
118 * @return
119 */
120 public static String getToken(HttpServletRequest req) {
121 String token = req.getParameter("token");
122 if (!ObjectUtils.isEmpty(token)) {
123 return token;
124 } else {
125 Cookie[] cks = req.getCookies();
126 if (cks != null) {
127 for (Cookie ck : cks) {
128 if (ck.getName().equals("token")) {
129 return ck.getValue();
130 }
131 }
132 }
133 return req.getHeader("token");
134 }
135 }
136
137 }

1.2注册拦截器

 1 package com.cxs.allmodel.interceptor;
2
3 import javax.annotation.Resource;
4
5 import org.springframework.context.annotation.Configuration;
6 import org.springframework.data.redis.core.RedisTemplate;
7 import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
8 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
9 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
10
11 /**
12 * 为了使自定义的拦截器生效,需要注册拦截器到spring容器中,具体的做法是继承WebMvcConfigurerAdapter类,
13 * 覆盖其addInterceptors(InterceptorRegistry registry)方法。最后别忘了把Bean注册到Spring容器中,
14 * 可以选择@Component 或者 @Configuration。
15 *
16 */
17 @Configuration
18 public class InterceptorConfig implements WebMvcConfigurer{
19 //在拦截器执行时实例化redisTemplate
20 @Resource
21 private RedisTemplate<String, Object> redisTemplate;
22
23 @Override
24 public void addInterceptors(InterceptorRegistry registry) {
25 // 注册拦截器
26 InterceptorRegistration ir = registry.addInterceptor(new LoginInterceptor(redisTemplate));
27 // 配置拦截的路径
28 ir.addPathPatterns("/**");
29 // 配置不拦截的路径
30 ir.excludePathPatterns("/user/info","/user/add");
31
32 // 还可以在这里注册其它的拦截器
33 //registry.addInterceptor(new OtherInterceptor()).addPathPatterns("/**");
34 }
35 }

 也可以直接在SpringBoot的启动类中继承WebMvcConfigurerAdaoter类

  1 package cn.wowkai.mall;
2
3 import java.nio.charset.Charset;
4 import java.util.Arrays;
5
6 import javax.annotation.PostConstruct;
7 import javax.annotation.Resource;
8 import javax.sql.DataSource;
9
10 import org.springframework.boot.SpringApplication;
11 import org.springframework.boot.autoconfigure.SpringBootApplication;
12 import org.springframework.context.annotation.Bean;
13 import org.springframework.data.redis.core.RedisTemplate;
14 import org.springframework.data.redis.serializer.StringRedisSerializer;
15 import org.springframework.http.HttpHeaders;
16 import org.springframework.scheduling.annotation.EnableScheduling;
17 import org.springframework.web.cors.CorsConfiguration;
18 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
19 import org.springframework.web.filter.CorsFilter;
20 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
21 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
22
23 import com.fd.myshardingfordata.helper.ConnectionManager;
24 import com.fd.myshardingfordata.helper.TransManager;
25
26 import cn.wowkai.mall.web.interceptor.AuthInterceptor;
27
28 @EnableScheduling
29 @SpringBootApplication
30 public class ShopSaasMallApplication implements WebMvcConfigurer {
31
32 public static void main(String[] args) {
33 SpringApplication.run(ShopSaasMallApplication.class, args);
34 }
35
36 @Resource
37 protected RedisTemplate<String, Object> redisTemplate;
38
39 @PostConstruct
40 private void init() {
41 redisTemplate.setKeySerializer(new StringRedisSerializer(Charset.forName("UTF8")));
42 redisTemplate.setValueSerializer(new StringRedisSerializer(Charset.forName("UTF8")));
43 }
44
45 @Override
46 public void addInterceptors(InterceptorRegistry registry) {
47 registry.addInterceptor(new AuthInterceptor(redisTemplate)).excludePathPatterns("/mall/mp/*",
48 "/mall/notify/panganNotify", "/mall/notify/panganRechargeNotify", "/mall/notify/panganbillNotify",
49 "/mall/notify/wxpayrechargenotify", "/mall/notify/wxpaybillnotify", "/mall/notify/wxpaynotify",
50 "/mall/api/getToken", "/mall/store/getStoreListWithLongitudeLatitude",
51 "/mall/sett/getIndexTemplateListWithXcx", "/mall/product/productListForXcxSelect",
52 "/mall/product/custFindProduct", "/mall/product/custFindProductSpeceInventoryAndPrice",
53 "/mall/sett/xcx/getIndexTemplateMastList");
54 }
55
56 @Resource
57 private DataSource dataSource;
58
59 @Bean
60 public TransManager transManager() {
61 TransManager trans = new TransManager();
62 trans.setConnectionManager(connectionManager());
63 return trans;
64 }
65
66 @Bean
67 public ConnectionManager connectionManager() {
68 ConnectionManager conm = new ConnectionManager();
69 conm.setGenerateDdl(true);
70 conm.setShowSql(false);
71 conm.setInitConnect("set names utf8mb4");
72 conm.setDataSource(dataSource);
73 conm.setReadDataSources(Arrays.asList(dataSource));
74
75 return conm;
76 }
77
78 @Bean
79 public CorsFilter corsFilter() {
80 // 1.添加CORS配置信息
81 CorsConfiguration config = new CorsConfiguration();
82 // 放行哪些原始域
83 config.addAllowedOrigin("*");
84 // 是否发送Cookie信息
85 config.setAllowCredentials(true);
86 // 放行哪些原始域(请求方式)
87 config.addAllowedMethod("*");
88 // 放行哪些原始域(头部信息)
89 config.addAllowedHeader("*");
90 // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
91 config.addExposedHeader(HttpHeaders.LOCATION);
92 config.setExposedHeaders(Arrays.asList("JSESSIONID", "SESSION", "token", HttpHeaders.LOCATION,
93 HttpHeaders.ACCEPT, HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
94 HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, HttpHeaders.COOKIE, HttpHeaders.SET_COOKIE,
95 HttpHeaders.SET_COOKIE2));
96 // 2.添加映射路径
97 UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
98 configSource.registerCorsConfiguration("/**", config);
99
100 // 3.返回新的CorsFilter.
101 return new CorsFilter(configSource);
102 }
103
104 }

1.3拦截器的应用场景

  拦截器的本质是面向切面编程(AOP),符合橫切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括:

  1、登录验证,判断用户是否登录。

  2、权限验证,判断用户是否具有访问权限。

  3、日志记录,记录请求日志,以便统计请求访问量。

  4、处理cookie、本地化、国际化、主题等。

  5、性能监控,监控请求处理时长等。

  6、防止同一用户在短时间内重复请求接口

2、原理

2.1、工作原理

  拦截器不是Filter,却实现了filter的功能,其原理在于:

  所有的拦截器(Interceptor)和处理器(Handler)都注册在HandlerMapping中。

  Spring MVC中所有的请求都是由DispatcherServlet分发的。

  当请求进入DispatcherServlet.doDispatch()时候,首先会得到处理该请求的Handler(即Controller中对应的方法)以及所有拦截该请求的拦截器。拦截器就是在这里被调用开始工作的。
 
2.2、拦截器工作流程

  一个拦截器,只有preHandle方法返回true,postHandleafterCompletion才有可能被执行;如果preHandle方法返回false,则该拦截器的postHandleafterCompletion必然不会被执行。

假设我们有两个拦截器,例如叫Interceptor1和Interceptor2,当一个请求过来,正常的流程和中断的流程分别如下。

2.2.1正常流程

  注意两个拦截器在执行preHandle方法和执行postHandleafterCompletion方法时,顺序是颠倒的。

 1 Interceptor1.preHandle
2
3 Interceptor2.preHandle
4
5 //Controller处理请求
6
7 Interceptor2.postHandle
8
9 Interceptor1.postHandle
10
11 //渲染视图
12
13 Interceptor2.afterCompletion
14
15 Interceptor1.afterCompletion

2.2.2中断流程

  假设执行Interceptor2.preHandle中报错,那么流程被中断,之前被执行过的拦截器的afterCompletion仍然会执行。在本例中,即执行了Interceptor1.afterCompletion
1 Interceptor1.preHandle
2
3 Interceptor2.preHandle
4
5 //中间流程被中断,不再执行
6
7 Interceptor1.afterCompletion

2.3和Filter共存时的执行顺序

  拦截器是在DispatcherServlet这个servlet中执行的,因此所有的请求最先进入Filter,最后离开Filter。其顺序如下。

 1 Filter
2
3 Interceptor.preHandle
4
5 Handler
6
7 Interceptor.postHandle
8
9 Interceptor.afterCompletion
10
11 Filter

在SpringBoot项目中添加SpringMVC拦截器的更多相关文章

  1. Spring Boot项目中如何定制拦截器

    本文首发于个人网站:Spring Boot项目中如何定制拦截器 Servlet 过滤器属于Servlet API,和Spring关系不大.除了使用过滤器包装web请求,Spring MVC还提供Han ...

  2. 在SpringBoot项目中添加logback的MDC

    在SpringBoot项目中添加logback的MDC     先看下MDC是什么 Mapped Diagnostic Context,用于打LOG时跟踪一个“会话“.一个”事务“.举例,有一个web ...

  3. 在springboot项目中引入quartz任务调度器。

    quartz是一个非常强大的任务调度器.我们可能使用它来管理我们的项目,常见的是做业绩统计等等.当然它的功能远不止这些.我们在这里不介绍quartz的原理,下面讲讲如何在springboot中使用qu ...

  4. SpringBoot项目中,异常拦截

    SpringBoot自带异常拦截@ControllerAdvice 1.创建一个SellerExceptionHandler类打上@ControllerAdvice标签 @ControllerAdvi ...

  5. Springboot中SpringMvc拦截器配置与应用(实战)

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

  6. springboot(五).如何在springboot项目中使用拦截器

    在每个项目中,拦截器都是我们经常会去使用的东西,基本上任一一个项目都缺不了拦截器的使用. 如日志记录.登录验证,session验证等,都需要拦截器来拦截URL请求,那springboot中的拦截器是如 ...

  7. taotao用户登录springMVC拦截器的实现

    在springMVC中写拦截器,只需要两步: 一.写 java 拦截器类,实现 interceptor 拦截器接口. 二.在 springMVC 的xml配置文件中,配置我们创建的拦截器对象及其拦截目 ...

  8. 基于SpringMVC拦截器和注解实现controller中访问权限控制

    SpringMVC的拦截器HandlerInterceptorAdapter对应提供了三个preHandle,postHandle,afterCompletion方法. preHandle在业务处理器 ...

  9. SpringMVC之八:基于SpringMVC拦截器和注解实现controller中访问权限控制

    SpringMVC的拦截器HandlerInterceptorAdapter对应提供了三个preHandle,postHandle,afterCompletion方法. preHandle在业务处理器 ...

随机推荐

  1. 微服务架构 | 7.2 构建使用 JWT 令牌存储的 OAuth2 安全认证

    目录 前言 1. JWT 令牌存储基础知识 1.1 JSON Web Token 2. 构建使用 JWT 令牌存储的 OAuth2 服务器 2.1 引入 pom.xml 依赖文件 2.2 创建 JWT ...

  2. Lesson1——Pandas是什么

    pandas目录 一.简介 Pandas 是一个开源的第三方 Python 库,从 Numpy 和 Matplotlib 的基础上构建而来,享有数据分析"三剑客之一"的盛名(Num ...

  3. Redis 哨兵模式

    主从切换技术的方法是:当主服务器宕机了,需要手动将一台从服务器切换为主服务器,这就需要人工干预,这可能会造成一段时间的服务不可用. 一.哨兵模式的概述: 哨兵是一个独立的进程,作为一个进程,他会独立地 ...

  4. Java基础复习(六)

    1. 接口的实现类中的实现接口中的抽象方法的方法必须为public,为什么? 接口中所有的方法与变量都默认是 public 的,在接口中可以不写出来.但在实现类中,如果不明写的话,就变成了 frien ...

  5. python基础——异常处理、递归

    异常处理 while True: try: num1 = int(input('num1:')) num2 = int(input('num2:')) result = num1 + num2 exc ...

  6. Solution -「JOISC 2021」「LOJ #3489」饮食区

    \(\mathcal{Description}\)   Link.   呐--不想概括题意,自己去读叭~ \(\mathcal{Solution}\)   如果仅有 1. 3. 操作,能不能做?    ...

  7. Solution -「NOI 2020」「洛谷 P6776」超现实树

    \(\mathcal{Description}\)   Link.   对于非空二叉树 \(T\),定义 \(\operatorname{grow}(T)\) 为所有能通过若干次"替换 \( ...

  8. Solution -「洛谷 P4449」于神之怒加强版

    \(\mathcal{Description}\)   Link.   给定 \(k\) 和 \(T\) 组 \(n,m\),对于每组,求 \[\sum_{i=1}^n\sum_{j=1}^m\ope ...

  9. 「微前端实践」使用Vue+qiankun微前端方案重构老项目的本地验证

    10月份换了新的工作,参与完一个月的需求迭代后,接到了项目重构的任务.简单来说,需要在短时间内提出方案设想,同时进行本地验证,最终需要拿出一套技术替换方案来.于是,埋头苦干了一个月,总算干了点成绩出来 ...

  10. CentOS7下修改默认网卡名为eth0的方法

    1.修改网卡配置文件中的 DEVICE=参数的,关于eth0 [root@ansheng ~ ]# cd /etc/sysconfig/network-scripts/ [root@ansheng n ...