關於Spring AOP與IOC的個人思考

衣舞晨風發表於2016-12-24

在閱讀本文前,強烈建議閱讀一下(有分析,有demo):
Java JDK 動態代理(AOP)使用及實現原理分析

AOP是Spring提供的關鍵特性之一。AOP即面向切面程式設計,是OOP程式設計的有效補充。使用AOP技術,可以將一些系統性相關的程式設計工作,獨立提取出來,獨立實現,然後通過切面切入進系統。從而避免了在業務邏輯的程式碼中混入很多的系統相關的邏輯——比如許可權管理,事物管理,日誌記錄等等。這些系統性的程式設計工作都可以獨立編碼實現,然後通過AOP技術切入進系統即可。從而達到了將不同的關注點分離出來的效果。本文深入剖析Spring的AOP的原理。

一、AOP相關的概念

1)Aspect:切面,切入系統的一個切面。比如事務管理是一個切面,許可權管理也是一個切面;

2)Join point:連線點,也就是可以進行橫向切入的位置;

3)Advice:通知,切面在某個連線點執行的操作(分為:Before advice,After returning advice,After throwing advice,After (finally) advice,Around advice);

4)Pointcut:切點,符合切點表示式的連線點,也就是真正被切入的地方;

這一部分的應用可以參考:
Spring MVC AOP通過註解方式攔截Controller等實現日誌管理

二、AOP 的實現原理

AOP分為靜態AOP和動態AOP。靜態AOP是指AspectJ實現的AOP,他是將切面程式碼直接編譯到Java類檔案中。動態AOP是指將切面程式碼進行動態織入實現的AOP。Spring的AOP為動態AOP,實現的技術為:JDK提供的動態代理技術 和 CGLIB(動態位元組碼增強技術)。儘管實現技術不一樣,但都是基於代理模式,都是生成一個代理物件。

1、JDK動態代理

JDK部分解析參考:

Java JDK 動態代理(AOP)使用及實現原理分析

2、CGLIB(code generate libary)

位元組碼生成技術實現AOP,其實就是繼承被代理物件,然後Override需要被代理的方法,在覆蓋該方法時,自然是可以插入我們自己的程式碼的。
因為需要Override被代理物件的方法,所以自然CGLIB技術實現AOP時,就必須要求需要被代理的方法不能是final方法,因為final方法不能被子類覆蓋。

package net.aazj.aop;

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CGProxy implements MethodInterceptor{
    private Object target;    // 被代理物件
    public CGProxy(Object target){
        this.target = target;
    }
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy proxy) throws Throwable {
        System.out.println("do sth before....");
        Object result = proxy.invokeSuper(arg0, arg2);
        System.out.println("do sth after....");
        return result;
    }
    public Object getProxyObject() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());    // 設定父類
        // 設定回撥
        enhancer.setCallback(this);    // 在呼叫父類方法時,回撥 this.intercept()
        // 建立代理物件
        return enhancer.create();
    }
}
public class CGProxyTest {
    public static void main(String[] args){
        Object proxyedObject = new UserServiceImpl();    // 被代理的物件
        CGProxy cgProxy = new CGProxy(proxyedObject);
        UserService proxyObject = (UserService) cgProxy.getProxyObject();
        proxyObject.getUser(1);
        proxyObject.addUser(new User());
    }
}

輸出結果:

do sth before....
getUser from database.
do sth after....
do sth before....
add user into database.
do sth after....

它的原理是生成一個父類enhancer.setSuperclass(this.target.getClass())的子類enhancer.create(),然後對父類的方法進行攔截enhancer.setCallback(this). 對父類的方法進行覆蓋,所以父類方法不能是final的。

三、思考

從以上兩種代理方式可以看出,實現AOP的關鍵是:動態代理,即將需要用的介面、類再包裝一層,通過動態修改位元組碼檔案實現各種攔截與通知。

注意,兩者都需要:要代理真實物件的例項。

比如: 在Spring MVC的Controller層一般@Autowired是Service介面,但帶有@Service標識的卻是實現Service介面的實體類,這樣對於JDK動態代理來說已經足以生成代理類了(其實,不過是cglib還是jdk的動態代理,你直接@Autowired Service介面實現類,也是可以注入成功的,但不如注入Service介面靈活),大家在跟蹤程式碼的時候可以看一下Spring注入的bean真正的型別,你就可以發現它是代理生成的例項。
比如這種:
這裡寫圖片描述

帶有註解標識的介面或者在Spring.XML中配置的bean會在Spring初始化的時候,被Spring通過反射載入例項化到Spring容器中,寫過CS模式的朋友應該知道,在Application執行過程中一般都會有一個應用上下文Context,將一些系統資訊放在裡面,比如一些登入資訊、WCF連線例項等。這些資訊在系統的任何地方都可以取到(其實就是一些頂級變數集合,生命週期最長的一些傢伙)。

換個角度想一下,如果我們在Application初始化的時候,用反射(獲取要代理物件的例項)和動態代理獲取有註解標識或者在xml中配置bean的例項,並放到應用上下文Context中,在需要的地方都能取到,這不就是一個簡單版的Spring 容器嗎?

生命週期的資料可以參考:
.Net 垃圾回收和大物件處理
深入理解JVM讀書筆記二: 垃圾收集器與記憶體分配策略

本文開頭及CGLIB部分參考:Spring AOP 深入剖析

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

個人微信公眾號:
這裡寫圖片描述

相關文章