Spring AOP 與代理的概念與使用
一、AOP 的基本概念
1.1 什么是 AOP
Aspect Oriented Programming,面向切面編程。
就跟我們說(shuō) OOP 是面向?qū)ο笠粯?,AOP 是面向切面的。切面是分散在應(yīng)用中的一個(gè)標(biāo)準(zhǔn)代碼或功能。切面通常與實(shí)際的業(yè)務(wù)邏輯不同(例如,事務(wù)管理)。每個(gè)切面專注于一個(gè)特定的環(huán)切功能。
這里的切面呢,可以理解為橫切。比如在所有的 DAO 層方法上加上一個(gè)同樣的切面,功能是記錄日志;又或者在某個(gè)接口上應(yīng)用一個(gè)切面,作用是檢查權(quán)限。
AOP 是基于代理來(lái)實(shí)現(xiàn)的。而代理又分為靜態(tài)代理和動(dòng)態(tài)代理。兩者的區(qū)別在于代理類于何時(shí)生成。
下面我們講講代理是怎么回事?
1.2 代理與 Spring AOP
代理分為靜態(tài)代理和動(dòng)態(tài)代理:
- 靜態(tài)代理:代理類在編譯階段生成,程序運(yùn)行前就存在。包括:AspectJ 靜態(tài)代理、JDK 靜態(tài)代理
- 動(dòng)態(tài)代理:代理類在程序運(yùn)行時(shí)創(chuàng)建。包括:JDK 動(dòng)態(tài)代理、CGLib 動(dòng)態(tài)代理
Spring AOP 原理:
- JDK Proxy:interface based
- CGLib Proxy: class based

Spring AOP 中默認(rèn)使用 JDK 動(dòng)態(tài)代理,通過(guò)反射獲取被代理的類,這個(gè)類必須實(shí)現(xiàn)一個(gè)接口。如果目標(biāo)類沒(méi)有實(shí)現(xiàn)接口,就會(huì)默認(rèn)使用 CGLIB Proxy 來(lái)動(dòng)態(tài)生成代理目標(biāo)類,后者是被代理類的子類。
可以通過(guò)獲取代理對(duì)象并打印的方式來(lái)查看其類型(JDK Proxy 下是 com.sun.prxy, CGlib 下是子類.
AspectJ: 用特定的編譯器和語(yǔ)法,在編譯時(shí)增強(qiáng),實(shí)現(xiàn)了靜態(tài)代理技術(shù)。
1.3 Spring AOP 與 AspectJ 的區(qū)別
AspectJ 是一套完整的 AOP 解決方案,而 Spring AOP 并不是 —— 它只是在 Spring 框架下滿足其使用要求的一個(gè)解決方法,比如 Spring AOP 僅支持對(duì)方法使用切面。
二、靜態(tài)代理
2.1 AspectJ 靜態(tài)代理
基于特殊的編譯器和語(yǔ)法。這里不多介紹了。
IDEA 下編譯 AspectJ 可以參考這篇:https://blog.csdn.net/gavin_john/article/details/80156963
2.2 JDK 靜態(tài)代理
實(shí)際上是利用實(shí)現(xiàn)一個(gè)具體的代理類來(lái)調(diào)用業(yè)務(wù)類。代理類持有了一個(gè)業(yè)務(wù)類的引用。
更概況地說(shuō),JDK 靜態(tài)代理體現(xiàn)的是一種設(shè)計(jì)模式。
缺點(diǎn)很明顯,代碼冗余,難以維護(hù)。
這里以 借書(shū) 和 還書(shū) 這兩個(gè)行為來(lái)作為一個(gè)示例:
編寫一個(gè) BookService 接口:
public interface BookService {
boolean borrow(String id, String userName);
boolean reBack(String id, String userName);
}
然后實(shí)現(xiàn)這個(gè)接口:
public class BookServiceImpl implements BookService {
@Override
public boolean borrow(String id, String userName) {
System.out.println(userName + " 借書(shū):" + id);
return true;
}
@Override
public boolean reBack(String id, String userName) {
System.out.println(userName + " 還書(shū):" + id);
return true;
}
}
下面我們來(lái)編寫 BookService 的代理類:
public class BookProxy implements BookService {
private BookServiceImpl bookService;
public BookProxy(BookServiceImpl bookService) {
this.bookService = bookService;
}
@Override
public boolean borrow(String id, String userName) {
boolean res = false;
if (check()) {
res = bookService.borrow(id, userName);
}
addLog();
return res;
}
@Override
public boolean reBack(String id, String userName) {
boolean res = false;
if (check()) {
res = bookService.reBack(id, userName);
}
addLog();
return res;
}
//
private boolean check() {
System.out.println("檢查權(quán)限");
return true;
}
private void addLog() {
System.out.println("操作完成");
}
}
編寫一個(gè)測(cè)試類:
public class MainTest {
public static void main(String[] args) {
BookProxy proxy = new BookProxy(new BookServiceImpl());
proxy.borrow("123", "eknown");
proxy.reBack("234", "java");
}
}
這里我們可以看到,JDK 靜態(tài)代理就是說(shuō)在原來(lái)的實(shí)現(xiàn)類上套一層 代理。它好像是體現(xiàn)了代理模式,但實(shí)際上并沒(méi)有帶來(lái)太多的好處。代碼相當(dāng)冗余,也不利于維護(hù)。
真正體現(xiàn)代理模式好處的還是動(dòng)態(tài)代理,下面我們來(lái)看看動(dòng)態(tài)代理的原理。
三、動(dòng)態(tài)代理
動(dòng)態(tài)代理是程序運(yùn)行時(shí),由 JVM 根據(jù)反射等機(jī)制動(dòng)態(tài)生成代理類的。
也就是說(shuō),程序運(yùn)行前,我們僅僅定義了代理的規(guī)則,而不知道代理類具體長(zhǎng)什么樣,這不像上面的靜態(tài)代理里,我們完整地定義了代理對(duì)象。
3.1 JDK 動(dòng)態(tài)代理
JDK 動(dòng)態(tài)代理是基于接口的。
我們可以通過(guò)實(shí)現(xiàn) InvocationHandler 接口來(lái)手動(dòng)創(chuàng)建一個(gè) JDK 代理類。
首先需要定義一個(gè)接口,讓業(yè)務(wù)類和代理類都實(shí)現(xiàn)這個(gè)接口。
然后編寫一個(gè) InvocationHandler 接口的實(shí)現(xiàn)類:
public class BookProxy implements InvocationHandler {
// 被該代理類處理的業(yè)務(wù)類
private BookService bookService;
public BookProxy(BookService bookService) {
this.bookService = bookService;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
if (check()) {
// 調(diào)用實(shí)際的 method,參數(shù)是 接口 + 參數(shù)
res = method.invoke(bookService, args);
}
addLog();
return res;
}
private boolean check() {
System.out.println("檢查權(quán)限");
return true;
}
private void addLog() {
System.out.println("操作完成");
}
}
測(cè)試:
public class MainTest {
public static void main(String[] args) {
// 創(chuàng)建被代理的實(shí)際業(yè)務(wù)類
BookServiceImpl bookServiceImpl = new BookServiceImpl();
ClassLoader classLoader = bookServiceImpl.getClass().getClassLoader();
// 獲取所有的接口方法
Class[] interfaces = bookServiceImpl.getClass().getInterfaces();
// 構(gòu)造 Handler
InvocationHandler invocationHandler = new BookProxy(bookServiceImpl);
// 創(chuàng)建代理
Object obj = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
BookService bookService = (BookService) obj;
bookService.borrow("abc", "eknown");
bookService.reBack("c23", "py");
}
}
3.2 CGLIB 動(dòng)態(tài)代理
CGLIB 代理的原理是:讓代理類繼承業(yè)務(wù)類(也就自動(dòng)擁有了業(yè)務(wù)類的所有非 final 的 public 方法)
我們這里手動(dòng)編寫一個(gè) CGLIB 的代理試試看。
首先我們有一個(gè) BookServiceImpl 業(yè)務(wù)類,這個(gè)業(yè)務(wù)類可以實(shí)現(xiàn)接口,也可以就是單純的一個(gè)業(yè)務(wù)類。
然后我們定義一個(gè) BookCglibProxy 類:
public class BookCglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
check();
// 調(diào)用實(shí)際的 method
Object obj = methodProxy.invokeSuper(o, objects);
addLog();
return obj;
}
private boolean check() {
System.out.println("檢查權(quán)限");
return true;
}
private void addLog() {
System.out.println("操作完成");
}
}
測(cè)試類:
public class CglibTest {
public static void main(String[] args) {
BookServiceImpl bookServiceImpl = new BookServiceImpl();
BookCglibProxy proxy = new BookCglibProxy();
// cjlib 中的增強(qiáng)器,用于創(chuàng)建動(dòng)態(tài)代理(被代理類的子類)
Enhancer enhancer = new Enhancer();
// 設(shè)置要被代理的類
enhancer.setSuperclass(bookServiceImpl.getClass());
// 設(shè)置回調(diào)
enhancer.setCallback(proxy);
// 強(qiáng)轉(zhuǎn)成父類
BookServiceImpl proxyResult = (BookServiceImpl) enhancer.create();
proxyResult.borrow("12333", "ye");
proxyResult.reBack("123", "fe");
}
}
在第一節(jié)我們提到過(guò) Spring AOP 是基于 JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理的。下面我們來(lái) Spring AOP 的一些基本案例。
四、Spring AOP 實(shí)例
AOP 中一些概念詞匯,通過(guò)這些詞匯,我們可以對(duì) AOP 有更高一層的抽象。
- Aspect - 切面,分散在應(yīng)用中的一個(gè)標(biāo)準(zhǔn)代碼或功能。切面通常與實(shí)際的業(yè)務(wù)邏輯不同(例如,事務(wù)管理)。每個(gè)切面專注于一個(gè)特定的環(huán)切功能。
- Joinpoint - 連接點(diǎn),是程序執(zhí)行過(guò)程中的特定點(diǎn),比如方法執(zhí)行、構(gòu)造器調(diào)用、字段賦值
- Advice - 通知,切面在某個(gè)連接點(diǎn)采取的操作。Advice 有 5 種類型。
- Pointcut - 切入點(diǎn),一個(gè)匹配連接點(diǎn)的正則表達(dá)式。每當(dāng)連接點(diǎn)匹配了一個(gè)切入點(diǎn)時(shí),一個(gè)特定的通知就會(huì)被執(zhí)行。
- Weaving - 織入,指的是將切面和目標(biāo)對(duì)象連接起來(lái)以創(chuàng)建代理對(duì)象的過(guò)程。
Spring AOP 有兩種實(shí)現(xiàn)方式:基于 XML 或基于注解。更流行、更方便的是后者。(阿 sir,不會(huì)還有人用 XML 來(lái)做 Bean 的配置文件吧?)
4.1 基于 XML 的實(shí)例
首先定義一下接口和實(shí)現(xiàn)類(沒(méi)有注解的?。?。再編寫一個(gè)代理類:
這里的代理類方法以 JoinPoint 為參數(shù)即可:
public class BookAspect {
public void checkUser(JoinPoint point) {
System.out.println("-----before-----");
Object[] args = point.getArgs();
for (Object arg : args) {
System.out.println(arg);
}
System.out.println("檢查用戶權(quán)限...");
}
public void saveLog(JoinPoint point) {
System.out.println("-----after-----");
Object[] args = point.getArgs();
for (Object arg : args) {
System.out.println(arg);
}
System.out.println("請(qǐng)求完畢,記錄日志...");
}
}
然后編寫 Spring 的配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <!-- 定義 bean --> <bean id="bookService" class="com.example.springaopdemo.basicxml.BookServiceImpl" /> <bean id="bookAspect" class="com.example.springaopdemo.basicxml.BookAspect" /> <aop:config> <!-- 這是定義一個(gè)切面,切面是切點(diǎn)和通知的集合--> <aop:aspect id="do" ref="bookAspect"> <!-- 定義切點(diǎn) ,后面是 expression 語(yǔ)言,表示包括該接口中定義的所有方法都會(huì)被執(zhí)行 --> <aop:pointcut id="point" expression="execution(* com.example.springaopdemo.basicxml.BookService.*(..))" /> <!-- 定義通知 --> <aop:before method="checkUser" pointcut-ref="point" /> <aop:after method="saveLog" pointcut-ref="point" /> </aop:aspect> </aop:config> </beans>
運(yùn)行測(cè)試:
public class AopXMLTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("SpringAop.xml");
BookService bookService = context.getBean("bookService", BookService.class);
bookService.borrow("123", "eknown");
bookService.reback("123", "eknown");
}
}
基于 XML 配置的 Spring 現(xiàn)在已經(jīng)很少使用了。下面我們來(lái)看看如何基于注解使用 Spring AOP
4.2 基于注解的實(shí)例
這里以一個(gè)使用 SpringBoot 框架的 Web 項(xiàng)目作為簡(jiǎn)單的實(shí)例。
首先創(chuàng)建一個(gè) SpringBoot 項(xiàng)目,寫好 Controller、Service、DAO 層的基本類。(示例源碼中沒(méi)有使用 Mybatis 等持久層框架,而是用 Map 來(lái)模擬數(shù)據(jù)的存取)
下面我們針對(duì) UserService 接口類,添加切面。
@Aspect
@Component
public class UserAspect {
@Before(value = "execution(* com.example.springaopdemo.boot.UserService.*(..))")
public void checkUser(JoinPoint point) {
System.out.println("-----before-----");
Object[] args = point.getArgs();
for (Object arg : args) {
System.out.println(arg);
}
System.out.println("檢查..." + point);
}
@After(value = "execution(* com.example.springaopdemo.boot.UserService.*(..))")
public void saveLog(JoinPoint point) {
System.out.println("-----after-----");
Object[] args = point.getArgs();
for (Object arg : args) {
System.out.println(arg);
}
// 這里可以使用 point.getTarget() 獲取到切面對(duì)應(yīng)的 bean
//Object target = point.getTarget();
//UserService userService = (UserService) target;
//List<User> userList = userService.findAll();
System.out.println("請(qǐng)求完畢,記錄日志..." + point);
}
@Around(value = "execution(* com.example.springaopdemo.boot.UserService.save(..))")
public Object saveAround(ProceedingJoinPoint point) {
System.out.println("around-before");
Object obj = null;
try {
obj = point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("around-after");
return obj;
}
}
示例中使用了 @Before/@After/@Aroud 三個(gè)注解,value 中使用切點(diǎn)表達(dá)式,分別匹配了 UserService 接口的所有方法和單個(gè) save 方法。
我們還可以通過(guò)切點(diǎn)表達(dá)式匹配自定義的注解,比如實(shí)現(xiàn)一個(gè) UserMonitor 注解,然后定義其切點(diǎn)方法:
public @interface UserMonitor {
String value() default "";
int roleLimit() default 0;
}
切點(diǎn):
@Around("@annotation(com.example.springaopdemo.boot.UserMonitor)")
public Object userRolePointCut(ProceedingJoinPoint point) {
System.out.println("檢查用戶權(quán)限...");
// 獲取參數(shù)
Object[] args = point.getArgs();
Class<?>[] argTypes = new Class[point.getArgs().length];
for (int i = 0; i < args.length; i++) {
argTypes[i] = args[i].getClass();
}
// 獲取方法
Method method = null;
try {
method = point.getTarget().getClass()
.getMethod(point.getSignature().getName(), argTypes);
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
// 獲取方法上的該注解,之后可以根據(jù)注解中的值進(jìn)行一些操作,比如判定是否具有權(quán)限
UserMonitor monitor = method.getAnnotation(UserMonitor.class);
System.out.println(monitor);
Object obj = null;
try {
obj = point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return obj;
}
以上就是Spring AOP 與代理的概念與使用的詳細(xì)內(nèi)容,更多關(guān)于Spring AOP 與代理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)ATM機(jī)操作系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)ATM機(jī)操作系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
本地安裝MinIO分布式對(duì)象存儲(chǔ)服務(wù)器的詳細(xì)步驟
本地安裝MinIO非常簡(jiǎn)單,MinIO提供了獨(dú)立的二進(jìn)制文件,無(wú)需額外的依賴,本文介紹如何在本地安裝MinIO分布式對(duì)象存儲(chǔ)服務(wù)器,感興趣的朋友一起看看吧2024-01-01
Java基于rest assured實(shí)現(xiàn)接口測(cè)試過(guò)程解析
這篇文章主要介紹了Java基于rest assured實(shí)現(xiàn)接口測(cè)試過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
Java實(shí)現(xiàn)表單提交(支持多文件同時(shí)上傳)
本文介紹了Java、Android實(shí)現(xiàn)表單提交(支持多文件同時(shí)上傳)的方法,具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-01-01
java使用JDBC動(dòng)態(tài)創(chuàng)建數(shù)據(jù)表及SQL預(yù)處理的方法
這篇文章主要介紹了java使用JDBC動(dòng)態(tài)創(chuàng)建數(shù)據(jù)表及SQL預(yù)處理的方法,涉及JDBC操作數(shù)據(jù)庫(kù)的連接、創(chuàng)建表、添加數(shù)據(jù)、查詢等相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-08-08
Java安全 ysoserial CommonsCollections3示例分析
這篇文章主要為大家介紹了Java安全 ysoserial CommonsCollections3示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Spring Core動(dòng)態(tài)代理的實(shí)現(xiàn)代碼
通過(guò)JDK的Proxy方式或者CGLIB方式生成代理對(duì)象的時(shí)候,相關(guān)的攔截器已經(jīng)配置到代理對(duì)象中去了,接下來(lái)通過(guò)本文給大家介紹Spring Core動(dòng)態(tài)代理的相關(guān)知識(shí),需要的朋友可以參考下2021-10-10
Java開(kāi)發(fā)必備知識(shí)之?dāng)?shù)組詳解
數(shù)組對(duì)于每一門編程語(yǔ)言來(lái)說(shuō)都是重要的數(shù)據(jù)結(jié)構(gòu)之一,當(dāng)然不同語(yǔ)言對(duì)數(shù)組的實(shí)現(xiàn)及處理也不盡相同.本篇文章為大家整理了Java最全關(guān)于數(shù)組的知識(shí)點(diǎn),并給出其對(duì)應(yīng)的代碼,需要的朋友可以參考下2021-06-06

