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

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

3天內不再提示

SpringBoot統一功能處理

jf_ro2CN3Fa ? 來源:CSDN ? 2023-04-19 14:51 ? 次閱讀

本篇將要學習 Spring Boot 統一功能處理模塊,這也是 AOP 的實戰環節

用戶登錄權限的校驗實現接口 HandlerInterceptor + WebMvcConfigurer

異常處理使用注解 @RestControllerAdvice + @ExceptionHandler

數據格式返回使用注解 @ControllerAdvice 并且實現接口 @ResponseBodyAdvice

1. 統一用戶登錄權限效驗

用戶登錄權限的發展完善過程

最初用戶登錄效驗: 在每個方法中獲取 Session 和 Session 中的用戶信息,如果存在用戶,那么就認為登錄成功了,否則就登錄失敗了

第二版用戶登錄效驗: 提供統一的方法,在每個需要驗證的方法中調用統一的用戶登錄身份效驗方法來判斷

第三版用戶登錄效驗: 使用 Spring AOP 來統一進行用戶登錄效驗

第四版用戶登錄效驗: 使用 Spring 攔截器來實現用戶的統一登錄驗證

1.1 最初用戶登錄權限效驗

@RestController
@RequestMapping("/user")
publicclassUserController{

@RequestMapping("/a1")
publicBooleanlogin(HttpServletRequestrequest){
//有Session就獲取,沒有就不創建
HttpSessionsession=request.getSession(false);
if(session!=null&&session.getAttribute("userinfo")!=null){
//說明已經登錄,進行業務處理
returntrue;
}else{
//未登錄
returnfalse;
}
}

@RequestMapping("/a2")
publicBooleanlogin2(HttpServletRequestrequest){
//有Session就獲取,沒有就不創建
HttpSessionsession=request.getSession(false);
if(session!=null&&session.getAttribute("userinfo")!=null){
//說明已經登錄,進行業務處理
returntrue;
}else{
//未登錄
returnfalse;
}
}
}

這種方式寫的代碼,每個方法中都有相同的用戶登錄驗證權限,缺點是:

每個方法中都要單獨寫用戶登錄驗證的方法,即使封裝成公共方法,也一樣要傳參調用和在方法中進行判斷

添加控制器越多,調用用戶登錄驗證的方法也越多,這樣就增加了后期的修改成功和維護成功

這些用戶登錄驗證的方法和現在要實現的業務幾乎沒有任何關聯,但還是要在每個方法中都要寫一遍,所以提供一個公共的 AOP 方法來進行統一的用戶登錄權限驗證是非常好的解決辦法。

1.2 Spring AOP 統一用戶登錄驗證

統一用戶登錄驗證,首先想到的實現方法是使用 Spring AOP 前置通知或環繞通知來實現

@Aspect//當前類是一個切面
@Component
publicclassUserAspect{
//定義切點方法Controller包下、子孫包下所有類的所有方法
@Pointcut("execution(*com.example.springaop.controller..*.*(..))")
publicvoidpointcut(){}

//前置通知
@Before("pointcut()")
publicvoiddoBefore(){}

//環繞通知
@Around("pointcut()")
publicObjectdoAround(ProceedingJoinPointjoinPoint){
Objectobj=null;
System.out.println("Around方法開始執行");
try{
obj=joinPoint.proceed();
}catch(Throwablee){
e.printStackTrace();
}
System.out.println("Around方法結束執行");
returnobj;
}
}

但如果只在以上代碼 Spring AOP 的切面中實現用戶登錄權限效驗的功能,有這樣兩個問題:

沒有辦法得到 HttpSession 和 Request 對象

我們要對一部分方法進行攔截,而另一部分方法不攔截,比如注冊方法和登錄方法是不攔截的,也就是實際的攔截規則很復雜,使用簡單的 aspectJ 表達式無法滿足攔截的需求

1.3 Spring 攔截器

針對上面代碼 Spring AOP 的問題,Spring 中提供了具體的實現攔截器:HandlerInterceptor,攔截器的實現有兩步:

1.創建自定義攔截器,實現 Spring 中的 HandlerInterceptor 接口中的 preHandle方法

2.將自定義攔截器加入到框架的配置中,并且設置攔截規則

給當前的類添加 @Configuration 注解

實現 WebMvcConfigurer 接口

重寫 addInterceptors 方法

注意:一個項目中可以同時配置多個攔截器

(1)創建自定義攔截器

/**
*@Description:自定義用戶登錄的攔截器
*@Date2023/2/1313:06
*/
@Component
publicclassLoginInterceptimplementsHandlerInterceptor{
//返回true表示攔截判斷通過,可以訪問后面的接口
//返回false表示攔截未通過,直接返回結果給前端
@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,
Objecthandler)throwsException{
//1.得到HttpSession對象
HttpSessionsession=request.getSession(false);
if(session!=null&&session.getAttribute("userinfo")!=null){
//表示已經登錄
returntrue;
}
//執行到此代碼表示未登錄,未登錄就跳轉到登錄頁面
response.sendRedirect("/login.html");
returnfalse;
}
}

(2)將自定義攔截器添加到系統配置中,并設置攔截的規則

addPathPatterns:表示需要攔截的 URL,**表示攔截所有?法

excludePathPatterns:表示需要排除的 URL

說明:攔截規則可以攔截此項?中的使? URL,包括靜態?件(圖??件、JS 和 CSS 等?件)。

/**
*@Description:將自定義攔截器添加到系統配置中,并設置攔截的規則
*@Date2023/2/1313:13
*/
@Configuration
publicclassAppConfigimplementsWebMvcConfigurer{

@Resource
privateLoginInterceptloginIntercept;

@Override
publicvoidaddInterceptors(InterceptorRegistryregistry){
//registry.addInterceptor(newLoginIntercept());//可以直接new也可以屬性注入
registry.addInterceptor(loginIntercept).
addPathPatterns("/**").//攔截所有url
excludePathPatterns("/user/login").//不攔截登錄注冊接口
excludePathPatterns("/user/reg").
excludePathPatterns("/login.html").
excludePathPatterns("/reg.html").
excludePathPatterns("/**/*.js").
excludePathPatterns("/**/*.css").
excludePathPatterns("/**/*.png").
excludePathPatterns("/**/*.jpg");
}
}

1.4 練習:登錄攔截器

要求

登錄、注冊頁面不攔截,其他頁面都攔截

當登錄成功寫入 session 之后,攔截的頁面可正常訪問

在 1.3 中已經創建了自定義攔截器 和 將自定義攔截器添加到系統配置中,并設置攔截的規則

(1)下面創建登錄和首頁的 html

38026caa-de7c-11ed-bfe3-dac502259ad0.png

(2)創建 controller 包,在包中創建 UserController,寫登錄頁面和首頁的業務代碼

@RestController
@RequestMapping("/user")
publicclassUserController{

@RequestMapping("/login")
publicbooleanlogin(HttpServletRequestrequest,Stringusername,Stringpassword){
booleanresult=false;
if(StringUtils.hasLength(username)&&StringUtils.hasLength(password)){
if(username.equals("admin")&&password.equals("admin")){
HttpSessionsession=request.getSession();
session.setAttribute("userinfo","userinfo");
returntrue;
}
}
returnresult;
}

@RequestMapping("/index")
publicStringindex(){
return"HelloIndex";
}
}

(3)運行程序,訪問頁面,對比登錄前和登錄后的效果

3811586e-de7c-11ed-bfe3-dac502259ad0.png381bb926-de7c-11ed-bfe3-dac502259ad0.png

1.5 攔截器實現原理

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

382522e0-de7c-11ed-bfe3-dac502259ad0.png

實現原理源碼分析

所有的 Controller 執行都會通過一個調度器 DispatcherServlet 來實現

382c5cd6-de7c-11ed-bfe3-dac502259ad0.png

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

3833f4a0-de7c-11ed-bfe3-dac502259ad0.png

通過源碼分析,可以看出,Sping 中的攔截器也是通過動態代理和環繞通知的思想實現的

1.6 統一訪問前綴添加

所有請求地址添加 api 前綴,c 表示所有

@Configuration
publicclassAppConfigimplementsWebMvcConfigurer{
//所有的接口添加api前綴
@Override
publicvoidconfigurePathMatch(PathMatchConfigurerconfigurer){
configurer.addPathPrefix("api",c->true);
}
}
383ead96-de7c-11ed-bfe3-dac502259ad0.png

2. 統一異常處理

給當前的類上加 @ControllerAdvice 表示控制器通知類

給方法上添加 @ExceptionHandler(xxx.class),表示異常處理器,添加異常返回的業務代碼

@RestController
@RequestMapping("/user")
publicclassUserController{
@RequestMapping("/index")
publicStringindex(){
intnum=10/0;
return"HelloIndex";
}
}

在 config 包中,創建 MyExceptionAdvice 類

@RestControllerAdvice//當前是針對Controller的通知類(增強類)
publicclassMyExceptionAdvice{
@ExceptionHandler(ArithmeticException.class)
publicHashMaparithmeticExceptionAdvice(ArithmeticExceptione){
HashMapresult=newHashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg","算出異常:"+e.getMessage());
returnresult;
}
}

也可以這樣寫,效果是一樣的

@ControllerAdvice
publicclassMyExceptionAdvice{
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
publicHashMaparithmeticExceptionAdvice(ArithmeticExceptione){
HashMapresult=newHashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg","算數異常:"+e.getMessage());
returnresult;
}
}
38442406-de7c-11ed-bfe3-dac502259ad0.png

如果再有一個空指針異常,那么上面的代碼是不行的,還要寫一個針對空指針異常處理器

@ExceptionHandler(NullPointerException.class)
publicHashMapnullPointerExceptionAdvice(NullPointerExceptione){
HashMapresult=newHashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg","空指針異常異常:"+e.getMessage());
returnresult;
}
@RequestMapping("/index")
publicStringindex(HttpServletRequestrequest,Stringusername,Stringpassword){
Objectobj=null;
System.out.println(obj.hashCode());
return"HelloIndex";
}
38495d86-de7c-11ed-bfe3-dac502259ad0.png

但是需要考慮的一點是,如果每個異常都這樣寫,那么工作量是非常大的,并且還有自定義異常,所以上面這樣寫肯定是不好的,既然是異常直接寫 Exception 就好了,它是所有異常的父類,如果遇到不是前面寫的兩種異常,那么就會直接匹配到 Exception

當有多個異常通知時,匹配順序為當前類及其?類向上依次匹配

@ExceptionHandler(Exception.class)
publicHashMapexceptionAdvice(Exceptione){
HashMapresult=newHashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg","異常:"+e.getMessage());
returnresult;
}

可以看到優先匹配的還是前面寫的 空指針異常

3852b372-de7c-11ed-bfe3-dac502259ad0.png

3. 統一數據格式返回

3.1 統一數據格式返回的實現

1.給當前類添加 @ControllerAdvice

2.實現 ResponseBodyAdvice 重寫其方法

supports 方法,此方法表示內容是否需要重寫(通過此?法可以選擇性部分控制器和方法進行重寫),如果要重寫返回 true

beforeBodyWrite 方法,方法返回之前調用此方法

@ControllerAdvice
publicclassMyResponseAdviceimplementsResponseBodyAdvice{

//返回一個boolean值,true表示返回數據之前對數據進行重寫,也就是會進入beforeBodyWrite方法
//返回false表示對結果不進行任何處理,直接返回
@Override
publicbooleansupports(MethodParameterreturnType,ClassconverterType){
returntrue;
}

//方法返回之前調用此方法
@Override
publicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,ClassselectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){
HashMapresult=newHashMap<>();
result.put("state",1);
result.put("data",body);
result.put("msg","");
returnresult;
}
}
@RestController
@RequestMapping("/user")
publicclassUserController{

@RequestMapping("/login")
publicbooleanlogin(HttpServletRequestrequest,Stringusername,Stringpassword){
booleanresult=false;
if(StringUtils.hasLength(username)&&StringUtils.hasLength(password)){
if(username.equals("admin")&&password.equals("admin")){
HttpSessionsession=request.getSession();
session.setAttribute("userinfo","userinfo");
returntrue;
}
}
returnresult;
}

@RequestMapping("/reg")
publicintreg(){
return1;
}
}
385dbbd2-de7c-11ed-bfe3-dac502259ad0.png

3.2 @ControllerAdvice 源碼分析

通過對 @ControllerAdvice 源碼的分析我們可以知道上面統一異常和統一數據返回的執行流程

(1)先看 @ControllerAdvice 源碼

386660ac-de7c-11ed-bfe3-dac502259ad0.png

可以看到 @ControllerAdvice 派生于 @Component 組件而所有組件初始化都會調用 InitializingBean 接口

(2)下面查看 initializingBean 有哪些實現類

在查詢過程中發現,其中 Spring MVC 中的實現子類是 RequestMappingHandlerAdapter,它里面有一個方法 afterPropertiesSet()方法,表示所有的參數設置完成之后執行的方法

386cc3e8-de7c-11ed-bfe3-dac502259ad0.png

(3)而這個方法中有一個 initControllerAdviceCache 方法,查詢此方法

3877f98e-de7c-11ed-bfe3-dac502259ad0.png

發現這個方法在執行時會查找使用所有的 @ControllerAdvice 類,發送某個事件時,調用相應的 Advice 方法,比如返回數據前調用統一數據封裝,比如發生異常是調用異常的 Advice 方法實現的。






審核編輯:劉清

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

    關注

    112

    文章

    15334

    瀏覽量

    172339
  • URL
    URL
    +關注

    關注

    0

    文章

    135

    瀏覽量

    14900
  • CSS
    CSS
    +關注

    關注

    0

    文章

    105

    瀏覽量

    14212
  • API接口
    +關注

    關注

    1

    文章

    80

    瀏覽量

    10352
  • SpringBoot
    +關注

    關注

    0

    文章

    172

    瀏覽量

    113

原文標題:SpringBoot 統一功能處理:用戶登錄權限校驗-攔截器、異常處理、數據格式返回

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

收藏 人收藏

    評論

    相關推薦

    SpringBoot中的Druid介紹

    SpringBoot中Druid數據源配置
    發表于 05-07 09:21

    SpringBoot知識總結

    SpringBoot干貨學習總結
    發表于 08-01 10:40

    springboot spring data jpa使用總結

    【本人禿頂程序員】springboot專輯:spring data jpa的使用
    發表于 04-15 11:38

    文解析SpringBoot2整合SSM框架

    SpringBoot2整合SSM框架詳解
    發表于 06-09 16:43

    怎么學習SpringBoot

    SpringBoot學習之路(X5)- 整合JPA
    發表于 06-10 14:52

    springboot集成mqtt

    springboot集成mqtt,大綱.數據入庫1.數據入庫解決方案二.開發實時訂閱發布展示頁面1.及時通訊技術2.技術整合
    發表于 07-16 07:53

    怎樣去使用springboot

    怎樣去使用springboot呢?學習springboot需要懂得哪些?
    發表于 10-25 07:13

    SpringBoot應用啟動運行run方法

    )、refreshContext(context);SpringBoot刷新IOC容器【創建IOC容器對象,并初始化容器,創建容器中的每個組件】;如果是web應用創建**AnnotationConfigEmbeddedWebApplicationContext**,否則
    發表于 12-20 06:16

    java springboot電影購票選座微信小程序源碼功能簡介

    功能簡介后臺:會員管理,電影管理,訂單管理,系統管理小程序端:首頁電影 選座 影院 我的 訂單環境準備jdk1.8 mysql5.7 eclipse(idea) navicat后臺框架springboot mybatis vue.js bootstrap具體實踐...
    發表于 12-30 06:15

    怎樣去設計個基于springboot+freemark+jpa+MySQL的在線電影訂票系統

    本系統是由springboot+freemark+jpa+MySQL實現的在線電影訂票系統,主要的亮點功能有:支持短信發送接口、支付寶在線支付接口、座位鎖定及并發處理、排片時間沖突檢測等。本系統主要
    發表于 01-03 07:22

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

    源碼剖析Springboot是如何獲取自定義異常并進行返回的。來吧!第步:肯定是在Springboot啟動的過程中進行的異常處理初始化,于是就找到了handlerExceptionR
    發表于 03-22 14:15

    公司這套架構統一處理try...catch真香!

    軟件開發springboot項目過程中,不可避免的需要處理各種異常,spring mvc 架構中各層會出現大量的try {...} catch {...} finally {...} 代碼塊,不僅
    的頭像 發表于 02-27 10:47 ?356次閱讀

    什么是 SpringBoot?

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

    SpringBoot的核心注解1

    今天跟大家來探討下SpringBoot的核心注解@SpringBootApplication以及run方法,理解下springBoot為什么不需要XML,達到零配置
    的頭像 發表于 04-07 14:34 ?513次閱讀
    <b class='flag-5'>SpringBoot</b>的核心注解1

    SpringBoot的核心注解2

    今天跟大家來探討下SpringBoot的核心注解@SpringBootApplication以及run方法,理解下springBoot為什么不需要XML,達到零配置
    的頭像 發表于 04-07 14:34 ?1777次閱讀
    <b class='flag-5'>SpringBoot</b>的核心注解2
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>