Spring AOP統(tǒng)一功能處理示例代碼
1. 什么是Spring AOP?
在介紹Spring AOP之前,首先要了解一下什么是AOP?
AOP (Aspect Oriented Programming)︰面向切面編程,它是一種思想,它是對某一類事情的集中處理。比如用戶登錄權(quán)限的效驗(yàn),沒學(xué)AOP之前,我們所有需要判斷用戶登錄的頁面(中的方法),都要各自實(shí)現(xiàn)或調(diào)用用戶驗(yàn)證的方法,然而有了AOP之后,我們只需要在某一處配置一下,所有需要判斷用戶登錄頁面(中的方法)就全部可以實(shí)現(xiàn)用戶登錄驗(yàn)證了,不再需要每個(gè)方法中都寫相同的用戶登錄驗(yàn)證了。
而AOP是一種思想,而Spring AOP是一個(gè)框架,提供了一種對AOP思想的實(shí)現(xiàn),它們的關(guān)系和loC與DI類似。
2. 為什要用 AOP?
我們之前的處理方式是每個(gè)Controller都要寫一遍用戶登錄驗(yàn)證,然而當(dāng)你的功能越來越多,那么你要寫的登錄驗(yàn)證也越來越多,而這些方法又是相同的,這么多的方法就會代碼修改和維護(hù)的成本。那有沒有簡單的處理方案呢?答案是有的,對于這種功能統(tǒng)一,且使用的地方較多的功能,就可以考慮AOP來統(tǒng)一處理了。
除了統(tǒng)一的用戶登錄判斷之外,AOP還可以實(shí)現(xiàn):
- 統(tǒng)一日志記錄
- 統(tǒng)一方法執(zhí)行時(shí)間統(tǒng)計(jì)
- 統(tǒng)一的返回格式設(shè)置
- 統(tǒng)一的異常處理
- 事務(wù)的開啟和提交等
也就是說使用AOP可以擴(kuò)充多個(gè)對象的某個(gè)能力,所以AOP可以說是OOP (Object OrientedProgramming,面向?qū)ο缶幊?的補(bǔ)充和完善。
3. Spring AOP 應(yīng)該怎么學(xué)習(xí)呢?
Spring AOP學(xué)習(xí)主要分為以下3個(gè)部分:
1.學(xué)習(xí)AOP是如何組成的?也就是學(xué)習(xí)AOP組成的相關(guān)概念。
2.學(xué)習(xí)Spring AOP使用。
3.學(xué)習(xí)Spring AOP實(shí)現(xiàn)原理。下面我們分別來看。
3.1AOP組成
3.1.1 切面(Aspect)
切面(Aspect)由切點(diǎn)(Pointcut)和通知(Advice)組成,它既包含了橫切邏輯的定義,也包括了連接點(diǎn)的定義。
切面是包含了:通知、切點(diǎn)和切面的類,相當(dāng)于AOP實(shí)現(xiàn)的某個(gè)功能的集合。
3.1.2 連接點(diǎn)(Join Point)
應(yīng)用執(zhí)行過程中能夠插入切面的一個(gè)點(diǎn),這個(gè)點(diǎn)可以是方法調(diào)用時(shí),拋出異常時(shí),甚至修改字段時(shí)。切面代碼可以利用這些點(diǎn)插入到應(yīng)用的正常流程之中,并添加新的行為。
連接點(diǎn)相當(dāng)于需要被增強(qiáng)的某個(gè)AOP功能的所有方法。
3.1.3 切點(diǎn)(Pointcut)
Pointcut是匹配Join Point的謂詞。
Pointcut 的作用就是提供一組規(guī)則(使用AspectJ pointcut expression language來描述)來匹配Join Point,給滿足規(guī)則的Join Point添加Advice。
切點(diǎn)相當(dāng)于保存了眾多連接點(diǎn)的一個(gè)集合(如果把切點(diǎn)看成一個(gè)表,而連接點(diǎn)就是表中一條一條的數(shù)據(jù))。
3.1.4 通知(Advice)
切面也是有目標(biāo)的——它必須完成的工作。在AOP術(shù)語中,切面的工作被稱之為通知。
通知︰定義了切面是什么,何時(shí)使用,其描述了切面要完成的工作,還解決何時(shí)執(zhí)行這個(gè)工作的問題。
Spring切面類中,可以在方法上使用以下注解,會設(shè)置方法為通知方法,在滿足條件后會通知本方法進(jìn)行調(diào)用:
- 前置通知使用@Before:通知方法會在目標(biāo)方法調(diào)用之前執(zhí)行。
- 后置通知使用@After:通知方法會在目標(biāo)方法返回或者拋出異常后調(diào)用。
- 返回之后通知使用@AfterReturning:通知方法會在目標(biāo)方法返回后調(diào)用。
- 拋異常后通知使用@AfterThrowing:通知方法會在目標(biāo)方法拋出異常后調(diào)用。
- 環(huán)繞通知使用@Around:通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為。
切點(diǎn)相當(dāng)于要增強(qiáng)的方法。
AOP整個(gè)組成部分的概念如下圖所示,以多個(gè)頁面都要訪問用戶登錄權(quán)限為例:

3.2 Spring AOP實(shí)現(xiàn)
使用Spring AOP來實(shí)現(xiàn)一下AOP的功能,完成的目標(biāo)是攔截所有UserController里面的方法,每次調(diào)用UserController中任意一個(gè)方法時(shí),都執(zhí)行相應(yīng)的通知事件。
Spring AOP 的實(shí)現(xiàn)步驟是:
- 添加 AOP 框架支持。
- 定義切面和切點(diǎn)。
- 定義通知。
3.2.1 添加 AOP 框架支持
3.2.2 定義切面和切點(diǎn)。
3.2.3 定義相關(guān)通知
通知定義的是被攔截的方法具體要執(zhí)行的業(yè)務(wù),比如用戶登錄權(quán)限驗(yàn)證方法就是具體要執(zhí)行的業(yè)務(wù)Spring AOP中,可以在方法上使用以下注解,會設(shè)置方法為通知方法,在滿足條件后會通知本方法進(jìn)行調(diào)用:
- 前置通知使用@Before:通知方法會在目標(biāo)方法調(diào)用之前執(zhí)行。
- 后置通知使用@After:通知方法會在目標(biāo)方法返回或者拋出異常后調(diào)用。
- 返回之后通知使用@AfterReturning:通知方法會在目標(biāo)方法返回后調(diào)用。
- 拋異常后通知使用@AfterThrowing:通知方法會在目標(biāo)方法拋出異常后調(diào)用。
- 環(huán)繞通知使用@Around:通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為。
具體實(shí)現(xiàn)如下:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class UserAspect {
// 定義切點(diǎn)?法
@Pointcut("execution(* com.example.demo.controller.UserController.*
(..))")
public void pointcut(){ }
// 前置通知
@Before("pointcut()")
public void doBefore(){
System.out.println("執(zhí)? Before ?法");
}
// 后置通知
@After("pointcut()")
public void doAfter(){
System.out.println("執(zhí)? After ?法");
}
// return 之前通知
@AfterReturning("pointcut()")
public void doAfterReturning(){
System.out.println("執(zhí)? AfterReturning ?法");
}
// 拋出異常之前通知
@AfterThrowing("pointcut()")
public void doAfterThrowing(){
System.out.println("執(zhí)? doAfterThrowing ?法");
}
// 添加環(huán)繞通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint){
Object obj = null;
System.out.println("Around ?法開始執(zhí)?");
try {
// 執(zhí)?攔截?法
obj = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("Around ?法結(jié)束執(zhí)?");
return obj;
}
}
3.3 Spring AOP 實(shí)現(xiàn)原理
3.3.1 動態(tài)代理
Spring AOP是構(gòu)建在動態(tài)代理基礎(chǔ)上,因此Spring對AOP的支持局限于方法級別的攔截。
Spring AOP支持JDK Proxy和CGLIB方式實(shí)現(xiàn)動態(tài)代理。默認(rèn)情況下,實(shí)現(xiàn)了接口的類,使用AOP會基于JDK生成代理類,沒有實(shí)現(xiàn)接口的類,會基于CGLIB生成代理類。

這兩種方式的代理目標(biāo)都是被代理類中的方法,在運(yùn)行期,動態(tài)的織入字節(jié)碼生成代理類。
3.3.2 JDK和CGLIB實(shí)現(xiàn)的區(qū)別
- JDK實(shí)現(xiàn),要求被代理類必須實(shí)現(xiàn)接口,之后是通過
InvocationHandler及Proxy,在運(yùn)行時(shí)動態(tài)的在內(nèi)存中生成了代理類對象,該代理對象是通過實(shí)現(xiàn)同樣的接口實(shí)現(xiàn)(類似靜態(tài)代理接口實(shí)現(xiàn)的方式),只是該代理類是在運(yùn)行期時(shí),動態(tài)的織入統(tǒng)一的業(yè)務(wù)邏輯字節(jié)碼來完成。 - CGLIB實(shí)現(xiàn),被代理類可以不實(shí)現(xiàn)接口,是通過繼承被代理類,在運(yùn)行時(shí)動態(tài)的生成代理類
3.3.3 織入(Weaving):代理的生成時(shí)機(jī)
織入是把切面應(yīng)用到目標(biāo)對象并創(chuàng)建新的代理對象的過程,切面在指定的連接點(diǎn)被織入到目標(biāo)對象中。
在目標(biāo)對象的生命周期里有多個(gè)點(diǎn)可以進(jìn)行織入∶
- 編譯期:切面在目標(biāo)類編譯時(shí)被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
- 類加載器:切面在目標(biāo)類加載到JVM時(shí)被織入。這種方式需要特殊的類加載器 (ClassLoader),它可以在目標(biāo)類被引入應(yīng)用之前增強(qiáng)該目標(biāo)類的字節(jié)碼。AspectJ5的加載時(shí)織入(load-time weaving. LTW)就支持以這種方式織入切面。
- 運(yùn)行期:切面在應(yīng)用運(yùn)行的某一時(shí)刻被織入。一般情況下,在織入切面時(shí),AOP容器會為目標(biāo)對象動態(tài)創(chuàng)建一個(gè)代理對象。SpringAOP就是以這種方式織入切面的。
此種實(shí)現(xiàn)在設(shè)計(jì)模式上稱為動態(tài)代理模式,在實(shí)現(xiàn)的技術(shù)手段上,都是在class代碼運(yùn)行期,動態(tài)的織入字節(jié)碼生成代理類。
3.3.4 總結(jié)
AOP是對某方面能力的統(tǒng)一實(shí)現(xiàn),它是一種實(shí)現(xiàn)思想,Spring AOP是對AOP的具體實(shí)現(xiàn),SpringAOP可通過AspectJ(注解) 的方式來實(shí)現(xiàn)AOP的功能,Spring AOP 的實(shí)現(xiàn)步驟是:
- 添加AOP框架支持。
- 定義切面和切點(diǎn)。
- 定義通知。
Spring AOP是通過動態(tài)代理的方式,在運(yùn)行期將AOP代碼織入到程序中的,它的實(shí)現(xiàn)方式有兩種JDK Proxy和CGLIB。
4. SpringBoot 統(tǒng)一功能處理
接下來是Spring Boot統(tǒng)一功能處理模塊了,也是AOP的實(shí)戰(zhàn)環(huán)節(jié),要實(shí)現(xiàn)的課程目標(biāo)有以下3個(gè):
- 統(tǒng)一用戶登錄權(quán)限驗(yàn)證;
- 統(tǒng)—數(shù)據(jù)格式返回;
- 統(tǒng)一異常處理。
接下我們一個(gè)一個(gè)來看。
4.1 用戶登錄權(quán)限效驗(yàn)
用戶登錄權(quán)限的發(fā)展從之前每個(gè)方法中自己驗(yàn)證用戶登錄權(quán)限,到現(xiàn)在統(tǒng)一的用戶登錄驗(yàn)證處理,它是—個(gè)逐漸完善和逐漸優(yōu)化的過程。
4.1.1 Spring攔截器
Spring 中提供了具體的實(shí)現(xiàn)攔截器:HandlerInterceptor,
統(tǒng)一用戶登錄權(quán)限的效驗(yàn)使用WebMvcConfigurer + HandlerInterceptor來實(shí)現(xiàn)。
攔截器的實(shí)現(xiàn)分為以下兩個(gè)步驟∶
- 創(chuàng)建自定義攔截器,實(shí)現(xiàn) HandlerInterceptor接口的preHandle (執(zhí)行具體方法之前的預(yù)處理)方法。
- 將自定義攔截器加入 WebMvcConfigurer的addInterceptors方法中。具體實(shí)現(xiàn)如下。
4.1.2 自定義攔截器
接下來使用代碼來實(shí)現(xiàn)一個(gè)用戶登錄的權(quán)限效驗(yàn),自定義攔截器是一個(gè)普通類,具體實(shí)現(xiàn)代碼如下
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
return true;
}
response.setStatus(401);
return false;
}
}4.1.3 將自定義攔截器加入到系統(tǒng)配置
將上一步中的自定義攔截器加入到系統(tǒng)配置信息中,具體實(shí)現(xiàn)代碼如下:
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 添加攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 攔截所有接?
.excludePathPatterns("/art/param11"); // 排除接?
}
}其中:
- addPathPatterns:表示需要攔截的URL,“**”表示攔截任意方法(也就是所有方法)。
- excludePathPatterns:表示需要排除的URL。
說明:以上攔截規(guī)則可以攔截此項(xiàng)目中的使用URL,包括靜態(tài)文件(圖片文件、JS和CSS等文件
排除所有的靜態(tài)資源
// 攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 攔截所有接?
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.jpg")
.excludePathPatterns("/login.html")
.excludePathPatterns("/**/login"); // 排除接?
}4.1.4 攔截器實(shí)現(xiàn)原理
正常情況下的調(diào)用順序:

然而有了攔截器之后,會在調(diào)用Controller 之前進(jìn)行相應(yīng)的業(yè)務(wù)處理,執(zhí)行的流程如下圖所示:

4.1.5 攔截器小結(jié)
通過上面的源碼分析,我們可以看出,Spring 中的攔截器也是通過動態(tài)代理和環(huán)繞通知的思想實(shí)現(xiàn)的大體的調(diào)用流程如下:

4.1.6 擴(kuò)展:統(tǒng)?訪問前綴添加
所有請求地址添加 api 前綴:
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 所有的接?添加 api 前綴
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", c -> true);
}
}
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 所有的接?添加 api 前綴
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", c -> true);
}
}其中第二個(gè)參數(shù)是?個(gè)表達(dá)式,設(shè)置為 true 表示啟動前綴。
4.2 統(tǒng)一異常處理
統(tǒng)一異常處理使用的是 @ControllerAdvice + @ExceptionHandler 來實(shí)現(xiàn)的,
@ControllerAdvice表示控制器通知類,@ExceptionHandler是異常處理器,兩個(gè)結(jié)合表示當(dāng)出現(xiàn)異常的時(shí)候執(zhí)行某個(gè)通知就是執(zhí)行某個(gè)方法事件,具體實(shí)現(xiàn)代碼如下:
import java.util.HashMap;
@ControllerAdvice
public class ErrorAdive {
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handler(Exception e) {
HashMap<String, Object> map = new HashMap<>();
map.put("success", 0);
map.put("status", 1);
map.put("msg", e.getMessage());
return map;
}
}PS:方法名和返回值可以自定義,其中最重要的是@ExceptionHandler(Exception.class)注解.
以上方法表示,如果出現(xiàn)了異常就返回給前端一個(gè)HashMap的對象,其中包含的字段如代碼中定義的那樣。
我們可以針對不同的異常,返回不同的結(jié)果,比以下代碼所示:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
@ExceptionHandler(Exception.class)
public Object exceptionAdvice(Exception e) {
HashMap<String, Object> result = new HashMap<>();
result.put("success", -1);
result.put("message", "總的異常信息:" + e.getMessage());
result.put("data", null);
return result;
}
@ExceptionHandler(NullPointerException.class)
public Object nullPointerexceptionAdvice(NullPointerException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("success", -1);
result.put("message", "空指針異常:" + e.getMessage());
result.put("data", null);
return result;
}
}當(dāng)有多個(gè)異常通知時(shí),匹配順序?yàn)楫?dāng)前類及其子類向上依次匹配,案例演示。在UserController中設(shè)置一個(gè)空指針異常,實(shí)現(xiàn)代碼如下:
@RestController
@RequestMapping("/u")
public class UserController {
@RequestMapping("/index")
public String index() {
Object obj = null;
int i = obj.hashCode();
return "Hello,User Index.";
}
}以上程序的執(zhí)行結(jié)果如下:

4.2 統(tǒng)一數(shù)據(jù)返回格式
4.2.1 為什么需要統(tǒng)一數(shù)據(jù)返回格式?
統(tǒng)一數(shù)據(jù)返回格式的優(yōu)點(diǎn)有很多,比如以下幾個(gè):
- 方便前端程序員更好的接收和解析后端數(shù)據(jù)接口返回的數(shù)據(jù)。
- 降低前端程序員和后端程序員的溝通成本,這按照某個(gè)格式實(shí)現(xiàn)就行了,因?yàn)樗薪涌诙际沁@樣返回的。
- 有利于項(xiàng)目統(tǒng)—數(shù)據(jù)的維護(hù)和修改。
- 有利于后端技術(shù)部門的統(tǒng)一規(guī)范的標(biāo)準(zhǔn)制定,不會出現(xiàn)稀奇古怪的返回內(nèi)容。
4.2.2 統(tǒng)一數(shù)據(jù)返回格式的實(shí)現(xiàn)
統(tǒng)一的數(shù)據(jù)返回格式可以使用
@ControllerAdvice + ResponseBodyAdvice 的方式實(shí)現(xiàn),具體實(shí)現(xiàn)代碼如下:
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
/**
* 內(nèi)容是否需要重寫(通過此?法可以選擇性部分控制器和?法進(jìn)?重寫)
* 返回 true 表示重寫
*/
@Override
public boolean supports(MethodParameter returnType, Class
converterType) {
return true;
}
/**
* ?法返回之前調(diào)?此?法
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter
returnType, MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// 構(gòu)造統(tǒng)?返回對象
HashMap<String, Object> result = new HashMap<>();
result.put("success", 1);
result.put("message", "");
result.put("data", body);
return result;
}
}
總結(jié)
- 統(tǒng)一用戶登錄權(quán)限的效驗(yàn)使用WebMvcConfigurer + HandlerInterceptor來實(shí)現(xiàn),
- 統(tǒng)一異常處理使用 @ControllerAdvice + @ExceptionHandler 來實(shí)現(xiàn),
- 統(tǒng)一返回值處理使用 @ControllerAdvice + ResponseBodyAdvice 來處理。
到此這篇關(guān)于Spring AOP統(tǒng)一功能處理的文章就介紹到這了,更多相關(guān)Spring AOP用戶登陸統(tǒng)一驗(yàn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring AOP與AspectJ的對比及應(yīng)用詳解
- Spring使用AOP完成統(tǒng)一結(jié)果封裝實(shí)例demo
- SpringBoot使用AOP與注解實(shí)現(xiàn)請求參數(shù)自動填充流程詳解
- Spring?AOP實(shí)現(xiàn)用戶登錄統(tǒng)一驗(yàn)證功能
- Spring AOP源碼深入分析
- Spring AOP如何自定義注解實(shí)現(xiàn)審計(jì)或日志記錄(完整代碼)
- Spring?AOP實(shí)現(xiàn)聲明式事務(wù)機(jī)制源碼解析
- Spring AOP的概念與實(shí)現(xiàn)過程詳解
相關(guān)文章
SpringBoot+Elasticsearch實(shí)現(xiàn)數(shù)據(jù)搜索的方法詳解
Elasticsearch是一個(gè)基于Lucene的搜索服務(wù)器。它提供了一個(gè)分布式多用戶能力的全文搜索引擎,基于RESTful?web接口。本文將利用SpringBoot整合Elasticsearch實(shí)現(xiàn)海量級數(shù)據(jù)搜索,需要的可以參考一下2022-05-05
SpringBoot實(shí)現(xiàn)ORM操作MySQL的幾種方法
本文主要介紹了SpringBoot實(shí)現(xiàn)ORM操作MySQL的幾種方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
如何解決Mybatis-plus中@TableLogic注解失效問題
這篇文章主要介紹了如何解決Mybatis-plus中@TableLogic注解失效問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
SpringCloud超詳細(xì)講解微服務(wù)網(wǎng)關(guān)Gateway
這篇文章主要介紹了SpringCloud Gateway微服務(wù)網(wǎng)關(guān),負(fù)載均衡,熔斷和限流,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
Mybatis空值關(guān)聯(lián)的問題解析及解決方案
這篇文章給大家介紹了Mybatis空值關(guān)聯(lián)的問題解析及解決方案,文中通過代碼示例介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-01-01

