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

3.2 Spring AOP實現(xiàn)
使用Spring AOP來實現(xiàn)一下AOP的功能,完成的目標是攔截所有UserController里面的方法,每次調(diào)用UserController中任意一個方法時,都執(zhí)行相應的通知事件。
Spring AOP 的實現(xiàn)步驟是:
- 添加 AOP 框架支持。
- 定義切面和切點。
- 定義通知。
3.2.1 添加 AOP 框架支持
3.2.2 定義切面和切點。
3.2.3 定義相關通知
通知定義的是被攔截的方法具體要執(zhí)行的業(yè)務,比如用戶登錄權限驗證方法就是具體要執(zhí)行的業(yè)務Spring AOP中,可以在方法上使用以下注解,會設置方法為通知方法,在滿足條件后會通知本方法進行調(diào)用:
- 前置通知使用@Before:通知方法會在目標方法調(diào)用之前執(zhí)行。
- 后置通知使用@After:通知方法會在目標方法返回或者拋出異常后調(diào)用。
- 返回之后通知使用@AfterReturning:通知方法會在目標方法返回后調(diào)用。
- 拋異常后通知使用@AfterThrowing:通知方法會在目標方法拋出異常后調(diào)用。
- 環(huán)繞通知使用@Around:通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為。
具體實現(xiàn)如下:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class UserAspect {
// 定義切點?法
@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 ?法結束執(zhí)?");
return obj;
}
}
3.3 Spring AOP 實現(xiàn)原理
3.3.1 動態(tài)代理
Spring AOP是構建在動態(tài)代理基礎上,因此Spring對AOP的支持局限于方法級別的攔截。
Spring AOP支持JDK Proxy和CGLIB方式實現(xiàn)動態(tài)代理。默認情況下,實現(xiàn)了接口的類,使用AOP會基于JDK生成代理類,沒有實現(xiàn)接口的類,會基于CGLIB生成代理類。

這兩種方式的代理目標都是被代理類中的方法,在運行期,動態(tài)的織入字節(jié)碼生成代理類。
3.3.2 JDK和CGLIB實現(xiàn)的區(qū)別
- JDK實現(xiàn),要求被代理類必須實現(xiàn)接口,之后是通過
InvocationHandler及Proxy,在運行時動態(tài)的在內(nèi)存中生成了代理類對象,該代理對象是通過實現(xiàn)同樣的接口實現(xiàn)(類似靜態(tài)代理接口實現(xiàn)的方式),只是該代理類是在運行期時,動態(tài)的織入統(tǒng)一的業(yè)務邏輯字節(jié)碼來完成。 - CGLIB實現(xiàn),被代理類可以不實現(xiàn)接口,是通過繼承被代理類,在運行時動態(tài)的生成代理類
3.3.3 織入(Weaving):代理的生成時機
織入是把切面應用到目標對象并創(chuàng)建新的代理對象的過程,切面在指定的連接點被織入到目標對象中。
在目標對象的生命周期里有多個點可以進行織入∶
- 編譯期:切面在目標類編譯時被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
- 類加載器:切面在目標類加載到JVM時被織入。這種方式需要特殊的類加載器 (ClassLoader),它可以在目標類被引入應用之前增強該目標類的字節(jié)碼。AspectJ5的加載時織入(load-time weaving. LTW)就支持以這種方式織入切面。
- 運行期:切面在應用運行的某一時刻被織入。一般情況下,在織入切面時,AOP容器會為目標對象動態(tài)創(chuàng)建一個代理對象。SpringAOP就是以這種方式織入切面的。
此種實現(xiàn)在設計模式上稱為動態(tài)代理模式,在實現(xiàn)的技術手段上,都是在class代碼運行期,動態(tài)的織入字節(jié)碼生成代理類。
3.3.4 總結
AOP是對某方面能力的統(tǒng)一實現(xiàn),它是一種實現(xiàn)思想,Spring AOP是對AOP的具體實現(xiàn),SpringAOP可通過AspectJ(注解) 的方式來實現(xiàn)AOP的功能,Spring AOP 的實現(xiàn)步驟是:
- 添加AOP框架支持。
- 定義切面和切點。
- 定義通知。
Spring AOP是通過動態(tài)代理的方式,在運行期將AOP代碼織入到程序中的,它的實現(xiàn)方式有兩種JDK Proxy和CGLIB。
4. SpringBoot 統(tǒng)一功能處理
接下來是Spring Boot統(tǒng)一功能處理模塊了,也是AOP的實戰(zhàn)環(huán)節(jié),要實現(xiàn)的課程目標有以下3個:
- 統(tǒng)一用戶登錄權限驗證;
- 統(tǒng)—數(shù)據(jù)格式返回;
- 統(tǒng)一異常處理。
接下我們一個一個來看。
4.1 用戶登錄權限效驗
用戶登錄權限的發(fā)展從之前每個方法中自己驗證用戶登錄權限,到現(xiàn)在統(tǒng)一的用戶登錄驗證處理,它是—個逐漸完善和逐漸優(yōu)化的過程。
4.1.1 Spring攔截器
Spring 中提供了具體的實現(xiàn)攔截器:HandlerInterceptor,
統(tǒng)一用戶登錄權限的效驗使用WebMvcConfigurer + HandlerInterceptor來實現(xiàn)。
攔截器的實現(xiàn)分為以下兩個步驟∶
- 創(chuàng)建自定義攔截器,實現(xiàn) HandlerInterceptor接口的preHandle (執(zhí)行具體方法之前的預處理)方法。
- 將自定義攔截器加入 WebMvcConfigurer的addInterceptors方法中。具體實現(xiàn)如下。
4.1.2 自定義攔截器
接下來使用代碼來實現(xiàn)一個用戶登錄的權限效驗,自定義攔截器是一個普通類,具體實現(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)配置信息中,具體實現(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ī)則可以攔截此項目中的使用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 攔截器實現(xiàn)原理
正常情況下的調(diào)用順序:

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

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

4.1.6 擴展:統(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);
}
}其中第二個參數(shù)是?個表達式,設置為 true 表示啟動前綴。
4.2 統(tǒng)一異常處理
統(tǒng)一異常處理使用的是 @ControllerAdvice + @ExceptionHandler 來實現(xiàn)的,
@ControllerAdvice表示控制器通知類,@ExceptionHandler是異常處理器,兩個結合表示當出現(xiàn)異常的時候執(zhí)行某個通知就是執(zhí)行某個方法事件,具體實現(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)了異常就返回給前端一個HashMap的對象,其中包含的字段如代碼中定義的那樣。
我們可以針對不同的異常,返回不同的結果,比以下代碼所示:
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;
}
}當有多個異常通知時,匹配順序為當前類及其子類向上依次匹配,案例演示。在UserController中設置一個空指針異常,實現(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í)行結果如下:

4.2 統(tǒng)一數(shù)據(jù)返回格式
4.2.1 為什么需要統(tǒng)一數(shù)據(jù)返回格式?
統(tǒng)一數(shù)據(jù)返回格式的優(yōu)點有很多,比如以下幾個:
- 方便前端程序員更好的接收和解析后端數(shù)據(jù)接口返回的數(shù)據(jù)。
- 降低前端程序員和后端程序員的溝通成本,這按照某個格式實現(xiàn)就行了,因為所有接口都是這樣返回的。
- 有利于項目統(tǒng)—數(shù)據(jù)的維護和修改。
- 有利于后端技術部門的統(tǒng)一規(guī)范的標準制定,不會出現(xiàn)稀奇古怪的返回內(nèi)容。
4.2.2 統(tǒng)一數(shù)據(jù)返回格式的實現(xiàn)
統(tǒng)一的數(shù)據(jù)返回格式可以使用
@ControllerAdvice + ResponseBodyAdvice 的方式實現(xiàn),具體實現(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)容是否需要重寫(通過此?法可以選擇性部分控制器和?法進?重寫)
* 返回 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) {
// 構造統(tǒng)?返回對象
HashMap<String, Object> result = new HashMap<>();
result.put("success", 1);
result.put("message", "");
result.put("data", body);
return result;
}
}
總結
- 統(tǒng)一用戶登錄權限的效驗使用WebMvcConfigurer + HandlerInterceptor來實現(xiàn),
- 統(tǒng)一異常處理使用 @ControllerAdvice + @ExceptionHandler 來實現(xiàn),
- 統(tǒng)一返回值處理使用 @ControllerAdvice + ResponseBodyAdvice 來處理。
到此這篇關于Spring AOP統(tǒng)一功能處理的文章就介紹到這了,更多相關Spring AOP用戶登陸統(tǒng)一驗證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot+Elasticsearch實現(xiàn)數(shù)據(jù)搜索的方法詳解
Elasticsearch是一個基于Lucene的搜索服務器。它提供了一個分布式多用戶能力的全文搜索引擎,基于RESTful?web接口。本文將利用SpringBoot整合Elasticsearch實現(xiàn)海量級數(shù)據(jù)搜索,需要的可以參考一下2022-05-05
SpringBoot實現(xiàn)ORM操作MySQL的幾種方法
本文主要介紹了SpringBoot實現(xiàn)ORM操作MySQL的幾種方法,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
如何解決Mybatis-plus中@TableLogic注解失效問題
這篇文章主要介紹了如何解決Mybatis-plus中@TableLogic注解失效問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
SpringCloud超詳細講解微服務網(wǎng)關Gateway
這篇文章主要介紹了SpringCloud Gateway微服務網(wǎng)關,負載均衡,熔斷和限流,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07

