SpringBoot 統(tǒng)一功能處理方案
在我們進(jìn)行項(xiàng)目編寫時,有時相同的一段代碼在不同的地方使用多次,對于這種情況,spring 幫我們實(shí)現(xiàn)了統(tǒng)一功能處理,下面介紹一些常用的統(tǒng)一功能處理。
一、攔截器
在有些項(xiàng)目中,是需要用戶登錄才能進(jìn)行后續(xù)訪問的。但如果這時有人拿到了后端接口的 url,這時就能跳過用戶登錄進(jìn)而訪問我們的網(wǎng)站,這就有可能對網(wǎng)站造成攻擊,對于這種情況,我們可以將用戶的部分信息存到 session 中,在每次訪問接口前,都可以先獲取到 session,再將用戶信息取出,看看這個用戶是否登錄,若登錄,就允許繼續(xù)訪問,若沒有登陸,就強(qiáng)制登錄。
但是,在我們的項(xiàng)目中,接口的數(shù)目可不小,若是在每個接口中都加上這段邏輯,就會顯得我們的代碼過于冗余,于是 Spring 就為我們構(gòu)造了攔截器。
攔截器主要用來攔截用戶的請求,在指定方法前后執(zhí)行攔截器中預(yù)設(shè)的代碼,也就是說,攔截器可以在用戶的請求之前發(fā)揮作用,也可以在用戶的請求之后發(fā)揮作用。
對于上述判斷用戶登錄的業(yè)務(wù),我們就可以在攔截器中寫明判斷用戶是否登錄的代碼,這樣在用戶發(fā)送請求時,就會先執(zhí)行這段代碼,若用戶沒有登錄,就會阻止用戶進(jìn)行訪問,并跳轉(zhuǎn)到登陸頁面(這是前端干的事)。
下面介紹攔截器的具體用法。
在實(shí)現(xiàn)攔截器時,需要實(shí)現(xiàn) HandlerInterceptor 類中的 preHandle,postHandle,afterCompletion 方法,preHandle 是在目標(biāo)方法執(zhí)行前執(zhí)行的,postHandle 是在目標(biāo)方法執(zhí)行之后執(zhí)行的,afterCompletion 是在試圖渲染完成之后執(zhí)行的,但現(xiàn)在后端開發(fā)基本上都不會涉及到試圖,那么這個方法就不重點(diǎn)介紹了。
代碼如下:
@Slf4j public class LoginInterceptor implements HandlerInterceptor { //在目標(biāo)方法執(zhí)行前執(zhí)行 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("在目標(biāo)方法執(zhí)行前執(zhí)行"); return true; } //在目標(biāo)方法之后執(zhí)行 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("在目方法執(zhí)行后執(zhí)行"); } //試圖渲染完成之后執(zhí)行 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("視圖渲染完成后執(zhí)行"); } }
preHandle 有返回值,返回 true 表示不攔截請求,返回 false 表示攔截請求。
定義好攔截器的功能后,接下來就需要注冊攔截器。注冊攔截器需要實(shí)現(xiàn) WebMvcConfigurer 中的 addInterceptors,代碼如下:
@Slf4j @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/test/**"); } }
使用 addInterceptor 即表示需要注冊的是哪個攔截器,使用 addPathPatterns 即表示需要攔截的路徑,即攔截器對哪些請求生效,這里的 /test/** 表示對 test 路徑下的所有方法均生效,還有其它的路徑表示,如下:
/* : 表示一級路徑,只能匹配 /test,不能匹配 /test/t1等;
/** : 表示任意級路徑,能匹配 /test,/test/t1等;
/test/* : 表示 /test 下的一級路徑,可以匹配 /test/t1,但不能匹配 /test/t1/t2等;
/test/** : 表示 /test 下的任意級路徑,可以匹配 /test/t1,/test/t1/t2,但不能匹配 /user/login 等。
當(dāng)我們配置完攔截器和注冊完攔截器后,就可以開始運(yùn)行代碼,我們使用下面的代碼來進(jìn)行測試:
@Slf4j @RestController @RequestMapping("/test") public class TestController { @RequestMapping("/t1") public void t1() { log.info("攔截器測試..."); } }
代碼運(yùn)行結(jié)果如下:
在這里我們可以看到,當(dāng)訪問被攔截的方法時,首先執(zhí)行的是 preHandle,由于 postHandle 返回值為 true,就不會攔截用戶請求,于是就會先執(zhí)行 t1 方法,之后會順序執(zhí)行 postHandle 和 afterCompletion。
若 preHandle 返回值為 false,代碼的運(yùn)行結(jié)果如下:
從結(jié)果中可以看出,程序只執(zhí)行了 preHandle,由于 t1 被攔截,于是 t1、postHandle、afterCompletion 就不會執(zhí)行。
在上述的攔截路徑中,我們將 /test 下的所有路徑都給攔截了,但是有的請求我們不需要攔截,那么我們就可以使用 excludPathPatterns 去去除掉不需要攔截的路徑,改動后的代碼如下:
@Slf4j @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/test/**") .excludePathPatterns("/test/t2"); } } @Slf4j @RestController @RequestMapping("/test") public class TestController { @RequestMapping("/t1") public void t1() { log.info("t1攔截器測試..."); } @RequestMapping("/t2") public void t2() { log.info("不攔截t2測試"); } }
TestController 中新增了 t2 方法,只攔截 t1,不攔截 t2,代碼運(yùn)行結(jié)果如下:
從圖中可以看出,t1被攔截,但 t2 沒有被攔截。
當(dāng)我們知道如何使用攔截器之后,就可以在項(xiàng)目中進(jìn)行運(yùn)用。
當(dāng)用戶在進(jìn)行訪問時,就可以對其訪問進(jìn)行攔截,在攔截器中判斷用戶是否登錄,若登錄就放行,若沒有登錄就跳轉(zhuǎn)到登錄頁面進(jìn)行登錄,代碼如下:
package com.gjm.demo.interceptor; import com.gjm.demo.constant.Constants; import com.gjm.demo.enums.ResultStatusEnums; import com.gjm.demo.model.Result; import com.gjm.demo.model.UserInfo; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * 登錄攔截器 */ @Slf4j @Component public class LoginInterceptor implements HandlerInterceptor { /** * 目標(biāo)方法前執(zhí)行, 攔截器攔截到后,就會將response返回給前端,前端的error中就能就受到response * @param request * @param response * @param handler * @return true: 繼續(xù)執(zhí)行, false: 中斷后續(xù)操作 * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("登錄之前執(zhí)行"); //用戶未登錄的處理 if (!checkUserInfo(request.getSession())) { response.setContentType("text/html;character=utf-8"); response.setStatus(401); String errorMessage = "用戶未登錄"; response.getOutputStream().write(errorMessage.getBytes("UTF-8")); return false; } return true; } /** * 對檢查用戶信息進(jìn)行封裝 * @param session * @return */ public boolean checkUserInfo(HttpSession session) { //用戶未登錄 if (session == null || session.getAttribute(Constants.SESSION_USER_KEY) == null) { return false; } //獲取用戶信息 UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY); //用戶信息不對 if (userInfo == null || userInfo.getId() <= 0) { return false; } return true; } }
其中 check 的用處是檢查用戶是否登錄。
二、統(tǒng)一數(shù)據(jù)返回格式
在進(jìn)行項(xiàng)目開發(fā)時,我需要向前端返回一個統(tǒng)一的數(shù)據(jù),這個數(shù)據(jù)是經(jīng)過封裝的,但是,在我們的接口中,有的返回的是 String,有的返回的是 boolean等,這就需要我們針對每一個接口都編寫封裝結(jié)果的代碼,這樣的代碼就有點(diǎn)冗余了。于是,Spring 就封裝了能夠統(tǒng)一返回數(shù)據(jù)格式的方法。
現(xiàn)在有一個結(jié)果類 Result,對于接口中的每一種返回值,都需要將其封裝為 Result 對象,實(shí)現(xiàn)的代碼如下:
@ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice { @Autowired private ObjectMapper objectMapper; @Override public boolean supports(MethodParameter returnType, Class converterType) { //false為不處理,true為處理 return true; } @SneakyThrows//異常處理 @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof String) { return objectMapper.writeValueAsString(Result.success(body));//將Result類型轉(zhuǎn)化為String類型 } //body已經(jīng)是result類型,就不需要封裝 if (body instanceof Result) { return body; } return Result.success(body); } }
該類需實(shí)現(xiàn) ResponseBodyAdvice 接口,實(shí)現(xiàn) support 和 beforeBodyWrite 方法,而且需要加上 @ControllerAdvice 注解。
support 的返回值為 boolean,返回 true 表示對各類型的返回值都進(jìn)行處理,返回 false 表示不處理。
beforeBodyWrite 即具體的實(shí)現(xiàn)類。在這里需要強(qiáng)調(diào),若接口中的返回值為 String,那么就需要對其進(jìn)行單獨(dú)處理,不然就會報錯。
對于本來就是 Result 類型的對象,就不要進(jìn)行處理,直接返回即可。
三、統(tǒng)一異常處理
在我們編寫代碼的時候,有時候可能判斷的不準(zhǔn)確,不知道哪里會出現(xiàn)異常,那么如果我們不進(jìn)行處理,就會影響程序的運(yùn)行。對于這種情況,Spring 為我們封裝了一個能統(tǒng)一處理異常的方法,代碼如下:
@Slf4j @ControllerAdvice @ResponseBody public class ExceptionAdvice { @ExceptionHandler public Result handler(Exception e) { log.error("發(fā)生異常, e: ", e); return Result.fail(ResultStatusEnums.ERROR, "內(nèi)部錯誤,請聯(lián)系管理員"); } @ExceptionHandler public Result handler(NullPointerException e) { log.error("發(fā)生異常, e: ", e); return Result.fail(ResultStatusEnums.ERROR, "空指針異常,請聯(lián)系管理員"); } @ExceptionHandler public Result handler(IndexOutOfBoundsException e) { log.error("發(fā)生異常, e: ", e); return Result.fail(ResultStatusEnums.ERROR, "數(shù)組越界異常,請聯(lián)系管理員"); } }
ExceptionAdvice 需要使用 @ControllerAdvice 和 @ResponseBody 注解。
在各個方法上需要使用 @ExceptionHandler 注解。
在這個類中,一共處理了三個異常,也可以繼續(xù)增加異常的種類。若代碼中拋出了異常并且沒有手動捕獲,就會拋給 ExceptionAdvice 類,在這個類中尋找與之距離最近的異常,若沒有,就會直接使用 Exception 代替。
上述的代碼還有另一種寫法,代碼如下:
@Slf4j @ControllerAdvice @ResponseBody public class ExceptionAdvice { @ExceptionHandler(Exception.class) public Result handler1(Exception e) { log.error("發(fā)生異常, e: ", e); return Result.fail(ResultStatusEnums.ERROR, "內(nèi)部錯誤,請聯(lián)系管理員"); } @ExceptionHandler(NullPointerException.class) public Result handler2(Exception e) { log.error("發(fā)生異常, e: ", e); return Result.fail(ResultStatusEnums.ERROR, "空指針異常,請聯(lián)系管理員"); } @ExceptionHandler(IndexOutOfBoundsException.class) public Result handler3(Exception e) { log.error("發(fā)生異常, e: ", e); return Result.fail(ResultStatusEnums.ERROR, "數(shù)組越界異常,請聯(lián)系管理員"); } }
在每個 @ExceptionHandler 注解中標(biāo)明捕獲的是什么類型的異常,這樣在每個方法的參數(shù)中就能直接使用 Exception 代替。
到此這篇關(guān)于SpringBoot 統(tǒng)一功能處理方案的文章就介紹到這了,更多相關(guān)SpringBoot 統(tǒng)一功能內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java?zxing實(shí)現(xiàn)生成并解析二維碼與條形碼
這篇文章主要為大家詳細(xì)介紹了Java如何通過zxing實(shí)現(xiàn)生成并解析二維碼與條形碼,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-11-11java基于Apache FTP實(shí)現(xiàn)文件上傳、下載、修改文件名、刪除
本篇文章主要介紹了Apache FTP實(shí)現(xiàn)文件上傳、下載、修改文件名、刪除,實(shí)現(xiàn)了FTP文件上傳(斷點(diǎn)續(xù)傳)、FTP文件下載、FTP文件重命名、FTP文件刪除等功能,有需要的可以了解一下。2016-11-11SpringBoot注解@EnableScheduling定時任務(wù)詳細(xì)解析
這篇文章主要介紹了SpringBoot注解@EnableScheduling定時任務(wù)詳細(xì)解析,@EnableScheduling 開啟對定時任務(wù)的支持,啟動類里面使用@EnableScheduling 注解開啟功能,自動掃描,需要的朋友可以參考下2024-01-01Spring事務(wù)框架之TransactionStatus源碼解析
這篇文章主要為大家介紹了Spring事務(wù)框架之TransactionStatus源碼示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08springcloud檢索中間件?ElasticSearch?分布式場景的使用
單機(jī)的elasticsearch做數(shù)據(jù)存儲,必然面臨兩個問題:海量數(shù)據(jù)存儲問題、單點(diǎn)故障問題,本文重點(diǎn)給大家介紹springcloud檢索中間件?ElasticSearch?分布式場景的運(yùn)用,感興趣的朋友跟隨小編一起看看吧2023-10-10Spring boot 數(shù)據(jù)源未配置異常的解決
這篇文章主要介紹了Spring boot 數(shù)據(jù)源未配置異常的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08