SpringBoot 統(tǒng)一功能處理方案
在我們進(jìn)行項(xiàng)目編寫(xiě)時(shí),有時(shí)相同的一段代碼在不同的地方使用多次,對(duì)于這種情況,spring 幫我們實(shí)現(xiàn)了統(tǒng)一功能處理,下面介紹一些常用的統(tǒng)一功能處理。
一、攔截器
在有些項(xiàng)目中,是需要用戶登錄才能進(jìn)行后續(xù)訪問(wèn)的。但如果這時(shí)有人拿到了后端接口的 url,這時(shí)就能跳過(guò)用戶登錄進(jìn)而訪問(wèn)我們的網(wǎng)站,這就有可能對(duì)網(wǎng)站造成攻擊,對(duì)于這種情況,我們可以將用戶的部分信息存到 session 中,在每次訪問(wèn)接口前,都可以先獲取到 session,再將用戶信息取出,看看這個(gè)用戶是否登錄,若登錄,就允許繼續(xù)訪問(wèn),若沒(méi)有登陸,就強(qiáng)制登錄。
但是,在我們的項(xiàng)目中,接口的數(shù)目可不小,若是在每個(gè)接口中都加上這段邏輯,就會(huì)顯得我們的代碼過(guò)于冗余,于是 Spring 就為我們構(gòu)造了攔截器。
攔截器主要用來(lái)攔截用戶的請(qǐng)求,在指定方法前后執(zhí)行攔截器中預(yù)設(shè)的代碼,也就是說(shuō),攔截器可以在用戶的請(qǐng)求之前發(fā)揮作用,也可以在用戶的請(qǐng)求之后發(fā)揮作用。
對(duì)于上述判斷用戶登錄的業(yè)務(wù),我們就可以在攔截器中寫(xiě)明判斷用戶是否登錄的代碼,這樣在用戶發(fā)送請(qǐng)求時(shí),就會(huì)先執(zhí)行這段代碼,若用戶沒(méi)有登錄,就會(huì)阻止用戶進(jìn)行訪問(wèn),并跳轉(zhuǎn)到登陸頁(yè)面(這是前端干的事)。
下面介紹攔截器的具體用法。
在實(shí)現(xiàn)攔截器時(shí),需要實(shí)現(xiàn) HandlerInterceptor 類(lèi)中的 preHandle,postHandle,afterCompletion 方法,preHandle 是在目標(biāo)方法執(zhí)行前執(zhí)行的,postHandle 是在目標(biāo)方法執(zhí)行之后執(zhí)行的,afterCompletion 是在試圖渲染完成之后執(zhí)行的,但現(xiàn)在后端開(kāi)發(fā)基本上都不會(huì)涉及到試圖,那么這個(gè)方法就不重點(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 表示不攔截請(qǐng)求,返回 false 表示攔截請(qǐng)求。
定義好攔截器的功能后,接下來(lái)就需要注冊(cè)攔截器。注冊(cè)攔截器需要實(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 即表示需要注冊(cè)的是哪個(gè)攔截器,使用 addPathPatterns 即表示需要攔截的路徑,即攔截器對(duì)哪些請(qǐng)求生效,這里的 /test/** 表示對(duì) test 路徑下的所有方法均生效,還有其它的路徑表示,如下:
/* : 表示一級(jí)路徑,只能匹配 /test,不能匹配 /test/t1等;
/** : 表示任意級(jí)路徑,能匹配 /test,/test/t1等;
/test/* : 表示 /test 下的一級(jí)路徑,可以匹配 /test/t1,但不能匹配 /test/t1/t2等;
/test/** : 表示 /test 下的任意級(jí)路徑,可以匹配 /test/t1,/test/t1/t2,但不能匹配 /user/login 等。
當(dāng)我們配置完攔截器和注冊(cè)完攔截器后,就可以開(kāi)始運(yùn)行代碼,我們使用下面的代碼來(lái)進(jìn)行測(cè)試:
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/t1")
public void t1() {
log.info("攔截器測(cè)試...");
}
}代碼運(yùn)行結(jié)果如下:

在這里我們可以看到,當(dāng)訪問(wèn)被攔截的方法時(shí),首先執(zhí)行的是 preHandle,由于 postHandle 返回值為 true,就不會(huì)攔截用戶請(qǐng)求,于是就會(huì)先執(zhí)行 t1 方法,之后會(huì)順序執(zhí)行 postHandle 和 afterCompletion。
若 preHandle 返回值為 false,代碼的運(yùn)行結(jié)果如下:

從結(jié)果中可以看出,程序只執(zhí)行了 preHandle,由于 t1 被攔截,于是 t1、postHandle、afterCompletion 就不會(huì)執(zhí)行。
在上述的攔截路徑中,我們將 /test 下的所有路徑都給攔截了,但是有的請(qǐng)求我們不需要攔截,那么我們就可以使用 excludPathPatterns 去去除掉不需要攔截的路徑,改動(dòng)后的代碼如下:
@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攔截器測(cè)試...");
}
@RequestMapping("/t2")
public void t2() {
log.info("不攔截t2測(cè)試");
}
}TestController 中新增了 t2 方法,只攔截 t1,不攔截 t2,代碼運(yùn)行結(jié)果如下:

從圖中可以看出,t1被攔截,但 t2 沒(méi)有被攔截。
當(dāng)我們知道如何使用攔截器之后,就可以在項(xiàng)目中進(jìn)行運(yùn)用。
當(dāng)用戶在進(jìn)行訪問(wèn)時(shí),就可以對(duì)其訪問(wèn)進(jìn)行攔截,在攔截器中判斷用戶是否登錄,若登錄就放行,若沒(méi)有登錄就跳轉(zhuǎn)到登錄頁(yè)面進(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í)行, 攔截器攔截到后,就會(huì)將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;
}
/**
* 對(duì)檢查用戶信息進(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);
//用戶信息不對(duì)
if (userInfo == null || userInfo.getId() <= 0) {
return false;
}
return true;
}
}其中 check 的用處是檢查用戶是否登錄。
二、統(tǒng)一數(shù)據(jù)返回格式
在進(jìn)行項(xiàng)目開(kāi)發(fā)時(shí),我需要向前端返回一個(gè)統(tǒng)一的數(shù)據(jù),這個(gè)數(shù)據(jù)是經(jīng)過(guò)封裝的,但是,在我們的接口中,有的返回的是 String,有的返回的是 boolean等,這就需要我們針對(duì)每一個(gè)接口都編寫(xiě)封裝結(jié)果的代碼,這樣的代碼就有點(diǎn)冗余了。于是,Spring 就封裝了能夠統(tǒng)一返回?cái)?shù)據(jù)格式的方法。
現(xiàn)在有一個(gè)結(jié)果類(lèi) Result,對(duì)于接口中的每一種返回值,都需要將其封裝為 Result 對(duì)象,實(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類(lèi)型轉(zhuǎn)化為String類(lèi)型
}
//body已經(jīng)是result類(lèi)型,就不需要封裝
if (body instanceof Result) {
return body;
}
return Result.success(body);
}
}該類(lèi)需實(shí)現(xiàn) ResponseBodyAdvice 接口,實(shí)現(xiàn) support 和 beforeBodyWrite 方法,而且需要加上 @ControllerAdvice 注解。
support 的返回值為 boolean,返回 true 表示對(duì)各類(lèi)型的返回值都進(jìn)行處理,返回 false 表示不處理。
beforeBodyWrite 即具體的實(shí)現(xiàn)類(lèi)。在這里需要強(qiáng)調(diào),若接口中的返回值為 String,那么就需要對(duì)其進(jìn)行單獨(dú)處理,不然就會(huì)報(bào)錯(cuò)。
對(duì)于本來(lái)就是 Result 類(lèi)型的對(duì)象,就不要進(jìn)行處理,直接返回即可。
三、統(tǒng)一異常處理
在我們編寫(xiě)代碼的時(shí)候,有時(shí)候可能判斷的不準(zhǔn)確,不知道哪里會(huì)出現(xiàn)異常,那么如果我們不進(jìn)行處理,就會(huì)影響程序的運(yùn)行。對(duì)于這種情況,Spring 為我們封裝了一個(gè)能統(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)部錯(cuò)誤,請(qǐng)聯(lián)系管理員");
}
@ExceptionHandler
public Result handler(NullPointerException e) {
log.error("發(fā)生異常, e: ", e);
return Result.fail(ResultStatusEnums.ERROR, "空指針異常,請(qǐng)聯(lián)系管理員");
}
@ExceptionHandler
public Result handler(IndexOutOfBoundsException e) {
log.error("發(fā)生異常, e: ", e);
return Result.fail(ResultStatusEnums.ERROR, "數(shù)組越界異常,請(qǐng)聯(lián)系管理員");
}
}ExceptionAdvice 需要使用 @ControllerAdvice 和 @ResponseBody 注解。
在各個(gè)方法上需要使用 @ExceptionHandler 注解。
在這個(gè)類(lèi)中,一共處理了三個(gè)異常,也可以繼續(xù)增加異常的種類(lèi)。若代碼中拋出了異常并且沒(méi)有手動(dòng)捕獲,就會(huì)拋給 ExceptionAdvice 類(lèi),在這個(gè)類(lèi)中尋找與之距離最近的異常,若沒(méi)有,就會(huì)直接使用 Exception 代替。
上述的代碼還有另一種寫(xiě)法,代碼如下:
@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)部錯(cuò)誤,請(qǐng)聯(lián)系管理員");
}
@ExceptionHandler(NullPointerException.class)
public Result handler2(Exception e) {
log.error("發(fā)生異常, e: ", e);
return Result.fail(ResultStatusEnums.ERROR, "空指針異常,請(qǐng)聯(lián)系管理員");
}
@ExceptionHandler(IndexOutOfBoundsException.class)
public Result handler3(Exception e) {
log.error("發(fā)生異常, e: ", e);
return Result.fail(ResultStatusEnums.ERROR, "數(shù)組越界異常,請(qǐng)聯(lián)系管理員");
}
}在每個(gè) @ExceptionHandler 注解中標(biāo)明捕獲的是什么類(lèi)型的異常,這樣在每個(gè)方法的參數(shù)中就能直接使用 Exception 代替。
到此這篇關(guān)于SpringBoot 統(tǒng)一功能處理方案的文章就介紹到這了,更多相關(guān)SpringBoot 統(tǒng)一功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)字符串轉(zhuǎn)換為十進(jìn)制ASCII碼的實(shí)戰(zhàn)指南
將Java字符串轉(zhuǎn)換為十進(jìn)制ASCII碼是文本處理、協(xié)議編碼、數(shù)據(jù)序列化和底層通信中的常見(jiàn)需求,下面我們就來(lái)看看具體的實(shí)現(xiàn)方法,希望對(duì)大家有所幫助2026-05-05
詳解Http請(qǐng)求中Content-Type講解以及在Spring MVC中的應(yīng)用
這篇文章主要介紹了Http請(qǐng)求中Content-Type講解以及在Spring MVC中的應(yīng)用的相關(guān)資料,需要的朋友可以參考下2017-02-02
springcloud 中 zuul 修改請(qǐng)求參數(shù)信息的方法
這篇文章主要介紹了springcloud 中 zuul 修改請(qǐng)求參數(shù)信息的方法,需要的朋友可以參考下2018-02-02
Java中Collection和Collections的區(qū)別
Collection是一個(gè)集合接口,集合類(lèi)的頂級(jí)接口,Collections是一個(gè)包裝類(lèi),本文主要介紹了Java中Collection和Collections的區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下2024-04-04
SpringMVC 方法四種類(lèi)型返回值總結(jié)(你用過(guò)幾種)
這篇文章主要介紹了SpringMVC 方法四種類(lèi)型返回值總結(jié)(你用過(guò)幾種),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05

