1.概述
Spring核心概念為IoC和AOP。
2.Spring IoC底層原理
要了解控制反轉,需要先了解軟件設計的一個重要思想:依賴倒置原則。
什么是依賴倒置原則?假設我們設計一輛汽車:先設計輪子,然后根據輪子大小設計底盤,接着根據底盤設計車身,最后根據車身設計好整個汽車。這里就出現了一個“依賴”關系:汽車依賴車身,車身依賴底盤,底盤依賴輪子。但這種設計維護性很低。
換一種思路:我們先設計汽車的大概樣子,然后根據汽車的樣子來設計車身,根據車身來設計底盤,最后根據底盤來設計輪子。這時候,依賴關系就倒置過來了:輪子依賴底盤,底盤依賴車身,車身依賴汽車。
這時候,上司再說要改動輪子的設計,我們就只需要改動輪子的設計,而不需要動底盤、車身、汽車的設計了。
這就是依賴倒置原則——把原本的高層建築依賴底層建築“倒置”過來,變成底層建築依賴高層建築。高層建築決定需要什么,底層去實現這樣的需求,但是高層並不用管底層是怎么實現的。
控制反轉就是依賴倒置原則的一種代碼設計的思路。具體采用的方法就是所謂的依賴注入。這幾種概念的關系大概如下:
為了理解這幾個概念,我們還是用上面汽車的例子。只不過這次換成代碼,我們先定義四個Class,車、車身、底盤、輪胎。然后初始化這輛車,最后跑這輛車。代碼結構如下:
這樣,就相當於上面第一個例子,上層建築依賴下層建築——每一個類的構造函數都直接調用了底層代碼的構造函數。假設我們需要改動一下輪胎(Tire)類,把它的尺寸變成動態的,而不是一直都是30。我們需要像上面這樣改。
由於我們修改了輪胎的定義,為了讓整個程序正常運行,我們需要做以下改動:
由此我們可以看到,僅僅是為了修改輪胎的構造函數,這種設計卻需要修改整個上層所有類的構造函數!在軟件工程中,這樣的設計幾乎是不可維護的——在實際工程項目中,有的類可能會是幾千個類的底層,如果每次修改這個類,我們都要修改所有以它作為依賴的類,那軟件的維護成本就太高了。
所以我們需要進行控制反轉(IoC),即上層控制下層,而不是下層控制着上層。我們用依賴注入(Dependency Injection)這種方式來實現控制反轉。所謂依賴注入,就是把底層類作為參數傳入上層類,實現上層類對下層類的“控制”。這里我們用構造方法傳遞的依賴注入方式重新寫車類的定義:
這里我只需要修改輪胎類就行了,不用修改其他任何上層類。這顯然是更容易維護的代碼。
這里我們采用的構造函數傳入的方式進行的依賴注入。其實還有另外兩種方法:Setter傳遞和接口傳遞,核心思路都是一樣的,都是為了實現控制反轉。
那什么是控制反轉容器(IoC Container)呢?其實上面的例子中,對車類進行初始化的那段代碼發生的地方,就是控制反轉容器。
因為采用了依賴注入,在初始化的過程中就不可避免的會寫大量的new。這里IoC容器就解決了這個問題。這個容器可以自動對你的代碼進行初始化,你只需要維護一個Configuration(可以是xml,也可以是一段代碼),而不用每次初始化一輛車都要親手去寫那一大段初始化的代碼。這是引入IoC Container的第一個好處。
IoC Container的第二個好處是:我們在創建實例的時候不需要了解其中的細節。在上面的例子中,我們自己手動創建一個車instance時候,是從底層往上層new的:
這個過程中,我們需要了解整個Car/Framework/Bottom/Tire類構造函數是怎么定義的,才能一步一步new注入。
而IoC Container在進行這個工作的時候是反過來的,它先從最上層開始往下找依賴關系,到達最底層之后再往上一步一步new。
實際項目中,有Service Class可能是十年前寫的,有幾百個類作為它的底層。假設我們新寫一個API需要實例化這個Service,我們總不可能回去搞清楚這幾百個類的構造函數吧。IoC Container的這個特性就很完美的解決了這類問題——因為這個架構要求你在寫class的時候需要寫相應的Config文件,所以你要初始化很久以前的Service類的時候,前人都已經寫好了Config文件,你直接在需要用的地方注入這個Service就可以了。這大大增加了項目的可維護性且降低了開發難度。
3.Spring AOP底層原理
Spring AOP全稱為Spring Aspect-Oriented Programming,即面向切面編程,是運行時織入的,那么運行時織入到底是怎么實現的呢?答案就是代理對象。代理對象又可以分為靜態代理和動態代理。
- 靜態代理:由程序員創建或特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。
- 動態代理:在程序運行時,運用反射機制動態創建而成。
靜態代理的每一個代理類只能為一個接口服務,這樣一來程序開發中必然會產生過多的代理,而且,所有的代理操作除了調用的方法不一樣之外,其他的操作都一樣,則此時肯定是重復代碼。解決這一問題最好的做法是可以通過一個代理類完成全部的代理功能,那么此時就必須使用動態代理完成。
動態代理與靜態代理對照的是動態代理類,動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。動態代理不僅簡化了編程工作,而且提高了軟件系統的可擴展性,因為Java反射機制可以生成任意類型的動態代理類。java.lang.reflect包中的Proxy類和InvocationHandler接口提供了生成動態代理類的能力。
Spring AOP使用動態代理技術在運行期間織入增強的代碼,主要有兩種代理機制:基於JDK的動態代理;基於cglib的動態代理。JDK本身只提供接口的代理,而不支持類的代理。
3.1 代理模式
首先我們來了解代理模式的基本定義。
代理模式的英文叫做Proxy,就是一個人或者一個機構代表另一個人或者另一個機構采取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之間起到中介作用。
//Subject接口 public interface Subject { void request(); }
//目標對象RealSubject public class RealSubject implements Subject{ @Override public void request() { System.out.println("real subject execute request"); } }
//Proxy代理類 public class Proxy implements Subject{ private RealSubject realSubject; public Proxy(RealSubject realSubject) { this.realSubject = realSubject; } @Override public void request() { System.out.println("before"); try{ realSubject.request(); }catch (Exception e){ System.out.println("ex:"+e.getMessage()); throw e; }finally { System.out.println("after"); } } }
//Client客戶端 public class Client { public static void main(String[] args){ Subject subject = new Proxy(new RealSubject()); subject.request(); } }
這樣就會輸出:
before
real subject execute request
after
是不是達到和AOP一樣的效果了呢?AOP將真正要執行的代碼交給RealSubject來執行,然后通過proxy來對額外的代碼進行織入。
3.2 動態代理
上面的案例就是一個典型的靜態代理的案例,這樣有什么缺點呢?
如果當你要代理的方法越多時,你需要重復的邏輯就越多,假設你的目標類有100個方法,那么你的代理類就需要對這100個方法進行委托,但是又可能他們前后需要執行的邏輯時一樣的,這樣就會產生很多冗余。
這樣,就有個更好的動態代理的方法出現了。
- 動態代理也分為兩類:基於接口的代理和基於繼承的代理
- 兩類實現的代表是:JDK代理 與 CGlib代理
3.2.1 JDK代理模式
JDK動態代理主要涉及java.lang.reflect包下的兩個類:Proxy類和InvocationHandler接口。
JDK代理實現的三個要點:
① 通過Java.lang.reflect.Proxy類來動態生成代理類;
② 代理類要實現InvocationHandler接口;
③ JDK代理只能基於接口進行動態代理的;
//客戶端Client public class Client { /* *newProxyInstance方法參數解析 *ClassLoader loader:類加載器 *Class<?>[] interfaces:得到全部的接口 *InvocationHandler h:得到InvocationHandler接口的子類實例 */ public static void main(String[] args){ Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Subject.class},new JdkProxySubject(new RealSubject())); subject.hello(); subject.request(); } }
//代理類JdkProxySubject public class JdkProxySubject implements InvocationHandler{ private RealSubject realSubject; public JdkProxySubject(RealSubject realSubject) { this.realSubject = realSubject; } /* *invoke方法方法參數解析 *Object proxy:指被代理的對象。 *Method method:要調用的方法 *Object[] args:方法調用時所需要的參數 *InvocationHandler接口的子類可以看成代理的最終操作類。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before"); Object result = null; try{ //利用反射動態的來反射方法,這就是動態代理和靜態代理的區別 result = method.invoke(realSubject,args); }catch (Exception e){ System.out.println("ex:"+e.getMessage()); throw e; }finally { System.out.println("after"); } return result; } }
//目標對象RealSubject public class RealSubject implements Subject{ @Override public void request() { System.out.println("real subject execute request"); } @Override public void hello() { System.out.println("hello"); } }
//subject接口,這個是jdk動態代理必須的前提。 public interface Subject { void request(); void hello(); }
輸出:
before
hello
after
before
real subject execute request
after
我們在subject接口中新增加了一個hello()方法,然后在RealSubject中對hello()方法進行實現,但是在代理類中,我們不需要再去為hello方法再去寫一個代理方法,而是通過反射調用目標對象的方法,來動態的生成代理類。
總結:
- 因為利用JdkProxySubject生成的代理類實現了接口,所以目標類中所有的方法在代理類中都有;
- 生成的代理類的所有方法都攔截了目標類的所有的方法。而攔截器中invoke方法的內容正好就是代理類的各個方法的組成體;
- 利用JDK代理方式必須有接口的存在。
3.2.2 CGLib代理模式
CGLib采用非常底層的字節碼技術,可以為一個類創建子類,並在子類中采用方法去技術攔截所有的父類方法的調用,並順勢織入橫切邏輯。
CGLib和JDK的原理類似,也是通過方法去反射調用目標對象的方法。
//客戶端 public class Client { public static void main(String[] args){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(RealSubject.class); enhancer.setCallback(new DemoMethodInterceptor()); // 此刻,realSubject不是單純的目標類,而是增強過的目標類 RealSubject realSubject = (RealSubject) enhancer.create(); realSubject.hello(); realSubject.request() } }
//代理對象 public class DemoMethodInterceptor implements MethodInterceptor{ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before in cglib"); Object result = null; try{ result = proxy.invokeSuper(obj, args); }catch (Exception e){ System.out.println("get ex:"+e.getMessage()); throw e; }finally { System.out.println("after in cglib"); } return result; } }
//目標對象RealSubject,cglib不需要定義目標類的統一接口 public class RealSubject { public void request() { System.out.println("real subject execute request"); } public void hello() { System.out.println("hello"); } }
輸出:
before in cglib
hello
after in cglib
before in cglib
real subject execute request
after in cglib
3.3 Spring兩種代理方式
若目標對象實現了接口,spring默認使用JDK的動態代理。
優點:因為有接口,所以使系統更加松耦合;
缺點:為每一個目標類創建接口;
若目標對象沒有實現任何接口,spring使用CGLib進行動態代理。
優點:因為代理類與目標類是繼承關系,所以不需要有接口的存在。
缺點:因為沒有使用接口,所以系統的耦合性沒有使用JDK的動態代理好。
若目標對象實現了接口,但是強制cglib代理,則使用cglib代理
@SpringBootApplication //強制使用cglib代理 @EnableAspectJAutoProxy(proxyTargetClass = tree) public class AopDemoApplication{ public static void main(String[] args){ SpringApplication.run(AopDemoApplication.class,args); } }
總結
【參考資料】
https://blog.csdn.net/DoUUnderstand/article/details/78865385