Proxy實現(xiàn)AOP切面編程案例
通過JDK的Proxy代理實現(xiàn)對業(yè)務(wù)類做簡單的AOP實現(xiàn)
接口:UserService 包含的方法為切入點,會被代理攔截
類:UserServiceImpl 實現(xiàn)UserService接口
類:UserServiceFactory 工廠模式生成動態(tài)代理
類:MyAspect 切面類,實現(xiàn)對切入點的操作
UserService
public interface UserService {
//切面: 需要被攔截的方法
public void addUser();
public void updateUser();
public int deleteUser(int id);
}
UserServiceImpl
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("UserServiceImpl.add()");
}
public void add(User user) {
System.out.println("UserServiceImpl.add(" + user + ")");
}
//下面繼承自UserService接口的方法會被攔截
@Override
public void addUser() {
System.out.println("UserServiceImpl.addUser()");
}
@Override
public void updateUser() {
System.out.println("UserServiceImpl.updateUser()");
}
@Override
public int deleteUser(int id) {
System.out.println("UserServiceImpl.deleteUser(" + id + ")");
return 1;
}
}
UserServiceFactory
public class UserServiceFactory {
public static UserService createUserService() {
//1、創(chuàng)建目標(biāo)對象target
final UserService userService = new UserServiceImpl();
//2、聲明切面類對象
final MyAspect myAspect = new MyAspect();
//3、將切面類before()與after()方法應(yīng)用到目標(biāo)類
//3.1、創(chuàng)建JDK代理(返回一個接口)
/*
newProxyInstance(
ClassLoader loader, //類加載器,寫當(dāng)前類
Class<?>[] interfaces, //接口,接口中包含的方法執(zhí)行時會被攔截
InvocationHandler h) //處理 調(diào)用切面類中的處理如:deforre()、after()
*/
UserService serviceProxy = (UserService) Proxy.newProxyInstance(
UserServiceFactory.class.getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//開啟事務(wù)
myAspect.before();
//返回值是調(diào)用的業(yè)務(wù)方法的返回值
Object obj = method.invoke(userService, args);
//提交事務(wù)
myAspect.after();
return obj;
}
});
return serviceProxy;
}
}
MyAspect :(就是一些具體操作,如記錄日志等)
public class MyAspect {
public void before() {
System.out.println("MyAspect.before()開啟事務(wù)...");
}
public void after() {
System.out.println("MyAspect.after()提交事務(wù)...");
}
}
單元測試:
@Test
public void aop_test() {
UserService userService = UserServiceFactory.createUserService();
userService.addUser();
userService.deleteUser(10);
userService.updateUser();
}
輸出:
MyAspect.before()開啟事務(wù)...
UserServiceImpl.addUser()
MyAspect.after()提交事務(wù)...
MyAspect.before()開啟事務(wù)...
UserServiceImpl.deleteUser(10)
MyAspect.after()提交事務(wù)...
MyAspect.before()開啟事務(wù)...
UserServiceImpl.updateUser()
MyAspect.after()提交事務(wù)...
補充知識:結(jié)合動態(tài)代理技術(shù)學(xué)習(xí)SpringAop實現(xiàn)切面編程
結(jié)合一個例子利用動態(tài)代理技術(shù)和SpringAop實現(xiàn)需求
需求:為我的UserService類中的每一個方法加上一個計時器
最初的實現(xiàn)是為每一個類添加一段代碼,這樣看起來代碼的冗余度特別大

靜態(tài)代理實現(xiàn)
在使用JDK提供動態(tài)代理之前我們先利用靜態(tài)代理技術(shù)實現(xiàn)這個需求

靜態(tài)代理需要我們自己創(chuàng)建代理類具體代碼如下:
創(chuàng)建UserService接口以及他的實現(xiàn)類及目標(biāo)類UserServiceTarget
public interface UserService {
public void insert();
public void update();
public void delete();
}
// 目標(biāo)類
public class UserServiceTarget implements UserService {
@Time
public void insert() {
System.out.println("插入用戶");
}
public void update() {
System.out.println("修改用戶");
}
public void delete() {
System.out.println("刪除用戶");
}
}
創(chuàng)建TimeHandler類,將重復(fù)的計時器代碼邏輯寫入TimeHandler類中
public class TimeHandler {
private UserServiceTarget userService = new UserServiceTarget();
//需要加計時器的方法對應(yīng)的對象 -- method
public void invoke(Method method) {
long start = System.nanoTime();
// 反射調(diào)用: 方法.invoke(對象, 參數(shù));
try {
method.invoke(userService);
} catch (Exception e) {
e.printStackTrace();
}
long end = System.nanoTime();
Time time = method.getAnnotation(Time.class);
if(time != null) {
System.out.println("花費了: " + (end - start));
}
}
}
最后一步就是自己實現(xiàn)代理類UserServiceProxy,自己實現(xiàn)代理類被稱作靜態(tài)代理
public class UserServiceProxy implements UserService {
public void insert() {
try {
TimeHandler timeHandler = new TimeHandler();
Method a = UserServiceTarget.class.getMethod("insert");
timeHandler.invoke(a);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public void update() {
try {
TimeHandler timeHandler = new TimeHandler();
Method b = UserServiceTarget.class.getMethod("update");
timeHandler.invoke(b);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public void delete() {
try {
TimeHandler timeHandler = new TimeHandler();
Method c = UserServiceTarget.class.getMethod("delete");
timeHandler.invoke(c);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
這樣在無需改變UserService類和其實現(xiàn)類的情況下增加了代碼的擴展性,降低了代碼間的耦合度。
動態(tài)代理實現(xiàn)
動態(tài)代理就是不需要我們自己創(chuàng)建代理類和代理對象,JDK會在程序運行中為我們自動生成代理對象
動態(tài)代理的三個步驟
1、生成代理類的字節(jié)碼
2、執(zhí)行類加載將字節(jié)碼加載進入JVM
3、創(chuàng)建代理類的實例對象
方式一
自己寫代碼生成需要代理類的字節(jié)碼
1、獲取代理類的字節(jié)碼
byte[] bytes = ProxyGenerator.generateProxyClass("UserServiceProxy", new Class[]{UserService.class});
//這里第一個參數(shù)是自己為代理類起的類名,第二個參數(shù)是需要創(chuàng)建代理類的字節(jié)碼數(shù)組
2、執(zhí)行類加載
ClassLoader cl = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return defineClass(name, bytes, 0, bytes.length);
}
};
Class c = cl.loadClass("UserServiceProxy"); // 進行類加載, 獲得了 UserServiceProxy 類對象
3、 創(chuàng)建代理類實例對象--通過反射
// 獲取代理類的構(gòu)造方法
Constructor constructor = c.getConstructor(InvocationHandler.class);
UserServiceTarget target = new UserServiceTarget();
// 創(chuàng)建實例對象, 強制轉(zhuǎn)換為它的接口類型
UserService proxy = (UserService)constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.nanoTime();
method.invoke(target, args);
long end = System.nanoTime();
System.out.println("花費了:" + (end - start));
return null;
}
});
這里的InvocationHandler接口匿名實現(xiàn)類似于我們之前的TimeHandler類,只需要將重復(fù)代碼邏輯寫入其中在通過方法對象反射調(diào)用該方法即可實現(xiàn)動態(tài)代理。
//使用代理對象
proxy.insert();
方式二
利用Proxy類的newProxyInstance()方法實現(xiàn)動態(tài)代理,具體代碼如下
public static void main(String[] args) {
// 直接創(chuàng)建代理類的實例
// 1. 獲取類加載器
ClassLoader cl = UserService.class.getClassLoader();
// 2. 規(guī)定代理類要實現(xiàn)的接口
Class[] interfaces = new Class[] {UserService.class};
// 3. 給一個 InvocationHandler 對象, 包含要執(zhí)行的重復(fù)邏輯
UserServiceTarget target = new UserServiceTarget();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.nanoTime();
// 方法.invoke(目標(biāo), 參數(shù));
method.invoke(target, args);
long end = System.nanoTime();
System.out.println("花費了:" + (end - start));
return null;
}
};
UserService proxy = (UserService) Proxy.newProxyInstance(cl, interfaces, h);
//4. 使用代理對象
proxy.update();
}
}
使用Spring框架AOP(面向切面編程)完成需求
Spring框架最最主要的兩大特性就是IOC(控制反轉(zhuǎn))和AOP(面向切面編程)
IOC總結(jié)見我的博客SpringIOC總結(jié)
AOP (aspect oriented programming ) 即面向切面編程
切面 aspect = 通知 adivce + 切點 pointcut
通知:是一個方法,其中包含了重復(fù)的邏輯(例如我們今天需要實現(xiàn)的計時器需求,以及Spring事務(wù)管理的底層實現(xiàn))
切點:是一種匹配條件, 與條件相符合的目標(biāo)方法,才會應(yīng)用通知方法,需要配合切點表達式
再來類比一下之前的圖

圖中的UserService就是SpringAOP技術(shù)中的代理,TimeHandler就是切面,UserServiceTarget在SpringAOP技術(shù)中被稱作目標(biāo)。
SpringAOP實現(xiàn)
首先需要添加maven依賴
<!--spring核心依賴--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.22.RELEASE</version> </dependency> <!--切面相關(guān)依賴--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
第二步,編寫切面類
@Component
//將切面交給spring容器管理
@Aspect
//@Aspect 注解表示該類是一個切面類
//切面 = 通知 + 切點
public class UserAspect {
//配置切點 @Around注解和切點表達式
@Around("within(service.impl.*)")
//配置通知方法
//ProceedingJoinPoint參數(shù)用來調(diào)用目標(biāo)方法
public Object time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long start = System.nanoTime();
Object proceed = proceedingJoinPoint.proceed();//調(diào)用目標(biāo)方法返回結(jié)果
long end = System.nanoTime();
System.out.println("springaop 方法耗時" + (end - start) + "納秒");
return proceed;
}
}
UserService和UserServiceImpl代碼如下
public interface UserService {
void insert();
void update();
void delete();
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void insert() {
System.out.println("UserServiceImpl 增加用戶信息");
}
@Override
public void update() {
System.out.println("UserServiceImpl 修改用戶信息");
}
@Override
public void delete() {
System.out.println("UserServiceImpl 刪除用戶信息");
}
}
最后一步,配置Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!-- spring容器進行包掃描 配有@Componet @Service @Controller @Repository會交由spring容器管理-->
<context:component-scan base-package="service,aspect"/>
<!-- 啟用切面編程的相關(guān)注解,例如: @Aspect, @Around, 還提供了自動產(chǎn)生代理類的功能-->
<aop:aspectj-autoproxy/>
</beans>
編寫測試類
public class TestSpringAopProgramming {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
UserService userService = context.getBean(UserService.class);
userService.insert();
userService.update();
userService.delete();
}
}
彩蛋
這個時候上面的需求又發(fā)生了變法,不是給UserService中所有的方法加計時器,而是給指定方法加計時器,又該如何實現(xiàn)?
如果我們給需要加計時器的方法加上一個注解,當(dāng)反射調(diào)用該方法的時候判斷如果有該注解在通過動態(tài)代理的方式為其加計時器不就可以解決問題了。
自定義注解

自定義注解需要添加兩個注解 @Target @Retention
@Target 表示能夠加在哪些位置
ElementType.TYPE 表示能夠加在 類上
ElementType.METHOD 表示能夠加在 方法上
ElementType.FIELD 表示能夠加在 屬性上
@Retention 表示注解的作用范圍
Source 表示注解僅在 *.java 源碼中有效
Class 表示注解在 *.java 源碼 和 *.class 字節(jié)碼中有效
Runtime 表示注解在 *.java 源碼 和 *.class 字節(jié)碼 和 運行期間都中有效
自定義注解類Time
@Target({ ElementType.METHOD } ) //該只需要加載方法上
@Retention(RetentionPolicy.RUNTIME)//需要在源碼,字節(jié)碼,以及運行中都有效
public @interface Time {
}
這個時候只需要在指定的方法上加@Time注解,然后在代理對象進行判斷即可,示例代碼如下:
public void insert() {
try {
TimeHandler timeHandler = new TimeHandler();
Method a = UserServiceTarget.class.getMethod("insert");
//通過getAnnotation()方法判斷是否存在@Time注解
if(method.getAnnotation(Time.class) !=null) {
timeHandler.invoke(a);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
以上這篇Proxy實現(xiàn)AOP切面編程案例就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用@RequiredArgsConstructor注解來取代繁瑣的@Autowrired
有了@RequiredArgsConstructor注解,我們就可以減少@Autowired的書寫,本文主要介紹了使用@RequiredArgsConstructor注解來取代繁瑣的@Autowrired,感興趣的可以了解一下2022-04-04
Spring Cloud Consul實現(xiàn)選舉機制的代碼工程
Spring Cloud Consul 是 Spring Cloud 提供的對 HashiCorp Consul 的支持,它是一種基于服務(wù)網(wǎng)格的工具,用于實現(xiàn)服務(wù)注冊、發(fā)現(xiàn)、配置管理和健康檢查,本文給大家介紹了如何用Spring Cloud Consul實現(xiàn)選舉機制,需要的朋友可以參考下2024-11-11
springboot3生成本地文件url的實現(xiàn)示例
本文主要介紹了springboot3生成本地文件url的實現(xiàn)示例,從而提供一種高效的文件管理方式,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01
IDEA?報Plugin'maven-resources-plugin:'not?found?
如果在使用?IDEA?時遇到?"Plugin?'maven-resources-plugin:'?not?found"?錯誤,可能是由于?Maven?倉庫中未找到所需的?Maven?插件,近小編給大家分享幾種解決方法,感興趣的朋友跟隨小編一起看看吧2023-07-07

