動態代理與AOP
代理模式
代理模式給某一個目標對象(target)提供代理對象(proxy),並由代理對象控制對target對象的引用。
模式圖:
代理模式中的角色有:
- 抽象對象角色(AbstractObject):聲明了目標對象和代理對象的共同接口,這樣依賴在任何可以使用目標對象的地方都可以使用代理對象。
- 目標對象角色(RealObject):定義了代理對象所代表的目標對象。
- 代理對象角色(ProxyObject):代理對象內部含有目標對象的引用,從而可以在任何時候操作目標對象;代理對象提供一個與目標對象相同的接口,以便可以在任何時候替代目標對象。代理對象通常在客戶端調用傳遞給目標對象之前或者之后,執行某個操作,而不是單純的將調用傳遞給目標對象。
public abstract class AbstractObject { /** * 定義操作 */ public abstract void operation(); }
目標對象角色
public class RealObject extends AbstractObject { public void operation() { System.out.println("Do Something!"); } }
代理對象角色
public class ProxyObject extends AbstractObject { RealObject realObject = new RealObject(); public void operation() { //在調用目標對象之前,完成一些操作 System.out.println("Before Do Something"); realObject.operation(); //在調用目標對象之后,完成一些操作 System.out.println("After Do Something"); } }
客戶端
public class Client { public static void main(String[] args) { AbstractObject abstractObject = new ProxyObject(); abstractObject.operation(); } }
按照代理類的創建時期,可分為靜態代理和動態代理:
- 靜態:由程序員創建代理類或特定工具自動生成源代碼再對其編譯。在程序運行前代理類的.class文件就已經存在了。
- 動態:在程序運行時運用反射機制動態創建而成。
靜態代理
如下面的例子:
1 public interface Flyable { 2 void fly(long ms); 3 } 4 5 6 public class Bird implements Flyable { 7 8 @Override 9 public void fly(long ms) { 10 System.out.println("bird is flying!"); 11 try { 12 Thread.sleep(ms); 13 } catch (Exception e) { 14 15 } 16 } 17 } 18 19 public class Kite implements Flyable { 20 21 @Override 22 public void fly(long ms) { 23 System.out.println("kite is flying!"); 24 try { 25 Thread.sleep(ms); 26 } catch (Exception e) { 27 28 } 29 } 30 } 31 32 public class StaticProxy implements Flyable { 33 private Flyable flyable; 34 35 public StaticProxy(Flyable flyable) { 36 this.flyable = flyable; 37 } 38 39 @Override 40 public void fly(long ms) { 41 try { 42 System.out.println("before invoke "); 43 long begin = System.currentTimeMillis(); 44 flyable.fly(ms); 45 long end = System.currentTimeMillis(); 46 System.out.println("after invoke elpased " + (end - begin)); 47 } catch (Exception e) { 48 e.printStackTrace(); 49 System.out.println("invoke failed!"); 50 throw e; 51 } 52 } 53 } 54 public static void main(String[] args) { 55 StaticProxy staticProxyBird = new StaticProxy(new Bird()); 56 staticProxyBird.fly(100); 57 58 StaticProxy staticProxyKite = new StaticProxy(new Kite()); 59 staticProxyKite.fly(200); 60 }
可見,靜態代理可以做到在不修改目標對象的前提下,拓展目標對象的功能。但靜態代理有2個缺點:
1)代理類和委托類實現了相同的接口,代理類通過委托類實現了相同的方法。這樣就出現了大量的代碼重復。如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。
2)代理對象只服務於一種類型的對象,如果要服務多類型的對象。勢必要為每一種對象都進行代理,靜態代理在程序規模稍大時就無法勝任了。如上的代碼是只為Flyable類的訪問提供了代理,但是如果還要為其他類提供代理的話,就需要我們再次添加代理類。
JDK代理
在動態代理中,Proxy代理類在編譯期是不存在的,而是在程序運行時被動態生成的,因為有了反射,可以根據傳入的參數,生成你想要的代理(如你想代理A就代理A,想代理B就代理B),實現原理就是在生成Proxy的時候你需要傳入被代理類的所有接口(如果沒有接口是另一種方式,下文會提),反射機制會根據你傳入的所有接口,幫你生成一個也實現這些接口的代理類出來。之后,代理對象每調用一個方法,都會把這個請求轉交給InvocationHandler來執行,而在InvocationHandler里則通過反射機制,繼續轉發請求給真正的目標對象,最后由目標對象來返回結果。
動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。而且動態代理的應用使我們的類職責更加單一,復用性更強。
1 public class DynamicProxy implements InvocationHandler { 2 3 private Object targetObject; 4 5 public Object newProxyInstance(Object targetObject) { 6 this.targetObject = targetObject; 7 return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this); 8 } 9 10 @Override 11 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 12 try { 13 System.out.println("before invoke "); 14 long begin = System.currentTimeMillis(); 15 proxy = method.invoke(targetObject, args); 16 long end = System.currentTimeMillis(); 17 System.out.println("after invoke elpased " + (end - begin)); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 System.out.println("invoke failed!"); 21 throw e; 22 } 23 24 return proxy; 25 } 26 } 27 28 public static void main() { 29 30 DynamicProxy dynamicProxy = new DynamicProxy(); 31 Flyable bird = (Flyable) dynamicProxy.newProxyInstance(new Bird()); 32 bird.fly(100); 33 34 Flyable kite = (Flyable) dynamicProxy.newProxyInstance(new Kite()); 35 kite.fly(200); 36 }
上面的例子中,動態代理除了接受Flyable類型的目標對象,還可以接受任何其他類型的對象;也不管目標對象實現的接口有多少方法,都可以被代理。
從上面的代碼可以看出,動態代理對象不需要實現目標對象接口,但是目標對象一定要實現接口,否則不能使用動態代理。
CGLIB代理
上面的靜態代理和JDK代理模式都需要目標對象是一個實現了接口的目標對象,但是有的時候,目標對象可能只是一個單獨的對象,並沒有實現任何的接口,這個時候,我們就可以使用目標對象子類的方式實現代理,這種代理方式就是:Cglib代理,也叫做子類代理,它是在內存中構件一個子類對象,從而實現對目標對象的功能拓展。
Cglib是強大的高性能的代碼生成包,它可以在運行期間拓展Java類與實現Java接口。它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception
(攔截)。
Cglib包的底層是通過使用一個小而快的字節碼處理框架ASM來轉換字節碼並生成新的類,不鼓勵直接只使用ASM,因為它要求你必須對JVM內部結構,包括class文件的格式和指令集都很熟悉。
1 public class Plane { 2 3 public void fly(long ms) { 4 System.out.println("plane is flying!"); 5 try { 6 Thread.sleep(ms); 7 } catch (Exception e) { 8 9 } 10 } 11 } 12 13 public class CglibProxy implements MethodInterceptor { 14 private Object target; 15 16 public CglibProxy(Object target) { 17 this.target = target; 18 } 19 20 public Object getProxyInstance() { 21 //1. 實例化工具類 22 Enhancer en = new Enhancer(); 23 //2. 設置父類對象 24 en.setSuperclass(this.target.getClass()); 25 //3. 設置回調函數 26 en.setCallback(this); 27 //4. 創建子類,也就是代理對象 28 return en.create(); 29 } 30 31 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 32 System.out.println("before invoke "); 33 long begin = System.currentTimeMillis(); 34 35 //執行目標對象的方法 36 Object returnValue = method.invoke(target, objects); 37 38 long end = System.currentTimeMillis(); 39 System.out.println("after invoke elpased " + (end - begin)); 40 41 return returnValue; 42 } 43 44 } 45 46 public static void main() { 47 CglibProxy cglibProxy = new CglibProxy(new Plane()); 48 Plane plane = (Plane) cglibProxy.getProxyInstance(); 49 plane.fly(150); 50 }
AOP
AOP(Aspect Oriented Programming,面向切面編程),像日志、安全、緩存、事務 等與業務邏輯分離的功能,可能會散布於各個業務bean,這樣的稱為 橫切關注點(cross-cutting concern)。AOP有助於橫切關注點與它們所影響的對象之間解耦。

- 切面(Aspect):通知+切點,即它是什么,在何時、何處完成其功能;
- 切點(Pointcut):匹配通知所要織入的一個或多個連接點(where),通常使用明確的(或正則匹配的)類和方法名稱定義切點。
- 靜態方法切點,
- 動態方法切點,
- 注解切點,
- 表達式切點,
- 流程切點,
- 復合切點,
- 通知(Advice):定義了切面的工作和時機,也就是要做什么(what),什么時候做(when)。
- 前置通知(@Before):在目標方法被調用之前調用通知;
- 后置通知(@After):在目標方法被調用之后調用通知;
- 返回通知(@After-returning):在目標方法成功執行之后調用通知;
- 異常通知(@After-throwing):在目標方法拋出異常后調用通知;
- 環繞通知(@Around):目標方法調用之前、之后執行自定義的行為;
- 連接點(joint point):允許使用通知的地方,這個點可以是調用方法時、拋出異常時、甚至修改一個字段時。
- 引入(Introduction):向現有的類添加新方法或屬性;
- 織入(Weaving):把切面應用到目標對象,並創建新的代理對象的過程。
AOP的實現原理是基於動態代理。在Spring的AOP編程中:
- 如果加入容器的目標對象有實現接口,就使用JDK代理
- 如果目標對象沒有實現接口,就使用Cglib代理。
AOP除了有Spring AOP實現外,還有著名的AOP實現者:AspectJ。
- AspectJ是語言級別的AOP實現,擴展了Java語言,定義了AOP語法,能夠在編譯期提供橫切代碼的織入,所以它有專門的編譯器用來生成遵守Java字節碼規范的Class文件;
- Spring AOP本質上底層還是動態代理,所以Spring AOP是不需要有專門的編輯器的;
Spring AOP
Spring在新版本中對AOP功能進行了增強,體現在這么幾個方面:
- 在XML配置文件中為AOP提供了aop命名空間
- 增加了AspectJ切點表達式語言的支持
- 可以無縫地集成AspectJ
先看一個例子, 如何使用 引介切面(Introduction Advisor)為一個現有對象添加任何接口的實現:
public interface Waiter { // 向客人打招呼 void greetTo(String clientName); // 服務 void serveTo(String clientName); } public class NaiveWaiter implements Waiter { public void greetTo(String clientName) { System.out.println("NaiveWaiter:greet to " + clientName + "..."); } public void serveTo(String clientName) { System.out.println("NaiveWaiter:serving " + clientName + "..."); } }
public interface Seller { // 賣東西 int sell(String goods, String clientName); } public class SmartSeller implements Seller { // 賣東西 public int sell(String goods, String clientName) { System.out.println("SmartSeller: sell " + goods + " to " + clientName + "..."); return 100; } }
如上示例代碼,有一個服務員的接口,還有一個售貨員的接口,現在想做的就是:想這個服務員可以充當售貨員的角色,可以賣東西!
我們的引介切面具體是這樣干的:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclareParents; @Aspect public class EnableSellerAspect { @DeclareParents(value = "com.example.demo.NaiveWaiter", // 切點(目標類) defaultImpl = SmartSeller.class) // 增強類 public Seller seller; // 增強類接口 }
切面技術將SmartSeller融合到NaiveWaiter中,這樣NaiveWaiter就實現了Seller接口!!!
PS:上面使用@Aspect注解需要引入如下依賴
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
beans.xml 也比較簡單
<?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.0.xsd http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <aop:aspectj-autoproxy/> <bean id="waiter" class="com.example.demo.NaiveWaiter"/> <bean class="com.example.demo.EnableSellerAspect"/> </beans>
測試一下:
public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); Waiter waiter = (Waiter) ctx.getBean("waiter"); // 調用服務員原有的方法 waiter.greetTo("Java3y"); waiter.serveTo("Java3y"); // 通過引介/引入切面已經將waiter服務員實現了Seller接口,所以可以強制轉換 Seller seller = (Seller) waiter; seller.sell("水軍", "Java3y"); } }
當引入接口方法被調用時,代理對象會把此調用委托給實現了新接口的某個其他對象。實際上,一個Bean的實現被拆分到多個類中
引介切面用代理的方式為某個對象實現接口,從而能夠使用該接口下的方法。這種方式是非侵入式的。
上面是使用注解方式,再看看下使用XML配置的方式,例如:
<bean id="testBeforeAdvice" class="com.example.demo.TestBeforeAdvice"/> <bean id="waiter" class="com.example.demo.NaiveWaiter"/> <bean class="com.example.demo.EnableSellerAspect"/> <aop:config proxy-target-class="true"> <aop:advisor advice-ref="testBeforeAdvice" pointcut="execution(* com..*.Waiter.greetTo(..))"/> </aop:config>
前置增強方法實現
public class TestBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("before prepared..."); System.out.println("args[0]: " + args[0]); System.out.println("before finish."); } }
如此,當調用 waiter.greetTo("Java3y") 方法時,會先調用before通知。
更多AOP配置元素:
配置元素 | 用途 |
<aop:advisor> | 定義AOP通知器 |
<aop:after> | 定義AOP后置通知 |
<aop:after-returning> | 定義AOP返回通知 |
<aop:after-throwing> | 定義AOP異常通知 |
<aop:around> | 定義AOP環繞通知 |
<aop:aspect> | 定義一個切面 |
<aop:before> | 定義AOP前置通知 |
<aop:config> | 頂層AOP配置元素,大多數的<aop:*>元素必須包含在<aop:config>元素內 |
<aop:aspectj-autoproxy> | 啟用@AspectJ注解驅動的切面 |
<aop:declare-parents> | 以透明的方式為被通知的對象引入額外的接口 |
<aop:pointcut> | 定義一個切點 |