在SpringBoot项目中添加SpringMVC拦截器

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
posted @ 2020-01-07 15:02  左手daima右手诗  阅读(1387)  评论(0编辑  收藏  举报