學習AOP之認識一下Spring AOP

心碎之事

要說知道AOP這個詞卻是好久好久之前了,可是直到今天我也不敢說很是的理解它,其中的各類概念即抽象又太拗口。
在幾回面試中都被問及AOP,可是真的沒有答上來,或者都在面上,這給面試官的感受就是java基礎不行。可見這仍是挺重要的一個概念。java

在看工做中也遇到了相關的問題,在RPC的一種實現機制裏應用了AOP,結果各類類一直繞來繞去看着頭都大了,這也就是沒有對動態代理和aop有了解致使的。面試

因此要好好的去掌握它,不然吃虧的仍是本身。spring

先從概念開始

面向側面的程序設計(aspect-oriented programming,AOP,又譯做面向方面的程序設計、觀點導向編程、剖面導向程序設計)是計算機科學中的一個術語,指一種程序設計範型。該範型以一種稱爲側面(aspect,又譯做方面)的語言構造爲基礎,側面是一種新的模塊化機制,用來描述分散在對象、類或函數中的橫切關注點(crosscutting concern)。——維基百科編程

不知道看完這段話你能理解AOP是個啥嗎?並不能,最多知道AOP是一種規範。因此說要理解AOP最重要仍是要從具體的實現入手,才能真正明白AOP究竟是幹啥子的。app

那麼AOP的核心是什麼?

其實就是一種代碼加強方式,能夠實現動態的代理,在運行期完成;也有一些是靜態的爲對象加強,在編繹期間完成。說的白話一點就是在對象進行代碼上的擴展加強,就是說原先可能只能跑10行代碼,加強後就能夠多跑些代碼,並且這種加強是經過織入的方式完成,而不是直接修改目標對象的代碼。ide

爲此仍是要先理解代理這個概念,能夠更好的理解AOP,那麼就從學習代理開始吧。模塊化

寫個靜態代理例子

//建立一個接口
public interface ISay {
    void say();
}

//接口的實現類(能夠理解爲業務類)
public class SayImpl implements ISay{

    @Override
    public void say() {
        System.out.print("我是5207.");
    }

}

//只說一個名字太單調了,須要多多拉票,建立一個代理類來加強一下

class StaticSayImplProxy implements ISay {
    private ISay target;
    
    public StaticSayImplProxy(ISay target) {
        this.target = target;
    }

    @Override
    public void say() {
        SayHello();
        this.target.say();
        ThumbUp();
    }
    
    void SayHello() {
        System.out.print("你們好:");
    }
    
    void ThumbUp() {
        System.out.print("但願你們多多點贊.");
    }
}


//調用代碼
public class StaticProxy {
    public static void main(String[] args) {
        ISay aop = new SayImpl();
        ISay aopProxy = new StaticSayImplProxy(aop);
        aopProxy.say();
    }
}

代碼很簡單,靜態代理代碼中經過增長一個StaticSayImplProxy類來對原先的實現類進行加強。注意這裏的加強很重要。也就是本來只會說一句「我是5207.」,經過代理的類後就完整了許多。函數

可是靜態代理的缺點是,代理時必須知道其類型是,好比上面代碼中就必須知道ISay這個接口,最終才能在代理類裏才能調用:this.target.say()這樣的代碼。那麼也就是說若是還有另外的IOtherSay接口也須要用這個代理類就無法使用了。工具

因此若是能把代理集中一個類中,只要將要代理的對象給這個類就能代理是否是比較方便,代碼少寫好多。學習

整成動態代理

辦法固然是有的,JDK提供了一種動態代理的技術,能夠動態的爲接口建立代理對象,從而實現代理模式。那麼接着上面的例子咱們須要作成動態代理代碼以下:

class DynamicProxyImpl implements InvocationHandler {
    private Object target;
    
    public DynamicProxyImpl(Object target) {
        this.target = target;
    }
    
    @SuppressWarnings("unchecked")
    public <T> T getObject() {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this
            );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        SayHello();
        Object result = method.invoke(target, args);
        ThumbUp();
        return result;
    }
    
    void SayHello() {
        System.out.print("你們好:");
    }
    
    void ThumbUp() {
        System.out.println("但願你們多多點贊.");
    }
}

這裏能夠發現DynamicProxyImpl類並無指定具體代理的接口類型,而使用Object類型。這樣就不用關心傳入給這個代理類的具體對象了。因此說這個代理類的範圍一會兒就大了許多,只要是相似的加強功能均可以用這個代理類來完成。舉個例子:

public interface IOtherSay {
    void applause();
}

public class OtherSayImpl implements IOtherSay {

    @Override
    public void applause() {
        System.out.print("你們鼓掌.");
    }

}

public static void main(String[] args) {
    IOtherSay oSay = new OtherSayImpl();
    DynamicProxyImpl oSayProxy = new DynamicProxyImpl(oSay);
    IOtherSay oSay2 = oSayProxy.getObject();
    oSay2.applause();
}

能夠看到使用同一個代理類也能夠代理IOtherSay接口派生的對象啦。

InvocationHandler接口
另一點就是DynamicProxyImpl實現了InvocationHandler接口,這個接口是關鍵,其實應當是JDK代理對象時的一個調用處理程序,這應當是暴露給開發者的代碼加強接口啦。InvocationHandler接口只有一個invoke方法,咱們須要作的就是在這個方法中增長鬚要的加強代碼。

Proxy工具類
對於getObject方法纔是真正的代理對象生成的過程,能夠看到最終是Proxy.newProxyInstance這個方法來完成代理對象生成並返回的。那麼這裏能夠看看Proxy的設計與原理。

由於暫時只用到了newProxyInstance方法,就從它開始吧:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

方法的三個參數中要注意interfaces和h

  • interfaces
    是指的傳入須要代理的接口列表,==這裏有一點比較重要JDK的動態代理只能代理接口哦==。

  • h
    另一個h則是指調用處理程序,發如今上面的DynamicProxyImpl類就是一個調用處理程序的實現,這裏的關鍵就是InvocationHandler。

接着看方法體中的代碼主要是幾個過程:

  1. 獲取/建立代理類Class:Class<?> cl = getProxyClass0(loader, intfs);
  2. 得到代理Class的構造函數:final Constructor<?> cons = cl.getConstructor(constructorParams);
  3. 建立代理類的對象實例並返回:return cons.newInstance(new Object[]{h});

這裏還有些反射相關的知識就再也不說明了,可是有一個點比較好奇,就是最終代理對象是如何經過invoke把目標對象的方法代理的呢?

篇幅有點多就引用一篇吧JDK動態代理實現原理

其實大致意思是最終JDK會自動的爲指定的接口生成代理對象,而這個生成的代理對象就和前面手寫的動態代理方法相似,只不過生成的代理類調用的是InvocationHandler的invoke。jdk幫咱們作了自動生成的過程,這樣就能夠在運行期生成代理類。

到此,最爲重要的代理差很少說完了,這也就是AOP的奧祕所在。

Spring中的AOP

瞭解了代理後再來看AOP的相關概念仍是理解不了,什麼切面、鏈接點、通知、切入點之類的,因此說仍是拋開吧,這樣要輕鬆許多。相信你們看aop的時候確定是看到了Advice這個東東吧?嗯,就從它入手吧。

在Spring中Advice下面這些類型:

  • Before Advice:在目標方法被調用前調用,涉及接口org.springframework.aop.MethodBeforeAdvice
  • After Advice:在目標方法被調用後調用,涉及接口爲org.springframework.aop.AfterReturningAdvice
  • Throws Advice:目標方法拋出異常時調用,涉及接口org.springframework.aop.ThrowsAdvice
  • Around Advice:攔截對目標對象方法調用,涉及接口爲org.aopalliance.intercept.MethodInterceptor
  • Introduction Advice:爲攔截的目標增長方法,涉及的接口爲org.springframework.aop.support.DelegatingIntroductionInterceptor

嗯,都是些啥意思?Berfore Advice能夠理解爲SayHello方法,那麼ThumbUp就是After Advice,兩個加一塊兒就是Around Advice。對於Throws Advice是針對異常拋出時的加強。最後還有一個比較牛的就是Introduction Advice則是能夠對即有的對象進行增長方法,這個貌似更強大點。

從這些描述能夠總結出來和前面動態代理時有殊途同歸之處,在Spring中其實就是利用了動態代理的技術,結合AOP的概念對代碼提供了一種更友好的擴展方式。固然也能夠說換了一個角度來理解代碼。舉個例子說,但願在現有系統中監控全部action的執行時間,就能夠攔截全部的action,加一個Before Advice記錄一下進入的時間,再加一個After Advice計算一下完成的時間。這樣就不會對現有action代碼作什麼修改,很是優雅啊,這對於系統設計時會有很是有用。

進一步理解Spring AOP

爲了可以更深刻的理解Spring AOP,仍是須要更深刻的去閱讀源代碼。這裏再以DynamicProxyImpl爲例子,若是使用Spring如何實現它的功能呢?以Spring xml配置方式試一下吧。

  • 首先實現用於加強SayImpl的代碼,這裏使用環繞加強,和前面DynamicProxyImpl的效果一致。
package aop.demo;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class SayImplAroundAdvice implements MethodInterceptor{

    public Object invoke(MethodInvocation invocation) throws Throwable {
        SayHello();
        Object result = invocation.proceed();
        ThumbUp();
        return result;
    }
    
    void SayHello() {
        System.out.print("你們好:");
    }
    
    void ThumbUp() {
        System.out.println("但願你們多多點贊.");
    }
}
  • 使用Spring的配置文件聲明對象和AOP代理,spring.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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 聲明被代理的目標對象 -->
    <bean id="sayImpl" class="aop.demo.SayImpl"></bean>
    <!-- 聲明用於加強的攔截器對象 -->
    <bean id="sayImplAroundAdvice" class="aop.demo.SayImplAroundAdvice"></bean>
    <!-- 聲明代理對象 -->
    <bean id="sayProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interfaces" value="aop.demo.ISay"/>            <!-- 這個就是被代理的接口 -->
        <property name="target" ref="sayImpl"/>                        <!-- 這個就是被代理的對象 -->
        <property name="interceptorNames" value="sayImplAroundAdvice"/><!-- 這個就是代理的加強器 --> 
    </bean>
</beans>
  • 實現代理的訪問
package aop.demo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {

    @SuppressWarnings("resource")
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml");
        ISay say = (ISay)context.getBean("sayProxy");
        
        say.say();
    }

}

最後執行一下程序輸出的結果是:

你們好:我是5207.但願你們多多點贊.

哎呀呀和前面的動態代理一致,效果達成。^_^

固然也能夠經過代碼方式完成上面的功能

package aop.demo;

import org.springframework.aop.framework.ProxyFactory;

public class ClientCode {

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();     // 建立代理工廠
        proxyFactory.setTarget(new SayImpl());         // 射入目標類對象
        proxyFactory.addAdvice(new SayImplAroundAdvice());
        ISay say = (ISay) proxyFactory.getProxy();
        say.say();
    }

}

總結

寫了一天才寫到這裏,只不過仍是值得的。原來一直覺得AOP就是動態代理,沒想到本身錯了,AOP是一種規範,而動態代理只是實現AOP的一種方式而已。

接下來繼續研究spring aop,進一步學習ProxyFactoryBean和ProxyFactory。

引用與參考

AOP 那點事兒
Java之美[從菜鳥到高手演練]之JDK動態代理的實現及原理
JDK動態代理實現原理
Spring AOP代理詳解

注:此文章爲原創,歡迎轉載,請在文章頁面明顯位置給出此文連接! 若您以爲這篇文章還不錯請點擊下右下角的推薦,很是感謝! http://www.cnblogs.com/5207

相關文章
相關標籤/搜索