您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關Spring AOP 與代理的概念與使用方法,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
一、AOP 的基本概念
1.1 什么是 AOP
Aspect Oriented Programming,面向切面編程。
就跟我們說 OOP 是面向對象一樣,AOP 是面向切面的。切面是分散在應用中的一個標準代碼或功能。切面通常與實際的業務邏輯不同(例如,事務管理)。每個切面專注于一個特定的環切功能。
這里的切面呢,可以理解為橫切。比如在所有的 DAO 層方法上加上一個同樣的切面,功能是記錄日志;又或者在某個接口上應用一個切面,作用是檢查權限。
AOP 是基于代理來實現的。而代理又分為靜態代理和動態代理。兩者的區別在于代理類于何時生成。
下面我們講講代理是怎么回事?
1.2 代理與 Spring AOP
代理分為靜態代理和動態代理:
Spring AOP 原理:
Spring AOP 中默認使用 JDK 動態代理,通過反射獲取被代理的類,這個類必須實現一個接口。如果目標類沒有實現接口,就會默認使用 CGLIB Proxy 來動態生成代理目標類,后者是被代理類的子類。
可以通過獲取代理對象并打印的方式來查看其類型(JDK Proxy 下是 com.sun.prxy, CGlib 下是子類.
AspectJ: 用特定的編譯器和語法,在編譯時增強,實現了靜態代理技術。
1.3 Spring AOP 與 AspectJ 的區別
AspectJ 是一套完整的 AOP 解決方案,而 Spring AOP 并不是 —— 它只是在 Spring 框架下滿足其使用要求的一個解決方法,比如 Spring AOP 僅支持對方法使用切面。
二、靜態代理
2.1 AspectJ 靜態代理
基于特殊的編譯器和語法。這里不多介紹了。
IDEA 下編譯 AspectJ 可以參考這篇:https://blog.csdn.net/gavin_john/article/details/80156963
2.2 JDK 靜態代理
實際上是利用實現一個具體的代理類來調用業務類。代理類持有了一個業務類的引用。
更概況地說,JDK 靜態代理體現的是一種設計模式。
缺點很明顯,代碼冗余,難以維護。
這里以 借書 和 還書 這兩個行為來作為一個示例:
編寫一個 BookService 接口:
public interface BookService { boolean borrow(String id, String userName); boolean reBack(String id, String userName); }
然后實現這個接口:
public class BookServiceImpl implements BookService { @Override public boolean borrow(String id, String userName) { System.out.println(userName + " 借書:" + id); return true; } @Override public boolean reBack(String id, String userName) { System.out.println(userName + " 還書:" + id); return true; } }
下面我們來編寫 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("檢查權限"); return true; } private void addLog() { System.out.println("操作完成"); } }
編寫一個測試類:
public class MainTest { public static void main(String[] args) { BookProxy proxy = new BookProxy(new BookServiceImpl()); proxy.borrow("123", "eknown"); proxy.reBack("234", "java"); } }
這里我們可以看到,JDK 靜態代理就是說在原來的實現類上套一層 代理。它好像是體現了代理模式,但實際上并沒有帶來太多的好處。代碼相當冗余,也不利于維護。
真正體現代理模式好處的還是動態代理,下面我們來看看動態代理的原理。
三、動態代理
動態代理是程序運行時,由 JVM 根據反射等機制動態生成代理類的。
也就是說,程序運行前,我們僅僅定義了代理的規則,而不知道代理類具體長什么樣,這不像上面的靜態代理里,我們完整地定義了代理對象。
3.1 JDK 動態代理
JDK 動態代理是基于接口的。
我們可以通過實現 InvocationHandler
接口來手動創建一個 JDK 代理類。
首先需要定義一個接口,讓業務類和代理類都實現這個接口。
然后編寫一個 InvocationHandler 接口的實現類:
public class BookProxy implements InvocationHandler { // 被該代理類處理的業務類 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()) { // 調用實際的 method,參數是 接口 + 參數 res = method.invoke(bookService, args); } addLog(); return res; } private boolean check() { System.out.println("檢查權限"); return true; } private void addLog() { System.out.println("操作完成"); } }
測試:
public class MainTest { public static void main(String[] args) { // 創建被代理的實際業務類 BookServiceImpl bookServiceImpl = new BookServiceImpl(); ClassLoader classLoader = bookServiceImpl.getClass().getClassLoader(); // 獲取所有的接口方法 Class[] interfaces = bookServiceImpl.getClass().getInterfaces(); // 構造 Handler InvocationHandler invocationHandler = new BookProxy(bookServiceImpl); // 創建代理 Object obj = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); BookService bookService = (BookService) obj; bookService.borrow("abc", "eknown"); bookService.reBack("c23", "py"); } }
3.2 CGLIB 動態代理
CGLIB 代理的原理是:讓代理類繼承業務類(也就自動擁有了業務類的所有非 final 的 public 方法)
我們這里手動編寫一個 CGLIB 的代理試試看。
首先我們有一個 BookServiceImpl 業務類,這個業務類可以實現接口,也可以就是單純的一個業務類。
然后我們定義一個 BookCglibProxy 類:
public class BookCglibProxy implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { check(); // 調用實際的 method Object obj = methodProxy.invokeSuper(o, objects); addLog(); return obj; } private boolean check() { System.out.println("檢查權限"); return true; } private void addLog() { System.out.println("操作完成"); } }
測試類:
public class CglibTest { public static void main(String[] args) { BookServiceImpl bookServiceImpl = new BookServiceImpl(); BookCglibProxy proxy = new BookCglibProxy(); // cjlib 中的增強器,用于創建動態代理(被代理類的子類) Enhancer enhancer = new Enhancer(); // 設置要被代理的類 enhancer.setSuperclass(bookServiceImpl.getClass()); // 設置回調 enhancer.setCallback(proxy); // 強轉成父類 BookServiceImpl proxyResult = (BookServiceImpl) enhancer.create(); proxyResult.borrow("12333", "ye"); proxyResult.reBack("123", "fe"); } }
在第一節我們提到過 Spring AOP 是基于 JDK 動態代理和 CGLIB 動態代理的。下面我們來 Spring AOP 的一些基本案例。
四、Spring AOP 實例
AOP 中一些概念詞匯,通過這些詞匯,我們可以對 AOP 有更高一層的抽象。
Spring AOP 有兩種實現方式:基于 XML 或基于注解。更流行、更方便的是后者。(阿 sir,不會還有人用 XML 來做 Bean 的配置文件吧?)
4.1 基于 XML 的實例
首先定義一下接口和實現類(沒有注解的!)。再編寫一個代理類:
這里的代理類方法以 JoinPoint 為參數即可:
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("檢查用戶權限..."); } public void saveLog(JoinPoint point) { System.out.println("-----after-----"); Object[] args = point.getArgs(); for (Object arg : args) { System.out.println(arg); } System.out.println("請求完畢,記錄日志..."); } }
然后編寫 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> <!-- 這是定義一個切面,切面是切點和通知的集合--> <aop:aspect id="do" ref="bookAspect"> <!-- 定義切點 ,后面是 expression 語言,表示包括該接口中定義的所有方法都會被執行 --> <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>
運行測試:
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 現在已經很少使用了。下面我們來看看如何基于注解使用 Spring AOP
4.2 基于注解的實例
這里以一個使用 SpringBoot 框架的 Web 項目作為簡單的實例。
首先創建一個 SpringBoot 項目,寫好 Controller、Service、DAO 層的基本類。(示例源碼中沒有使用 Mybatis 等持久層框架,而是用 Map 來模擬數據的存取)
下面我們針對 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() 獲取到切面對應的 bean //Object target = point.getTarget(); //UserService userService = (UserService) target; //List<User> userList = userService.findAll(); System.out.println("請求完畢,記錄日志..." + 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 三個注解,value 中使用切點表達式,分別匹配了 UserService 接口的所有方法和單個 save 方法。
我們還可以通過切點表達式匹配自定義的注解,比如實現一個 UserMonitor 注解,然后定義其切點方法:
public @interface UserMonitor { String value() default ""; int roleLimit() default 0; }
切點:
@Around("@annotation(com.example.springaopdemo.boot.UserMonitor)") public Object userRolePointCut(ProceedingJoinPoint point) { System.out.println("檢查用戶權限..."); // 獲取參數 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(); } // 獲取方法上的該注解,之后可以根據注解中的值進行一些操作,比如判定是否具有權限 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 與代理的概念與使用方法了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。