Spring學習(六)

AOP和OOPhtml

一、OOP:Object-Oriented Programming,面向對象程序設計,是靜態的,一旦類寫死了就不能改變了,要更改就得修改代碼從新編譯,父類類型引用指向對象來實現動態性。核心思想是將客觀存在的不一樣事物抽象成相互獨立的類,而後把與事物相關的屬性和行爲封裝到類裏,並經過繼承和多態來定義類彼此間的關係,最後經過操做類的實例來完成實際業務邏輯的功能需求。java

二、AOP:Aspect-Oriented Programming,面向切面編程,以OOP爲基礎修復自己不具有的能力具備動態語言的特色,和OOP互爲補充,Aop的最大意義是在不改變原來代碼的前提下,也不對源代碼作任何協議接口要求, 對OOP而實現了相似插件的方式,來修改源代碼,給源代碼插入新的執行代碼。核心思想是將業務邏輯中與類不相關的通用功能切面式的提取分離出來,讓多個類共享一個行爲,一旦這個行爲發生改變,沒必要修改類,而只須要修改這個行爲便可。Spring的AOP其代碼實質,即代理模式的應用。sql

三、OOP和AOP的區別編程

  • 面向目標不一樣:簡單來講OOP是面向名詞領域,AOP面向動詞領域。
  • 思想結構不一樣:OOP是縱向結構,AOP是橫向結構。
  • 注重方面不一樣:OOP注重業務邏輯單元的劃分,AOP偏重業務處理過程的某個步驟或階段。

四、OOP和AOP的聯繫:二者之間是一個相互補充和完善的關係api

五、AOP優勢:利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。數組

六、AOP應用安全

日誌記錄、事務處理、異常處理、安全控制和性能統計方面。ide

Spring中提供了面向切面編程的豐富支持,容許經過分離應用的業務邏輯與系統級服務和事務進行內聚性的開發。源碼分析

AOP的重要概念性能

一、切面 : 切點(Pointcut) + Advice【 在哪裏 、加什麼 】

二、Advice: 在 切點 選中的 鏈接點 "加入" 的 代碼 就是 Advice【肯定 加什麼 】

三、切點( Pointcut ) : 用來 篩選 鏈接點 的條件就是切點, 相似於sql語句,where條件就能夠理解爲切點【 肯定 在哪裏加 ,在那些方法裏面加入代碼

四、鏈接點( Join Point ) : 執行點 + 方位信息 全部被攔截的方法被加入了其餘的方法,經過代理的方法將想要加入的想要的代碼,加入的代碼和原有的代碼造成了鏈接點。一個方法有5個鏈接點,四個方法就有20個

  • 一個正在執行的方法 (執行點) 執行前 (方位信息)
  • 一個正在執行的方法 (執行點) 執行前、後 (方位信息)
  • 一個正在執行的方法 (執行點) 執行並返回後 (方位信息)
  • 一個正在執行的方法 (執行點) 執行拋出異常後 (方位信息)
  • 一個正在執行的方法 (執行點) 執行後 (方位信息)

五、方位信息

  • 方法執行前 ( before ) 、
  • 執行前和執行後,執行先後都有環繞,兩個結合起來纔可使用,二者之間是有聯繫的 ( around ) 、
  • 正常執行並返回後 ( after-returning ) 、
  • 方法拋出異常後 ( after-throwing ) 、
  • 方法執行後 ( after )

六、執行點:一個能夠執行的方法在執行時就是一個執行點

七、代理目標: 哪一個對象將被其它對象所代理 ( 誰將被別人代理 )  [ target ]
八、代理對象: 哪一個對象將要去代理別的對象 ( 誰將去代理別人 )  [ proxy ]

代理模式

一、什麼是代理呢?

 例如:蘋果公司的手機交由富士康工廠按照蘋果公司的要求去生產手機,富士康工廠生產的手機由京東按照蘋果公司的要求去完成銷售,京東就能夠當作是富士康的代理商(代理對象),而富士康就是代理目標。消費者在京東買手機,感受是由京東提供的手機,其實是由富士康生產的,由京東代理,這種思想就是代理思想
二、具體代碼實例

IPhone 蘋果手機類

package ecut.aop.proxy;

public class IPhone {
    
    public IPhone(){
        super();
        System.out.println( "愛瘋" );
    }

    public void charge() {
        System.out.println( "charge" );
    }

    public void call() {
        System.out.println( "call" );
    }

    public void message() {
        System.out.println( "message" );
    }

}

AppleCompany 蘋果公司接口

package ecut.aop.proxy;

public interface AppleCompany {
    //由蘋果公司去設計而後交由富士康生產
    public IPhone create() ;
    
}

FoxconnFactory 富士康工廠類

package ecut.aop.proxy;

public class FoxconnFactory implements AppleCompany {
    //須要實現蘋果公司接口
    @Override
    public IPhone create() {
        System.out.println( "富士康" );
        IPhone phone = new IPhone();
        return phone ;
    }

    @Override
    public String toString() {
        return "FoxconnFactory";
    }
    
}

JingDong 京東類

package ecut.aop.proxy;

public class JingDong implements AppleCompany {
    
    private FoxconnFactory factory ;
    
    @Override
    public IPhone create() {
        System.out.println( "京東" );
        return factory.create() ;
    }
    //經過getter , setter 方法注入FoxconnFactory
    public FoxconnFactory getFactory() {
        return factory;
    }

    public void setFactory(FoxconnFactory factory) {
        this.factory = factory;
    }

}

Main主類

package ecut.aop.proxy;

public class Main {

    public static void main(String[] args) {
        //被代理的目標target, 京東買的手機由富士康生產
        FoxconnFactory factory = new FoxconnFactory();
        //京東是富士康的代理商,代理對象proxy,代理商和代理對象都實現了蘋果公司的這個接口(前提),富士康按照蘋果公司要求生產,京東按照蘋果公司的要求買手機
        JingDong jd = new JingDong();
        
        jd.setFactory(factory);
        //至關於消費者買手機
        IPhone p = jd.create();
        
        System.out.println( p );
        
        p.call();

    }

}

先調用JingDong的create方法,實際上調用的FoxconnFactory的create方法(調用的proxy方法的時候調用target方法,表面上感受是proxy調用的實際上target完成具體實現的)。用JingDong之因此能夠代理FoxconnFactory,是由於FoxconnFactory和JingDong都實現了AppleCompany,這個是代理的前提

三、代理模式代碼的主要特色是:不改變原有類的前提下,在原有類某些方法執行先後,插入任意代碼。因此代理模式須要寫新的類對原有的類進行包裝。Struts2中的攔截器,Spring中的賴加載都是用代理模式實現

四、代理模式目前實現的方式有三種:

  • 靜態代理:須要加強原有類的哪一個方法,就須要對在代理類中包裝哪一個方法。我的理解,從功能上來講,原有類和代理類不必定要實現共同接口,可是爲了賦予代理和和被代理類之間的邏輯關係,增長程序的可讀性,可理解性,邏輯性,增長代理對象和被代理對象之間的關係,以更加符合面向對象編程是思惟,而應該實現共同接口。
  • 動態代理:使用反射機制,方法和對象都是傳入的變量,就能夠通過傳入的對象和方法而動態調用被代理對象的任何方法,jdk中提供了實現此動態代理的api,被代理類必須實現接口
  • Cglib代理:返回對象是代理對象的子類,不須要代理對象實現接口。當調用原對象方法時,實際上調用的是代理子類的方法。

JDK動態代理

一、目的:經過反射實現動態產生一個代理對象來代理富士康工廠(代理目標)
二、動態代理主要是經過Proxy類中的newProxyInstance建立一個代理對象,newProxyInstance方法須要傳三個參數ClassLoader loader,Class<?>[] interfaces,InvocationHandler h。

  • ClassLoader loader:代理目標對應的 類的 "類加載器"
  • Class<?>[] interfaces:代理目標對應的類所直接實現的接口
  • InvocationHandler h:請求指派器,將代理對象的請求派遣個代理目標

三、InvocationHandler 請求指派器的做用就是做用就是要將代理對象的請求派遣個代理目標(調用的proxy方法的時候調用target方法,表面上感受是proxy調用的實際上target完成具體實現的)。首先要經過匿名內部類實現InvocationHandler接口,並實現接口中的方法invoke( Object proxy , Method method , Object[] args )

  • Object proxy:代理對象
  • Method method:調用的方法
  • Object[] args:方法中要傳入的參數

在invoke方法中要是proxy和target產生聯繫經過method.invoke( target , args ) 來實現將代理對象的請求派遣個代理目標

四、測試代碼以下

package ecut.aop.proxy;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyTest {

    public static void main(String[] args) {
        
        // 代理目標 ( 誰將被別人代理 )
        final Object target = new FoxconnFactory();
        
        // 代理目標 的類型 : 得到 被代理的 對象的 運行時 類型,jdk代理的缺陷(前提)代理的目標類得實現接口,Spring提供了子類代理父類(Cglib)
        Class<?> targetClass = target.getClass(); 
        
        /** 用 代理目標 類型對應的類加載器 去加載 運行階段動態產生的 代理類 */
        ClassLoader loader = targetClass.getClassLoader() ; // 得到 代理目標 對應的 類的 "類加載器"
        
        /** 指示 未來產生 動態代理類 所須要實現的接口 */
        Class<?>[] interfaces = targetClass.getInterfaces() ; // 得到 代理目標 對應的類所直接實現的接口
        System.out.println( Arrays.toString( interfaces ) );
        
        /** 請求指派器 (快遞公司),調用的proxy方法的時候調用target方法,給你感受是proxy調用的實際上target實現的,是使proxy 和 target產生聯繫,InvocationHandler將請求派給target
         * method被調用的方法是誰, args 方法中的參數"借屍還魂"匿名內部類 */
        InvocationHandler handler = new InvocationHandler(){
            @Override
            //proxy對象
            public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
                //target.method(args);//被調用的是method方法;傳入的參數是args;調用的是target所引用的對象的method方法
                Object result = method.invoke( target , args ) ; // target.create();指派給target,由target調用方法
                 
                return result;
            }
        } ;
        // 代理對象 ( 誰將去代理別人 )Proxy動態代理類的父類Proxy.newProxyInstance建立一個代理對象
        Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ;
        
        System.out.println( proxy ); // proxy.toString()會被指派,經過InvocationHandler.invoke方法指派給target.toString(),所以返回的是target的tostring方法
        
        // proxy.getClass()不會被指派。代理類 : ( 在 運行階段 動態產生 ( 沒有 .java 文件、沒有 .class 文件 ,直接產生字節碼放在內存中去) )
        Class<?> proxyClass = proxy.getClass();
        
        System.out.println( proxyClass );
        
        // 全部的 由  java.lang.refrect.Proxy 產生的 動態代理類 的直接父類都是 Proxy
        System.out.println( proxyClass.getSuperclass() );
        
        // 得到 動態代理類 所實現過的 接口 ( 在 建立 代理對象時指定的數組中有哪些接口,這裏就有哪些接口 )
        System.out.println( Arrays.toString( proxyClass.getInterfaces() ) );
        
        System.out.println( "~~~構造方法~~~~~~~~~~~~" );
        
        Constructor<?>[] cons = proxyClass.getDeclaredConstructors();
        for( Constructor<?> c : cons ){
            System.out.println( c );
        }
        
        System.out.println( "~~~ 屬性 ( Field ) ~~~~~~~~~~~~" );
        //屬性都由private static修飾 ,代理對象 $Proxy0繼承了父類的Proxy類中的protected  InvocationHandler h屬性,其餘m0,m1....都與方法相對應
        Field[] fields = proxyClass.getDeclaredFields();
        for( Field c : fields ){
            System.out.println( c );
        }
        
        System.out.println( "~~~ 方法 ( Method ) ~~~~~~~~~~~~" );
        //重寫了toString,hashCode,equals方法,都由public final修飾
        Method[] methods = proxyClass.getDeclaredMethods();
        for( Method c : methods ){
            System.out.println( c );
        }
        
        
    }

}

運行結果以下:

[interface ecut.aop.proxy.AppleCompany]
FoxconnFactory
class com.sun.proxy.$Proxy0
class java.lang.reflect.Proxy
[interface ecut.aop.proxy.AppleCompany]
~~~構造方法~~~~~~~~~~~~
public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
~~~ 屬性 ( Field ) ~~~~~~~~~~~~
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m1
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m2
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m3
private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m0
~~~ 方法 ( Method ) ~~~~~~~~~~~~
public final boolean com.sun.proxy.$Proxy0.equals(java.lang.Object)
public final java.lang.String com.sun.proxy.$Proxy0.toString()
public final int com.sun.proxy.$Proxy0.hashCode()
public final ecut.aop.proxy.IPhone com.sun.proxy.$Proxy0.create()

由以上輸出能夠推測出來代理類的類名是$Proxy0,且有一個有參構造,參數是InvocationHandler類型,且有四個屬性,四個方法,還繼承了父類的Proxy類中的protected InvocationHandler h屬性,推測其餘m0,m1....都與方法相對應。$Proxy0源碼可能相似於

Proxy類

public class Proxy  {

      protected  InvocationHandler h  ; // 父類中 public 修飾的 和 protected 修飾 均可以被子類繼承

      protected  Proxy( InvocationHandler h )  {
              this.h = h ;
      }
 
}

 $Proxy0類

package com.sun.proxy ;

public class $Proxy0  extends  Proxy  implements AppleCompany {

       public $Proxy0( InvocationHandler h ){
              super( h );
       }

       private static Method m0 ;
       private static Method m1 ;
       private static Method m2 ;
       private static Method m3 ;
       
       /* ..........  */

       static {
            Class<?> c = $Proxy0.class ;
            m0 = c.getMethod( "equals" , Object.class );
            m1 = c.getMethod( "toString" );
            m2 = c.getMethod( "hashCode" );
            m3 = c.getMethod( "create" );
            
            /* ..........  */
       }

        public final boolean equals( Object another ) {
            Object[] args = new Object[]{ another  };
            return h.invoke( this , m0 , args );
        }

        public final String  toString() {
            Object[] args = new Object[ 0 ];
            return h.invoke( this , m1 , args );
        }

        public final int hashCode() {
            Object[] args = new Object[ 0 ];
            return h.invoke( this , m2 , args );
        }

        public final  IPhone create()  {
            Object[] args = new Object[ 0 ];
            return h.invoke( this , m3 , args );
        }

}

五、動態代理的實現和源碼分析

查看Proxy類的newProxyInstance方法,從生成對象方法中,咱們看到三個關鍵的地方:

  • Class<?> cl = getProxyClass0(loader, interfaces);//獲得代理類
  • final Constructor<?> cons = cl.getConstructor(constructorParams);   
  • newInstance(cons, h);//將InvocationHandler h傳入代理對象中

首先是經過Proxy.newProxyInstance( loader , interfaces , handler ) 將handler傳入了$Proxy0 中的 InvocationHandler h屬性中,而後當咱們調用方法時候其實是經過h.invoke( this , m3 , args )來實現的即調用handler裏面的invoke方法,而在handler的invoke方法中經過method.invoke( target , args )方法,最終調用的是target裏面的方法。

結合動態代理理解AOP相關概念

一、理解Advice測試案例

package ecut.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest2 {

    public static void main(String[] args) {
        
        // 代理目標 ( 誰將被別人代理 )
        final Object target = new FoxconnFactory();
        
        // 代理目標 的類型 : 得到 被代理的 對象的 運行時 類型
        Class<?> targetClass = target.getClass(); 
        
        /** 用 代理目標 類型對應的類加載器 去加載 運行階段動態產生的 代理類 */
        ClassLoader loader = targetClass.getClassLoader() ; // 得到 代理目標 對應的 類的 "類加載器"
        
        /** 指示 未來產生 動態代理類 所須要實現的接口 */
        Class<?>[] interfaces = targetClass.getInterfaces() ; // 得到 代理目標 對應的類所實現的接口
        
        /** 請求指派器  */
        InvocationHandler handler = new InvocationHandler(){
            @Override
            public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
                System.out.println( "方法[ " + method.getName() +  " ]將要執行了" ); // Advice
                Object result = method.invoke( target , args ) ; // target.create();
                System.out.println( "方法[ " + method.getName() +  " ]執行結束並返回了: " + result );  // Advice
                return result;
            }
        } ;
        
        // 代理對象 ( 誰將去代理別人 ),這四個方法執行其先後就被咱們攔截下來了 ,方法先後增長了輸出語句,沒有動方法,只是在動態代理類handler裏面增長了代碼
        //咱們所增長的代碼若是按照規範來封裝好就是Advice
        Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ;
        
        if( proxy instanceof AppleCompany ) {
            AppleCompany ac = (AppleCompany) proxy ;
            System.out.println( ac == proxy );
            
            IPhone p = ac.create();
            System.out.println( p );
            
            System.out.println( ac.toString() );
            
            System.out.println( ac.hashCode() );
            
            System.out.println( ac.equals(target) );//target.equals(target);
        }
        
    }

}

這四個方法執行其先後就被咱們攔截下來了 ,方法先後增長了輸出語句,沒有動方法,只是在動態代理類handler裏面增長了代碼 ,咱們所增長的代碼若是按照規範來封裝好就是Advice。

理解鏈接點測試案例:

package ecut.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest3 {

    public static void main(String[] args) {
        
        // 代理目標 ( 誰將被別人代理 )
        final Object target = new FoxconnFactory();
        
        Class<?> targetClass = target.getClass(); 
        
        ClassLoader loader = targetClass.getClassLoader() ;
        
        Class<?>[] interfaces = targetClass.getInterfaces() ; 
        
        InvocationHandler handler = new InvocationHandler(){
            @Override
            public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
                
                System.out.println( "方法[ " + method.getName() +  " ]將要執行了" ); // before
                
                Object result = null ;
                try{
                    long start = System.nanoTime(); // around
                    result = method.invoke( target , args ) ; // 執行點
                    long end = System.nanoTime(); // around
                    System.out.println( "執行用時: " + ( end - start ) + " 毫微妙" ); // around
                    System.out.println( "執行後並返回: " + result); // after-returing
                } catch ( Throwable t ){
                    System.out.println( "發生錯誤: " + t.getMessage()  ); // after-throwing
                }
                
                System.out.println( "方法[ " + method.getName() +  " ]執行結束" );  // after
                
                return result;
            }
        } ;
        
        Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ;
        
        if( proxy instanceof AppleCompany ) {
            AppleCompany ac = (AppleCompany) proxy ;
            System.out.println( ac == proxy );
            //經過create調用了target中create的方法
            IPhone p = ac.create();
            System.out.println( p );
            //代理對象 $Proxy0中的四個方法是可執行的,方法在執行時候就能夠視爲是一個執行點
            String s = ac.toString() ;
            System.out.println( s );
        }
        
    }

}

鏈接點 ( Join Point ) : 執行點 + 方位信息 全部被攔截的方法被加入了其餘的方法,經過代理的方法將想要加入的想要的代碼,加入的代碼和原有的代碼造成了鏈接點

執行點就是一個能夠執行的方法在執行時就是一個執行點,當create方法在執行的時候就是一個執行點,方位信息就是加入Advice的位置就是方位信息,執行點加上分爲信息就是鏈接點。

理解切點測試案例:

package ecut.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest4 {

    public static void main(String[] args) {
        
        // 代理目標 ( 誰將被別人代理 )
        final Object target = new FoxconnFactory();
        
        Class<?> targetClass = target.getClass(); 
        
        ClassLoader loader = targetClass.getClassLoader() ;
        
        Class<?>[] interfaces = targetClass.getInterfaces() ; 
        
        InvocationHandler handler = new InvocationHandler(){
            @Override
            //只保留了before和after的方位信息,20個鏈接點裏挑選了8個,增長了了判斷語句後只對create方法增長Advice,這些篩選條件就是切點
            public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
                
                if( "create".equals( method.getName() ) ) {
                    System.out.println( "方法[ " + method.getName() +  " ]將要執行了" ); // before
                }
                
                Object result = method.invoke( target , args ) ; // 執行點
                
                if( "create".equals( method.getName() ) ){
                    System.out.println( "方法[ " + method.getName() +  " ]執行結束" );  // after
                }
                
                return result;
            }
        } ;
        
        Object proxy = Proxy.newProxyInstance( loader , interfaces , handler ) ;
        
        if( proxy instanceof AppleCompany ) {
            AppleCompany ac = (AppleCompany) proxy ;
            System.out.println( ac == proxy );
            
            IPhone p = ac.create();
            System.out.println( p );
            
            String s = ac.toString() ;
            System.out.println( s );
            
            System.out.println( ac.hashCode() );
        }
        
    }

}

切點就是篩選鏈接點的條件,好比增長了了判斷語句後只對create方法增長Advice,這些篩選條件就是切點。

參考博客連接:

https://blog.csdn.net/pdsygt/article/details/46433537

https://blog.csdn.net/u011266694/article/details/78918394

轉載請於明顯處標明出處:

http://www.javashuo.com/article/p-srypzbjv-db.html

相關文章
相關標籤/搜索