Spring學習筆記(七、Spring AOP API)

上一篇:Spring學習筆記(六、Spring AOP基本概念)

一、Spring AOP API

  • 這是Spring 1.2歷史用法,現在(V4.0)仍然支持。
  • 這是Spring AOP 基礎,不得不了解。
  • 現在的用法也是基于歷史的,只是更簡便了。

1. Pointcut

  • 實現之一:NameMatchMethodPointcut,根據方法名字進行匹配。
  • 成員變量:mappedNames,匹配的方法名集合。
    創建BizLogic接口:
package test16;
/**
 * Created by amber on 2017/6/19.
 */
public interface BizLogic {
    String save();
}

創建實現類BizLogincImpl :

package test16;
/**
 * Created by amber on 2017/6/19.
 */
public class BizLogincImpl implements BizLogic {
    public String save() {
        System.out.println("BizLogincImpl:save");
        return "BizLogincImpl:save";
    }
}

applicationContext:

  <bean id="bizLogincTarget" class="test16.BizLogincImpl"></bean>
    <bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
        <property name="mappedNames">
            <list>
                <value>sa*</value>
            </list>
        </property>
    </bean>

2. Before advice

  • 一個簡單的通知類型
  • 只是在進入方法之前被調用,不需要MethodInvocation對象
  • 前置通知可以在連接點執行之前插入自定義行為,但不能改變返回值。
    創建前置通知類BeforeAdvice :
package test16;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
 * Created by amber on 2017/6/19.
 */
public class BeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("我是前置通知,攔截的方法名:" + method.getName() + "  目標類名:" + target.getClass().getName());
    }
}

3. Throws advice

  • 如果連接點拋出異常,throws advice 在連接點返回后被調用。
  • 如果throws-advice的方法拋出異常,那么它將覆蓋原有異常
  • 接口org.springframework.aop.ThrowsAdvice 不包含任何方法,僅僅是一個聲明,實現類需要實現類似下面的方法:
    void afterThrowing([Method,args,target],ThrowableSubclass);
    如:
public void afterThrowing(Exception ex)
public void afterThrowing(RemoteException ex)
public void afterThrowing(Method method,Object[] args,Object target,Exception ex)
public void afterThrowing(Method method,Object[] args,Object target,ServletException ex)

創建MyThrowsAdvice類:

package test16;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
/**
 * Created by amber on 2017/6/19.
 */
public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Exception ex) {
        System.out.println("拋出異常通知: afterThrowing 1");
    }
    public void afterThrowing(Method method, Object[] objects, Object target, Exception ex) {
        System.out.println("拋出異常通知: afterThrowing 2,出現異常的方法名:" + method.getName() + "   出現異常的類名:" + target.getClass().getName());
    }
}

4. After Returning advice

  • 后置通知必須實現org.springframework.aop.AfterReturningAdive接口
  • 可以訪問返回值(但不能進行修改),被調用的方法,方法的參數和目標。
  • 如果拋出異常,將會拋出攔截器鏈,替代返回值。
    創建AfterReturningAdvice 類:
package test16;
import java.lang.reflect.Method;
/**
 * Created by amber on 2017/6/19.
 */
public class AfterReturningAdvice implements org.springframework.aop.AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
         System.out.println("我是返回后通知, 攔截的方法名:"+method.getName()+"  目標類名:"+target.getClass().getName()+"  返回的值:"+returnValue );
    }
}

5. Interception around advice

  • Spring的切入點模型使得切入點可以獨立與advice重用,以針對不同的advice可以使用相同的切入點。
    創建MethodInterception 類:
package test16;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
 * Created by amber on 2017/6/19.
 */
public class MethodInterception implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("MethodInterceptor(類似環繞通知) invoke 1,攔截的方法: "+invocation.getMethod().getName()+"  攔截的目標類名:"+invocation.getStaticPart().getClass().getName());
        Object obj=invocation.proceed();
        System.out.println("MethodInterceptor(類似環繞通知) invoke 2,攔截的目標: "+obj);
        return obj;
    }
}

6. Introduction advice

  • 引用通知概念:一個Java類,沒有實現A接口,在不修改Java類的情況下,使其具備A接口的功能。
  • Spring 把引入通知作為一種特殊的攔截通知
  • 需要IntroductionAdvisor和IntroductionInterceptor
  • 僅適用于類,不能和任何切入點一起使用
  • 一個Spring test suite例子
  • 如果調用lock()方法,希望所有的setter方法拋出LockedException異常(如使物體不變,Spring典型例子)
  • 需要一個完成繁重任務的IntroductionInterceptor,這種情況下,可以使用org.springframework.aop.support.DelegatingIntroducationInterceptor。
    有時間再做實驗!!!

7. Advisor API in Spring

  • Advisor是僅包含一個切入點表達式關聯的單個通知的方面。
  • 除了introductions,advisor可以用于任何通知。
  • org.springframework.aop.support.DefaultPointcutAdvisor是最常用的類,它可以與MethodInterceptor,BeforeAdvice或者ThrowsAdvice一起使用。
  • 它可以混合在Spring同一個AOP代理的advisor和advice。

二、ProxyFactoryBean

  • 創建Spring AOP代理的基本方法是使用org.apringframework.aop.framework.ProxyFactoryBean。
  • 這個類可以完全控制切入點和通知(advice)以及它們的順序。
  • 使用ProxyFactoryBean或者其他IoC相關類,來創建AOP代理的最重要好處是:通知和切入點也可以由IoC管理。
  • 被代理類沒有實現任何接口,使用CGLIB代理,否則JDK代理。
  • 通過設置proxyTargetClass為true,可強制使用CGLIB
  • 如果目標類實現了一個(或者多個)接口,那么創建代理的類型將依賴ProxyFactoryBean 的配置。
  • 如果ProxyFactoryBean的proxyInterfaces屬性被設置為一個或者多個全限定接口名,基于JDK的代理將被創建。
  • 如果ProxyFactoryBean的proxyInterfaces屬性沒有被設置,但是目標類實現了一個(或者更多)接口,那么ProxyFactoryBean將自動檢測到這個目標類已經實現了至少一個接口,創建一個基于JDK的代理。

關于CGLIB動態代理,參考:CGLib動態代理原理及實現
關于JDK動態代理,參考:JDK動態代理實現原理

1. Proxying interfaces

  • 使用PointcutBean,不指定proxyInterfaces:
    1. applicationContext中:
 <bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="bizLogincTarget" class="test16.BizLogincImpl"></bean>

    <bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
        <property name="mappedNames">
            <list>
                <value>sa*</value>
            </list>
        </property>
    </bean>
    <bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="beforeAdvice"/>
        <property name="pointcut" ref="pointcutBean"/>
    </bean>
    <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target">
            <ref bean="bizLogincTarget"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>defaultAdvisor</value>
                <value>afterReturningAdvice</value>
                <value>methodInterception</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>
  • 測試類:
  @Test
    public void test16() {
        BizLogic logic=super.getBean("bizLogicImpl");
        logic.save();
    }
  • 結果:
Paste_Image.png
  • 不使用PointcutBean,指定proxyInterfaces:
    <bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
    <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>test16.BizLogic</value>
        </property>
        <property name="target">
            <ref bean="bizLogicTarget"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterReturningAdvice</value>
                <value>methodInterception</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>

結果:

Paste_Image.png
  • 可以使用匿名內部bean來隱藏目標和代理之前的區別
<property name="target">
            <ref bean="bizLogicTarget"/>
        </property>
引用beanId替換成直接引用路徑。
  <property name="target">
            <bean class="test16.BizLogincImpl"/>
        </property>
 <bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
    <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>test16.BizLogic</value>
        </property>
        <property name="target">
            <bean class="test16.BizLogincImpl"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterReturningAdvice</value>
                <value>methodInterception</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>

結果:

Paste_Image.png

三、Proxing classes

  • 前面的例子如果沒有接口,這種情況下Spring會使用CGLIB代理,而不是JDK動態代理。
  • 如果想,可以強制在任何情況下使用CGLIB,即使有接口。
  • CGLIB代理的工作原理是在運行時生成目標類的子類,Spring配置這個生成的子類委托方法調用到原來的目標。
  • 子類是用來實現Decorator模式,織入通知。

1. CGLIB的代理對用戶來說是透明的,需要注意:

  • final方法不能被通知,因為他們不能覆蓋。
  • 不用把CGLIB添加到classpath中,在Spring3.2中,CGLIB被重新包裝并包含在Spring核心的JAR(即基于CGLIB的AOP就像JDK動態代理一樣“開箱即用”)

2. 使用global advisors

  • 用*做通配,匹配所有攔截器加入通知鏈。
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>

    <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>test16.BizLogic</value>
        </property>
        <property name="target">
            <bean class="test16.BizLogincImpl"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterReturningAdvice</value>
                <value>method*</value>//只適用于實現了MethodInterceptor接口的攔截器,其他advice不適用哦!!!
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>

3. 簡化的Proxy定義

  • 使用父子bean定義,以及內部bean定義,可能會帶來更清潔更更簡潔的代理定義(抽象屬性標記父bean定義為抽象的這樣它不能被實例化)
    修改applicationContext:
    <bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="baseProxyBean" class="org.springframework.aop.framework.ProxyFactoryBean" lazy-init="true" abstract="true"/>
    <bean id="bizLogicImpl" parent="baseProxyBean">//將baseProxyBean作為父類
        <property name="target">
            <bean class="test16.BizLogincImpl"/>//直接引用實現類
        </property>
        <property name="proxyInterfaces">
            <value>test16.BizLogic</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterReturningAdvice</value>
                <value>methodInterception</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>

結果:


Paste_Image.png

4. 使用ProxyFactory

  • 使用Spring AOP而不依賴于Spring IoC。
  • 大多數情況下,最佳實踐是用IoC容器創建AOP代理。
  • 雖然可以硬編碼方式實現,但是Spring推薦使用配置或注解方式實現。


    Paste_Image.png

5. 使用auto-proxy

  • Spring也允許使用“自動代理”的bean定義,它可以自動代理選定的bean,這是建立在Spring的“bean post processer”功能基礎之上的(在加載bean的時候可以修改)。
  • BeanNameAutoProxyCreator。
Paste_Image.png
  • DefaultAdvisorAutoProxyCreator,當前IoC容器中自動應用,不用顯示聲明引用advisor的bean定義。
Paste_Image.png

下一篇:Spring學習筆記(八、Spring對AspectJ的支持)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容