吴忠躺衫网络科技有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

SpringBoot攔截器與統一功能處理實戰

jf_ro2CN3Fa ? 來源:CSDN ? 2023-08-27 10:44 ? 次閱讀

前言

Spring AOP是一個基于面向切面編程的框架,用于將橫切性關注點(如日志記錄、事務管理)與業務邏輯分離,通過代理對象將這些關注點織入到目標對象的方法執行前后、拋出異常或返回結果時等特定位置執行,從而提高程序的可復用性、可維護性和靈活性。

但使用原生Spring AOP實現統一的攔截是非常繁瑣、困難的。而在本節,我們將使用一種簡單的方式進行統一功能處理,這也是AOP的一次實戰,具體如下:

統一用戶登錄權限驗證

統一數據格式返回

統一異常處理

0 為什么需要統一功能處理?

統一功能處理是為了提高代碼的可維護性、可重用性和可擴展性而進行的一種設計思想。在應用程序中,可能存在一些通用的功能需求,例如身份驗證、日志記錄、異常處理等。

這些功能需要在多個地方進行調用和處理,如果每個地方都單獨實現這些功能,會導致代碼冗余、難以維護和重復勞動。通過統一功能處理的方式,可以將這些通用功能抽取出來,以統一的方式進行處理。這樣做有以下幾個好處:

「代碼復用」 :將通用功能抽取成獨立的模塊或組件,可以在多個地方共享使用,減少重復編寫代碼的工作量。

「可維護性」 :將通用功能集中處理,可以方便地對其進行修改、優化或擴展,而不需要在多個地方進行修改。

「代碼整潔性」 :通過統一功能處理,可以使代碼更加清晰、簡潔,減少了冗余的代碼。

「可擴展性」 :當需要添加新的功能時,只需要在統一功能處理的地方進行修改或擴展,而不需要在多個地方進行修改,降低了代碼的耦合度。

1 統一用戶登錄權限驗證

1.1 使用原生 Spring AOP 實現統一攔截的難點

以使用原生 Spring AOP 來實現?戶統?登錄驗證為例,主要是使用前置通知和環繞通知實現的,具體實現如下

importorg.aspectj.lang.ProceedingJoinPoint;
importorg.aspectj.lang.annotation.*;
importorg.springframework.stereotype.Component;

/**
*@author興趣使然黃小黃
*@version1.0
*@date2023/7/1816:37
*/
@Aspect//表明此類為一個切面
@Component//隨著框架的啟動而啟動
publicclassUserAspect{
//定義切點,這里使用Aspect表達式語法
@Pointcut("execution(*com.hxh.demo.controller.UserController.*(..))")
publicvoidpointcut(){}


//前置通知
@Before("pointcut()")
publicvoidbeforeAdvice(){
System.out.println("執行了前置通知~");
}

//環繞通知
@Around("pointcut()")
publicObjectaroundAdvice(ProceedingJoinPointjoinPoint){
System.out.println("進入環繞通知~");
Objectobj=null;
//執行目標方法
try{
obj=joinPoint.proceed();
}catch(Throwablee){
e.printStackTrace();
}
System.out.println("退出環繞通知~");
returnobj;
}

}

從上述的代碼示例可以看出,使用原生的 Spring AOP 實現統一攔截的難點主要有以下幾個方面:

定義攔截規則非常困難。如注冊?法和登錄?法是不攔截的,這樣的話排除?法的規則很難定義,甚?沒辦法定義。

在切面類中拿到 HttpSession 比較難。

為了解決 Spring AOP 的這些問題,Spring 提供了攔截器~

1.2 使用 Spring 攔截器實現統一用戶登錄驗證

Spring攔截器是Spring框架提供的一個功能強大的組件,用于在請求到達控制器之前或之后進行攔截和處理。攔截器可以用于實現各種功能,如身份驗證、日志記錄、性能監測等。

要使用Spring攔截器,需要創建一個實現了HandlerInterceptor接口的攔截器類。該接口定義了三個方法:preHandle、postHandle和afterCompletion。

preHandle方法在請求到達控制器之前執行,可以用于進行身份驗證、參數校驗等;

postHandle方法在控制器處理完請求后執行,可以對模型和視圖進行操作;

afterCompletion方法在視圖渲染完成后執行,用于清理資源或記錄日志。

攔截器的實現可以分為以下兩個步驟:

創建自定義攔截器,實現 HandlerInterceptor 接口的 preHandle(執行具體方法之前的預處理)方法。

將自定義攔截器加入 WebMvcConfigurer 的 addInterceptors 方法中,并且設置攔截規則。

具體實現如下:

step1. 創建自定義攔截器,自定義攔截器是一個普通類,代碼如下:

importorg.springframework.web.servlet.HandlerInterceptor;

importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjavax.servlet.http.HttpSession;

/**
*@author興趣使然黃小黃
*@version1.0
*@date2023/7/1916:31
*統一用戶登錄權限驗證——登錄攔截器
*/
publicclassLoginInterceptorimplementsHandlerInterceptor{
@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{
//用戶登錄業務判斷
HttpSessionsession=request.getSession(false);
if(session!=null&&session.getAttribute("userinfo")!=null){
returntrue;//驗證成功,繼續controller的流程
}
//可以跳轉登錄界面或者返回401/403沒有權限碼
response.sendRedirect("/login.html");//跳轉到登錄頁面
returnfalse;//驗證失敗
}
}

step2. 配置攔截器并設置攔截規則,代碼如下:

importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;
importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
*@author興趣使然黃小黃
*@version1.0
*@date2023/7/1916:51
*/
@Configuration
publicclassAppConfigimplementsWebMvcConfigurer{
@Override
publicvoidaddInterceptors(InterceptorRegistryregistry){
registry.addInterceptor(newLoginInterceptor())
.addPathPatterns("/**")//攔截所有請求
.excludePathPatterns("/user/login")//不攔截的url地址
.excludePathPatterns("/user/reg")
.excludePathPatterns("/**/*.html");//不攔截所有頁面
}
}

1.3 攔截器的實現原理及源碼分析

當有了攔截器后,會在調用 Controller 之前進行相應的業務處理,執行的流程如下圖所示:

8932c162-4482-11ee-a2ef-92fbcf53809c.png

「攔截器實現原理的源碼分析」

從上述案例實現結果的控制臺的日志信息可以看出,所有的 Controller 執?都會通過?個調度器 DispatcherServlet 來實現。

8990fae8-4482-11ee-a2ef-92fbcf53809c.png

而所有的方法都會執行 DispatcherServlet 中的 doDispatch 調度方法,doDispatch 源碼如下:

protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{
HttpServletRequestprocessedRequest=request;
HandlerExecutionChainmappedHandler=null;
booleanmultipartRequestParsed=false;
WebAsyncManagerasyncManager=WebAsyncUtils.getAsyncManager(request);
try{
try{
ModelAndViewmv=null;
ObjectdispatchException=null;
try{
processedRequest=this.checkMultipart(request);
multipartRequestParsed=processedRequest!=request;
mappedHandler=this.getHandler(processedRequest);
if(mappedHandler==null){
this.noHandlerFound(processedRequest,response);
return;
}
HandlerAdapterha=this.getHandlerAdapter(mappedHandler.getHandler());
Stringmethod=request.getMethod();
booleanisGet=HttpMethod.GET.matches(method);
if(isGet||HttpMethod.HEAD.matches(method)){
longlastModified=ha.getLastModified(request,mappedHandler.getHandler());
if((newServletWebRequest(request,response)).checkNotModified(lastModified)&&isGet){
return;
}
}

//調用預處理
if(!mappedHandler.applyPreHandle(processedRequest,response)){
return;
}
//執行Controller中的業務
mv=ha.handle(processedRequest,response,mappedHandler.getHandler());
if(asyncManager.isConcurrentHandlingStarted()){
return;
}
this.applyDefaultViewName(processedRequest,mv);
mappedHandler.applyPostHandle(processedRequest,response,mv);
}catch(Exceptionvar20){
dispatchException=var20;
}catch(Throwablevar21){
dispatchException=newNestedServletException("Handlerdispatchfailed",var21);
}
this.processDispatchResult(processedRequest,response,mappedHandler,mv,(Exception)dispatchException);
}catch(Exceptionvar22){
this.triggerAfterCompletion(processedRequest,response,mappedHandler,var22);
}catch(Throwablevar23){
this.triggerAfterCompletion(processedRequest,response,mappedHandler,newNestedServletException("Handlerprocessingfailed",var23));
}
}finally{
if(asyncManager.isConcurrentHandlingStarted()){
if(mappedHandler!=null){
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest,response);
}
}elseif(multipartRequestParsed){
this.cleanupMultipart(processedRequest);
}
}
}

從上述源碼可以看出,在執行 Controller 之前,先會調用 預處理方法 applyPreHandle,該方法源碼如下:

booleanapplyPreHandle(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{
for(inti=0;i

在上述源碼中,可以看出,在 applyPreHandle 中會獲取所有攔截器 HandlerInterceptor 并執行攔截器中的 preHandle 方法,這與之前我們實現攔截器的步驟對應,如下圖所示:

89dc818e-4482-11ee-a2ef-92fbcf53809c.png

此時,相應的preHandle中的業務邏輯就會執行。

1.4 統一訪問前綴添加

統一訪問前綴的添加與登錄攔截器實現類似,即給所有請求地址添加 /hxh 前綴,示例代碼如下:

@Configuration
publicclassAppConfigimplementsWebMvcConfigurer{
//給所有接口添加/hxh前綴
@Override
publicvoidconfigurePathMatch(PathMatchConfigurerconfigurer){
configurer.addPathPrefix("/hxh",c->true);
}
}

另一種方式是在application配置文件中配置:

server.servlet.context-path=/hxh

2 統一異常處理

統一異常處理是指 在應用程序中定義一個公共的異常處理機制,用來處理所有的異常情況。 這樣可以避免在應用程序中分散地處理異常,降低代碼的復雜度和重復度,提高代碼的可維護性和可擴展性。

需要考慮以下幾點:

異常處理的層次結構:定義異常處理的層次結構,確定哪些異常需要統一處理,哪些異常需要交給上層處理。

異常處理的方式:確定如何處理異常,比如打印日志、返回錯誤碼等。

異常處理的細節:處理異常時需要注意的一些細節,比如是否需要事務回滾、是否需要釋放資源等

本文講述的統一異常處理使用的是 @ControllerAdvice + @ExceptionHandler 來實現的:

@ControllerAdvice 表示控制器通知類。

@ExceptionHandler 異常處理器

以上兩個注解組合使用,表示當出現異常的時候執行某個通知,即執行某個方法事件,具體實現代碼如下:

importorg.springframework.web.bind.annotation.ControllerAdvice;
importorg.springframework.web.bind.annotation.ExceptionHandler;
importorg.springframework.web.bind.annotation.ResponseBody;

importjava.util.HashMap;

/**
*@author興趣使然黃小黃
*@version1.0
*@date2023/7/1918:27
*統一異常處理
*/
@ControllerAdvice//聲明是一個異常處理器
publicclassMyExHandler{

//攔截所有的空指針異常,進行統一的數據返回
@ExceptionHandler(NullPointerException.class)//統一處理空指針異常
@ResponseBody//返回數據
publicHashMapnullException(NullPointerExceptione){
HashMapresult=newHashMap<>();
result.put("code","-1");//與前端定義好的異常狀態碼
result.put("msg","空指針異常:"+e.getMessage());//錯誤碼的描述信息
result.put("data",null);//返回的數據
returnresult;
}
}

上述代碼中,實現了對所有空指針異常的攔截并進行統一的數據返回。

在實際中,常常設置一個保底,比如發生的非空指針異常,也會有保底措施進行處理,類似于 try-catch 塊中使用 Exception 進行捕獲,代碼示例如下:

@ExceptionHandler(Exception.class)
@ResponseBody
publicHashMapexception(Exceptione){
HashMapresult=newHashMap<>();
result.put("code","-1");//與前端定義好的異常狀態碼
result.put("msg","異常:"+e.getMessage());//錯誤碼的描述信息
result.put("data",null);//返回的數據
returnresult;
}

3 統一數據返回格式

為了保持 API 的一致性和易用性,通常需要使用統一的數據返回格式。 一般而言,一個標準的數據返回格式應該包括以下幾個元素:

狀態碼:用于標志請求成功失敗的狀態信息;

消息:用來描述請求狀態的具體信息;

數據:包含請求的數據信息;

時間戳:可以記錄請求的時間信息,便于調試和監控。

實現統一的數據返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式實現,具體步驟如下:

創建一個類,并添加 @ControllerAdvice 注解;

實現 ResponseBodyAdvice 接口,并重寫 supports 和 beforeBodyWrite 方法。

示例代碼如下:

importorg.springframework.core.MethodParameter;
importorg.springframework.http.MediaType;
importorg.springframework.http.server.ServerHttpRequest;
importorg.springframework.http.server.ServerHttpResponse;
importorg.springframework.web.bind.annotation.ControllerAdvice;
importorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

importjava.util.HashMap;

/**
*@author興趣使然黃小黃
*@version1.0
*@date2023/7/1918:59
*統一數據返回格式
*/
@ControllerAdvice
publicclassResponseAdviceimplementsResponseBodyAdvice{

/**
*此方法返回true則執行下面的beforeBodyWrite方法,反之則不執行
*/
@Override
publicbooleansupports(MethodParameterreturnType,ClassconverterType){
returntrue;
}

/**
*方法返回之前調用此方法
*/
@Override
publicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,ClassselectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){
HashMapresult=newHashMap<>();
result.put("code",200);
result.put("msg","");
result.put("data",body);
returnnull;
}
}

但是,如果返回的 body 原始數據類型是 String ,則會出現類型轉化異常,即 ClassCastException。

因此,如果原始返回數據類型為 String ,則需要使用 jackson 進行單獨處理,實現代碼如下:

importcom.fasterxml.jackson.core.JsonProcessingException;
importcom.fasterxml.jackson.databind.ObjectMapper;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.core.MethodParameter;
importorg.springframework.http.MediaType;
importorg.springframework.http.server.ServerHttpRequest;
importorg.springframework.http.server.ServerHttpResponse;
importorg.springframework.web.bind.annotation.ControllerAdvice;
importorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

importjava.util.HashMap;

/**
*@author興趣使然黃小黃
*@version1.0
*@date2023/7/1918:59
*統一數據返回格式
*/
@ControllerAdvice
publicclassResponseAdviceimplementsResponseBodyAdvice{

@Autowired
privateObjectMapperobjectMapper;

/**
*此方法返回true則執行下面的beforeBodyWrite方法,反之則不執行
*/
@Override
publicbooleansupports(MethodParameterreturnType,ClassconverterType){
returntrue;
}

/**
*方法返回之前調用此方法
*/
@Override
publicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,ClassselectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){
HashMapresult=newHashMap<>();
result.put("code",200);
result.put("msg","");
result.put("data",body);
if(bodyinstanceofString){
//需要對String特殊處理
try{
returnobjectMapper.writeValueAsString(result);
}catch(JsonProcessingExceptione){
e.printStackTrace();
}
}
returnresult;
}
}

但是,在實際業務中,上述代碼只是作為保底使用,因為狀態碼始終返回的是200,過于死板,還需要具體問題具體分析。






審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 處理器
    +關注

    關注

    68

    文章

    19407

    瀏覽量

    231182
  • 控制器
    +關注

    關注

    112

    文章

    16445

    瀏覽量

    179447
  • 狀態機
    +關注

    關注

    2

    文章

    492

    瀏覽量

    27647
  • SpringBoot
    +關注

    關注

    0

    文章

    174

    瀏覽量

    201

原文標題:告別繁瑣:SpringBoot 攔截器與統一功能處理

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    HarmonyOS實戰開發-如何在Navigation中完成路由攔截

    路由攔截器interceptor.ets,定義攔截容器、注冊方法和公共攔截邏輯,interceptor.ets /** * 定義攔截實現接口 * * @param routerI
    發表于 05-08 14:21

    [推薦]奧運全球眼 網絡視頻攝像機 電話報警系統 -電話報警 GSM防盜

    ........................................................ 10012.4.1 個生命周期回調事件上有多個回調攔截器方法...... 10112.4.2 異常
    發表于 07-07 15:39

    我想做個號碼攔截器。面對面5米內接收到對方的手機號碼。我也咨詢很多人,不是技

    我想做個號碼攔截器。面對面5米內接收到對方的手機號碼。我也咨詢很多人,不是技術問題就是,怕這東西觸犯法律。我只是正規用途,并不會觸犯法律底線!望“能人”解決我的問題!樣品只要符合以上條件,重金酬謝...謝謝!QQ896776242加我請注明電子*** 丁先生
    發表于 04-29 16:16

    Springboot是如何獲取自定義異常并進行返回的

    HandlerExceptionResolver這里呢?看下代碼:走完初始化,經過過濾器,攔截器終于到了我們的請求方法,我們的方法還報錯了,所以會走到異常中,我們DispatcherServlet會
    發表于 03-22 14:15

    網絡組件axios可以在OpenHarmony上使用了

    攔截器也是如此功能,只是在請求得到響應之后,對響應體的處理,通常是數據統一處理等,也常來判斷登錄失效等。axios的
    發表于 08-29 12:11

    動能攔截器六自由度仿真建模研究

    仿真建模技術是動能攔截器制導律研究中的重要技術,文中主要建立動能攔截器的軌道運動動力學以及姿態運動動力學模型,并建立完整的制導控制系統數學模型。文末,以某型
    發表于 08-07 08:50 ?14次下載

    springmvc 自定義攔截器實現未登錄用戶的攔截

    springmvc自定義攔截器實現未登錄用戶的攔截
    發表于 11-25 14:44 ?2528次閱讀
    springmvc 自定義<b class='flag-5'>攔截器</b>實現未登錄用戶的<b class='flag-5'>攔截</b>

    Spring Boot 系列(八)@ControllerAdvice 攔截異常并統一處理

    Spring Boot 系列(八)@ControllerAdvice 攔截異常并統一處理 在spring 3.2中,新增了@ControllerAdvice 注解,可以用于定義
    發表于 01-16 18:39 ?316次閱讀

    快速定位SpringBoot接口超時問題的神器

    渠道系統是個常見的spring-boot web工程,使用了集成的tomcat。分析了代碼之后,發現并沒有特殊的地方,沒有特殊的過濾器或者攔截器,所以初步排除是業務代碼問題
    的頭像 發表于 10-19 10:22 ?873次閱讀

    核心功能具體的執行過程-2

    這篇我們主要講解下 axios 中的 配置、攔截器和執行鏈等些核心的功能到底是怎么運行的。
    的頭像 發表于 03-01 09:59 ?615次閱讀
    核心<b class='flag-5'>功能</b>具體的執行過程-2

    什么是 SpringBoot

    本文從為什么要有 `SpringBoot`,以及 `SpringBoot` 到底方便在哪里開始入手,逐步分析了 `SpringBoot` 自動裝配的原理,最后手寫了個簡單的 `sta
    的頭像 發表于 04-07 11:28 ?1368次閱讀
    什么是 <b class='flag-5'>SpringBoot</b>?

    SpringBoot統一功能處理

    最初用戶登錄效驗: 在每個方法中獲取 Session 和 Session 中的用戶信息,如果存在用戶,那么就認為登錄成功了,否則就登錄失敗了
    的頭像 發表于 04-19 14:51 ?709次閱讀

    springboot過濾器和攔截器哪個先執行

    Spring Boot是個用于構建Java應用程序的開發框架,它提供了許多功能和工具來簡化開發和部署過程。其中兩個重要的功能是過濾器和攔截器。本文將詳細介紹Spring Boot過濾
    的頭像 發表于 12-03 15:00 ?2653次閱讀

    使用go語言實現個grpc攔截器

    在開發grpc服務時,我們經常會遇到些通用的需求,比如:日志、鏈路追蹤、鑒權等。這些需求可以通過grpc攔截器來實現。本文使用go語言來實現個 grpc元模式(Unary)
    的頭像 發表于 12-18 10:13 ?737次閱讀
    使用go語言實現<b class='flag-5'>一</b>個grpc<b class='flag-5'>攔截器</b>

    種輕量分表方案-MyBatis攔截器分表實踐

    文章,將分享如何使用MyBatis攔截器低成本的提升數據庫穩定性。 業界常見方案 針對冷數據多的大表,常用的策略有以2種: 刪除/歸檔舊數據。 分表。 歸檔/刪除舊數據 定期將冷數據移動到歸檔表或者冷存儲中,或定期對表進行刪除,以減少表的
    的頭像 發表于 01-23 17:38 ?125次閱讀
    虎在什么方位做生意好| 东莞百家乐官网的玩法技巧和规则| 娱乐城开户送彩金| 斗地主百家乐的玩法技巧和规则| 欧凯百家乐官网的玩法技巧和规则| 百家乐官网无敌直缆| 银河百家乐的玩法技巧和规则| 24山向| 赌博百家乐官网的乐趣| 萨嘎县| 678百家乐博彩娱乐平台| 24山64卦分金| 百家乐官网赌博玩法技巧| 文化| 澳门网上赌场| 卓达太阳城希望之洲| 百家乐电子作弊器| 澳门百家乐官网牌规| 棋牌游戏下载| 百家乐几点不用补牌| 百家乐最大的赌局| 百家乐视频金币| 可信百家乐官网的玩法技巧和规则| 百家乐官网平台网| 易赢百家乐官网软件| 娱乐场| 金博士百家乐的玩法技巧和规则 | 太阳城巧克力| 百家乐真人游戏棋牌| 澳门百家乐赌客| 百家乐算牌皇冠网| 喜达百家乐官网的玩法技巧和规则 | 百家乐刷钱| 百家乐机器二手| 百家乐官网赌博娱乐| 百家乐官网下注所有组合| 岢岚县| 天峨县| 阜康市| 安塞县| 揭东县|