走進Spring中AOP的世界(一)思想篇

前言

再寫完《走進Spring中Bean的世界》,寫這篇文章時,本來想法是把AOP的思想、原理以及源碼通通囊入其中,最後發現會和上篇文章同樣,讀起來難以專一。因此仍是本着跳出看全景,轉進去看本質的原則,將AOP的文章分爲三部分,分別爲思想篇、源碼篇、應用篇。html

你能夠了解到

  • Java程序在JVM中的運行流程
  • 面向切面編程思想
  • 代理模式的應用
  • Spring AOP的工做原理

Java程序在JVM中的運行流程

  • 先來了解下虛擬機棧(JVM stack)

    虛擬棧是一個後入先出(LIFO)棧。每個線程建立時,JVM會爲這個線程建立一個私有的虛擬機棧,當線程調用某個對象的方法時,JVM會相應地建立一個棧幀(Stack Frame)放到虛擬機中,用來表示某個方法的調用。線程對方法的調用就對應一個棧幀的入棧和出棧的過程spring

  • 來看段代碼瞭解虛擬機棧
    public class TestMethod {
            public void method1() {
                StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            }
            public static void main(String[] args) {
                TestMethod target = new TestMethod();
                target.method1();
            }
        }
    複製代碼
    上面代碼咱們在method()1時,打個斷點。
    當前虛擬棧的信息能夠以下圖所示:
  • Java程序執行機制
    圖中左面是方法執行順序,對應右面是抽象的調用流程,其實程序執行機制,把方法當作鏈接點,串起來就是整個執行過程。

面向切面編程思想

上面把程序執行機制以鏈接點的概念進行了抽象,下面來了解下面向切面以此爲基準的編程思想express

  • 走進面向切面編程
    AOP將每個方法調用抽象成鏈接點(Join Point),鏈接點串起來的程序執行流就是整個程序的執行過程,「按需」選擇鏈接點進行切入,也就是切入點(Pointcut)。那麼切入點如何肯定呢?
  • 肯定切入點
    切入點(Pointcut)肯定,實際上是根據切入點表達式來匹配該方法(鏈接點)是否知足。

代理模式的應用

上面瞭解到AOP是對方法調用進行編程,那麼AOP如何捕獲方法的調用的?其實AOP實現的基礎是代理模式的應用。編程

  • 引入代理模式Java程序執行機制
    想訪問目標對象的方法時,其實執行的是代理對象的「攔截方法」。下面來了解下Spring中代理模式的兩種實現方式。
  • 代理模式
    • 幾大角色
      • 抽象角色(Subject):經過接口或抽象類聲明真實角色實現的業務方法
      • 真實角色(TargetSubject):實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色調用
      • 代理角色(Proxy):實現抽象角色,是真實角色的代理,經過真實角色的業務邏輯方法來實現抽象方法,並能夠附加本身的操做。

    代理模式中,由抽象角色(Subject)、真實角色(TargetSubject)、代理角色(Proxy)。其中:抽象角色(Subject)負責定義真實角色(TargetSubject)和代理角色(Proxy)應該實現的接口;真實角色(TargetSubject)來完成真正的request功能;代理角色(Proxy)負責將自身的request請求調用真實角色(TargetSubject)對應的request功能來實現業務功能,本身不真正作業務,並在調用request先後,插入代碼實現具體功能。bash

    • JDK動態代理
      • 實現:JDK動態代理會根據真實角色(TargetSubject)的全部接口列表,肯定要生成的代理類的類名,默認爲:com.sun.proxy.$ProxyXXX,而後根據須要實現的接口信息,在代碼中動態建立 該Proxy類的字節碼,將對應的字節碼轉換爲對應的class 對象,建立InvocationHandler 實例handler,用來處理Proxy全部方法調用,Proxy 的class對象 以建立的handler對象爲參數,實例化一個proxy對象。
      • 代碼實現:
        public interface Subject {
            public void request();
        }
        public class TargetSubject implements Subject {
            @Override
            public void request() {
                System.out.println("TargetSubject#request...");
            }
        }
        public class InvocationHandlerImpl implements InvocationHandler {
            private TargetSubject targetSubject;
            public InvocationHandlerImpl(TargetSubject targetSubject) {
                this.targetSubject = targetSubject;
            }
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("TargetSubject#request before...");
                method.invoke(targetSubject, null);
                System.out.println("TargetSubject#request after...");
                return null;
            }
        }
        public class Test001 {
            public static void main(String[] args) {
                TargetSubject targetSubject = new TargetSubject();
                // 1.獲取對應的ClassLoader
                ClassLoader classLoader = targetSubject.getClass().getClassLoader();
                // 2.獲取targetSubject 所實現的全部接口
                Class[] interfaces = targetSubject.getClass().getInterfaces();
                // 3.設置一個來自代理傳過來的方法調用請求處理器,處理全部的代理對象上的方法調用
                InvocationHandler handler = new InvocationHandlerImpl(targetSubject);
        	/**
                 * 4.根據上面提供的信息,建立代理對象 在這個過程當中
                 *  a.JDK會經過根據傳入的參數信息動態地在內存中建立和.class 文件等同的字節碼。
                 *  b.而後根據相應的字節碼轉換成對應的class。
                 *  而後調用newInstance()建立實例。
        	 */
                Subject proxy = (Subject)Proxy.newProxyInstance(classLoader, interfaces, handler);
                proxy.request();
            }
        }
        複製代碼

      JDK動態代理提供的生成動態代理類有個特色,真實角色(TargetSubject)必須有實現的定義接口(Subject),而且只能代理該接口定義的方法,因此當某個類沒有實現接口,那麼這個類就不能使用動態代理了。那該如何去解決這種狀況呢?請看cglib代理。ide

    • cglib代理
      • 實現:cglib(Code Generation Library),是一個強大的,高性能,高質量的Code生成類庫,它能夠在運行期擴展Java類與實現Java接口。它經過查找類上的全部非final的public類型的方法定義,將這些方法的定義轉換成字節碼,將組成的字節碼轉換成相應的代理的class對象,實現 MethodInterceptor接口,用來處理對代理類上全部方法的請求(這個接口和JDK動態代理InvocationHandler的功能和角色是同樣的)。
      • 代碼實現
        public class MethodInterceptorImpl implements MethodInterceptor {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("TargetSubject#request before...");
                methodProxy.invokeSuper(o, args);
                System.out.println("TargetSubject#request after...");
                return null;
            }
        }
        public class Test002 {
            public static void main(String[] args) {
                TargetSubject targetSubject = new TargetSubject();
                MethodInterceptorImpl methodInterceptor = new MethodInterceptorImpl();
                //cglib 中增強器,用來建立動態代理
                Enhancer enhancer = new Enhancer();
                //設置要建立動態代理的類
                enhancer.setSuperclass(targetSubject.getClass());
                // 設置回調,這裏至關因而對於代理類上全部方法的調用,都會調用CallBack,而Callback則須要實行intercept()方法進行攔截
                enhancer.setCallback(methodInterceptor);
                TargetSubject proxy =(TargetSubject)enhancer.create();
                proxy.request();
            }
        }
        複製代碼

經過上面兩種代理模式的實現方式來看,有抽象角色存在時選擇JDK也能實現,cglib屬於全能型,有無抽象角色都可。Spring中對於「Bean」生成代理對象時,若是該「Bean」實現某個抽象角色(或者接口定義)則選擇JDK動態代理生成代理對象,某則選用cglib方式生成代理對象。post

Spring AOP的工做原理

  • 工做原理全景圖
    在Spring中,AOP工做原理如上圖所示,定義個切面(Aspect),在切面中編寫切入點(Pointcut)是什麼,匹配上的類,在生成Bean的時候,實際上不會生成本來的目標對象(Target Object),並且通過Spring生成的代理對象(AOP Proxy),這樣在執行目標方法(Join Point)時,其實執行的代理對象的攔截方法,而後按建議(Advice)在目標方法先後插入定製的代碼。
  • 全景圖中重要角色分析:
    • Aspect: A modularization of a concern that cuts across multiple classes。
    • Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
    • Advice: Action taken by an aspect at a particular join point. Different types of advice include 「around」, 「before」 and 「after」 advice. (Advice types are discussed later.)
    • Pointcut: A predicate that matches join points.Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name)
    • Target object: An object being advised by one or more aspects.
    • AOP proxy: An object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.

參考文章

相關文章
相關標籤/搜索