Proxy實現(xiàn)AOP切面編程案例
通過JDK的Proxy代理實現(xiàn)對業(yè)務類做簡單的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)建目標對象target final UserService userService = new UserServiceImpl(); //2、聲明切面類對象 final MyAspect myAspect = new MyAspect(); //3、將切面類before()與after()方法應用到目標類 //3.1、創(chuàng)建JDK代理(返回一個接口) /* newProxyInstance( ClassLoader loader, //類加載器,寫當前類 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 { //開啟事務 myAspect.before(); //返回值是調(diào)用的業(yè)務方法的返回值 Object obj = method.invoke(userService, args); //提交事務 myAspect.after(); return obj; } }); return serviceProxy; } }
MyAspect :(就是一些具體操作,如記錄日志等)
public class MyAspect { public void before() { System.out.println("MyAspect.before()開啟事務..."); } public void after() { System.out.println("MyAspect.after()提交事務..."); } }
單元測試:
@Test public void aop_test() { UserService userService = UserServiceFactory.createUserService(); userService.addUser(); userService.deleteUser(10); userService.updateUser(); }
輸出:
MyAspect.before()開啟事務...
UserServiceImpl.addUser()
MyAspect.after()提交事務...
MyAspect.before()開啟事務...
UserServiceImpl.deleteUser(10)
MyAspect.after()提交事務...
MyAspect.before()開啟事務...
UserServiceImpl.updateUser()
MyAspect.after()提交事務...
補充知識:結合動態(tài)代理技術學習SpringAop實現(xiàn)切面編程
結合一個例子利用動態(tài)代理技術和SpringAop實現(xiàn)需求
需求:為我的UserService類中的每一個方法加上一個計時器
最初的實現(xiàn)是為每一個類添加一段代碼,這樣看起來代碼的冗余度特別大
靜態(tài)代理實現(xiàn)
在使用JDK提供動態(tài)代理之前我們先利用靜態(tài)代理技術實現(xiàn)這個需求
靜態(tài)代理需要我們自己創(chuàng)建代理類具體代碼如下:
創(chuàng)建UserService接口以及他的實現(xiàn)類及目標類UserServiceTarget
public interface UserService { public void insert(); public void update(); public void delete(); } // 目標類 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類,將重復的計時器代碼邏輯寫入TimeHandler類中
public class TimeHandler { private UserServiceTarget userService = new UserServiceTarget(); //需要加計時器的方法對應的對象 -- 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)建代理類實例對象--通過反射
// 獲取代理類的構造方法 Constructor constructor = c.getConstructor(InvocationHandler.class); UserServiceTarget target = new UserServiceTarget(); // 創(chuàng)建實例對象, 強制轉換為它的接口類型 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類,只需要將重復代碼邏輯寫入其中在通過方法對象反射調(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í)行的重復邏輯 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(目標, 參數(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(控制反轉)和AOP(面向切面編程)
IOC總結見我的博客SpringIOC總結
AOP (aspect oriented programming ) 即面向切面編程
切面 aspect = 通知 adivce + 切點 pointcut
通知:是一個方法,其中包含了重復的邏輯(例如我們今天需要實現(xiàn)的計時器需求,以及Spring事務管理的底層實現(xiàn))
切點:是一種匹配條件, 與條件相符合的目標方法,才會應用通知方法,需要配合切點表達式
再來類比一下之前的圖
圖中的UserService就是SpringAOP技術中的代理,TimeHandler就是切面,UserServiceTarget在SpringAOP技術中被稱作目標。
SpringAOP實現(xiàn)
首先需要添加maven依賴
<!--spring核心依賴--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.22.RELEASE</version> </dependency> <!--切面相關依賴--> <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)用目標方法 public Object time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.nanoTime(); Object proceed = proceedingJoinPoint.proceed();//調(diào)用目標方法返回結果 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"/> <!-- 啟用切面編程的相關注解,例如: @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)?
如果我們給需要加計時器的方法加上一個注解,當反射調(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)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
SpringBoot集成PostgreSQL并設置最大連接數(shù)
本文主要介紹了SpringBoot集成PostgreSQL并設置最大連接數(shù),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-11-11SpringBoot配置文件、多環(huán)境配置、讀取配置的4種實現(xiàn)方式
SpringBoot支持多種配置文件位置和格式,其中application.properties和application.yml是默認加載的文件,配置文件可以根據(jù)環(huán)境通過spring.profiles.active屬性進行區(qū)分,命令行參數(shù)具有最高優(yōu)先級,可覆蓋其他所有配置2024-09-09Java實戰(zhàn)之實現(xiàn)一個好用的MybatisPlus代碼生成器
這篇文章主要介紹了Java實戰(zhàn)之實現(xiàn)一個好用的MybatisPlus代碼生成器,文中有非常詳細的代碼示例,對正在學習java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04Java實現(xiàn)等待所有子線程結束后再執(zhí)行一段代碼的方法
這篇文章主要介紹了Java實現(xiàn)等待所有子線程結束后再執(zhí)行一段代碼的方法,涉及java多線程的線程等待與執(zhí)行等相關操作技巧,需要的朋友可以參考下2017-08-08Java實現(xiàn)經(jīng)典游戲Flappy Bird的示例代碼
Flappy?Bird是13年紅極一時的小游戲,即摁上鍵控制鳥的位置穿過管道間的縫隙。本文將用Java語言實現(xiàn)這一經(jīng)典的游戲,需要的可以參考一下2022-02-02SpringBoot實現(xiàn)PDF添加水印的三種方法
本文主要介紹了SpringBoot實現(xiàn)PDF添加水印的三種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07