在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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM