Proxy實(shí)現(xiàn)AOP切面編程案例
通過(guò)JDK的Proxy代理實(shí)現(xiàn)對(duì)業(yè)務(wù)類做簡(jiǎn)單的AOP實(shí)現(xiàn)
接口:UserService 包含的方法為切入點(diǎn),會(huì)被代理攔截
類:UserServiceImpl 實(shí)現(xiàn)UserService接口
類:UserServiceFactory 工廠模式生成動(dòng)態(tài)代理
類:MyAspect 切面類,實(shí)現(xiàn)對(duì)切入點(diǎ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接口的方法會(huì)被攔截
@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)對(duì)象target
final UserService userService = new UserServiceImpl();
//2、聲明切面類對(duì)象
final MyAspect myAspect = new MyAspect();
//3、將切面類before()與after()方法應(yīng)用到目標(biāo)類
//3.1、創(chuàng)建JDK代理(返回一個(gè)接口)
/*
newProxyInstance(
ClassLoader loader, //類加載器,寫當(dāng)前類
Class<?>[] interfaces, //接口,接口中包含的方法執(zhí)行時(shí)會(huì)被攔截
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ù)...");
}
}
單元測(cè)試:
@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ù)...
補(bǔ)充知識(shí):結(jié)合動(dòng)態(tài)代理技術(shù)學(xué)習(xí)SpringAop實(shí)現(xiàn)切面編程
結(jié)合一個(gè)例子利用動(dòng)態(tài)代理技術(shù)和SpringAop實(shí)現(xiàn)需求
需求:為我的UserService類中的每一個(gè)方法加上一個(gè)計(jì)時(shí)器
最初的實(shí)現(xiàn)是為每一個(gè)類添加一段代碼,這樣看起來(lái)代碼的冗余度特別大

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

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

圖中的UserService就是SpringAOP技術(shù)中的代理,TimeHandler就是切面,UserServiceTarget在SpringAOP技術(shù)中被稱作目標(biāo)。
SpringAOP實(shí)現(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 注解表示該類是一個(gè)切面類
//切面 = 通知 + 切點(diǎn)
public class UserAspect {
//配置切點(diǎn) @Around注解和切點(diǎn)表達(dá)式
@Around("within(service.impl.*)")
//配置通知方法
//ProceedingJoinPoint參數(shù)用來(lái)調(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 方法耗時(shí)" + (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容器進(jìn)行包掃描 配有@Componet @Service @Controller @Repository會(huì)交由spring容器管理-->
<context:component-scan base-package="service,aspect"/>
<!-- 啟用切面編程的相關(guān)注解,例如: @Aspect, @Around, 還提供了自動(dòng)產(chǎn)生代理類的功能-->
<aop:aspectj-autoproxy/>
</beans>
編寫測(cè)試類
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();
}
}
彩蛋
這個(gè)時(shí)候上面的需求又發(fā)生了變法,不是給UserService中所有的方法加計(jì)時(shí)器,而是給指定方法加計(jì)時(shí)器,又該如何實(shí)現(xiàn)?
如果我們給需要加計(jì)時(shí)器的方法加上一個(gè)注解,當(dāng)反射調(diào)用該方法的時(shí)候判斷如果有該注解在通過(guò)動(dòng)態(tài)代理的方式為其加計(jì)時(shí)器不就可以解決問(wèn)題了。
自定義注解

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

