Spring AOP APIS

海淵發表於2019-06-26

1:Pointcut API in Spring

(1):切點介面定義

org.springframework.aop.Pointcut介面是中心介面。用來將Advice(通知)定位到特定的類和方法

public interface Pointcut {
​
    ClassFilter getClassFilter();
​
    MethodMatcher getMethodMatcher();
​
}

ClassFilter介面用來決定切點作用的類。如果matches()方法總是返回true,所有的目標均匹配。


public interface ClassFilter {
​
    boolean matches(Class clazz);
}

MethodMatcher介面通常更重要。

public interface MethodMatcher {
​
    boolean matches(Method m, Class targetClass);
​
    boolean isRuntime();
​
    boolean matches(Method m, Class targetClass, Object[] args);
}

大多數MethodMatcher實現是靜態的,意味著它們的isRuntime()返回false。由於這個原因,三引數的matches方法永遠不會被呼叫。

`注意:

如果可能的話,讓切點為靜態,允許AOP框架快取切點計算結果當AOP代理被建立的時候。

(2):切點操作

Spring支援切點操作有聯合(union)和交集(intersection)。聯合意味著方法任意一個匹配即可,交集

意味著所有切點都匹配。然而,使用AspectJ point expression通常更簡便。

AspectJExpressionPointcut:AspectJ point expression

(3):便利切點實現

  • 靜態切點(Static Pointcuts)

    靜態切入點基於方法和目標類,不能考慮方法引數。對於大多數用途,靜態切入點足夠並且最好。首次呼叫方法,Spring只評估一次靜態切入點。之後,無需再次使用每個方法呼叫來評估切入點。

  • 正規表示式切入點(Regular Expression Pointcuts)

    • JdkRegexpMethodPointcut :JDK支援的正規表示式。你可以設定正規表示式的列表,任何一個滿足,即切點評估為true

  • 動態切點(Dynamic Pointcuts)

​ 動態切點,評估成本比靜態成本高。考慮了方法引數和靜態資訊。這意味著必須使用每個方法呼叫來評估它們,並且不能快取結果,因為引數會有所不同。

  • 控制流切點(Control Flow Pointcuts)

    和AspectJ的cflow類似。

(4):切點父類(Pointcut Superclasses)

由於static pointcuts是最有用的,StaticMethodMatcherPointcut。使用示例如下:


class TestStaticPointcut extends StaticMethodMatcherPointcut {
​
    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }
}

2:Advice API in Spring

(1):通知生命週期

每一個通知都是一個Spring Bean通知示例可以在所有通知物件之間共享

(2):Spring通知型別

Around Advice(環繞通知)

Spring最基本的通知是環繞通知。使用了method interception。類實現MethodInterceptor


public interface MethodInterceptor extends Interceptor {
    
    /**
     * Implement this method to perform extra treatments before and
     * after the invocation. Polite implementations would certainly
     * like to invoke {@link Joinpoint#proceed()}.
     * @param invocation the method invocation joinpoint
     * @return the result of the call to {@link Joinpoint#proceed()};
     * might be intercepted by the interceptor
     * @throws Throwable if the interceptors or the target object
     * throws an exception
     */
    Object invoke(MethodInvocation invocation) throws Throwable;
​
}
​
/**
 * Description of an invocation to a method, given to an interceptor
 * upon method-call.
 *
 * <p>A method invocation is a joinpoint and can be intercepted by a
 * method interceptor.
 */
public interface MethodInvocation extends Invocation {
​
    /**
     * Get the method being called.
     * <p>This method is a frienly implementation of the
     * {@link Joinpoint#getStaticPart()} method (same result).
     * @return the method being called
     */
    Method getMethod();
​
}
​
​
public interface Invocation extends Joinpoint {
​
    /**
     * Get the arguments as an array object.
     * It is possible to change element values within this
     * array to change the arguments.
     * @return the argument of the invocation
     */
    Object[] getArguments();
}
​
​
public interface Joinpoint {
​
    /**
     * Proceed to the next interceptor in the chain.
     * <p>The implementation and the semantics of this method depends
     * on the actual joinpoint type (see the children interfaces).
     * @return see the children interfaces' proceed definition
     * @throws Throwable if the joinpoint throws an exception
     */
    Object proceed() throws Throwable;
​
    /**
     * Return the object that holds the current joinpoint's static part.
     * <p>For instance, the target object for an invocation.
     * @return the object (can be null if the accessible object is static)
     */
    Object getThis();
​
    /**
     * Return the static part of this joinpoint.
     * <p>The static part is an accessible object on which a chain of
     * interceptors are installed.
     */
    AccessibleObject getStaticPart();
​
}
 

invoke()方法的MethodInvocation引數公開了被呼叫的方法,目標連線點,AOP代理和方法的引數。 invoke()方法應該返回撥用的結果:連線點的返回值

Before Advice(前置通知)

簡單的通知型別是前置通知。不需要MethodInvocation物件。它是在進入方法之前呼叫

前置通知主要優點不需要呼叫proceed()方法,因此不會無意中無法繼續攔截鏈。


public interface MethodBeforeAdvice extends BeforeAdvice {
​
    void before(Method m, Object[] args, Object target) throws Throwable;
}

Spring API設計允許在通知之前提供欄位,儘管通常的物件適用於欄位攔截,但是Spring不太可能實現它。請注意,返回型別為void。在通知可以在連線點執行之前插入自定義行為但是不能更改返回值之前。如果before advice丟擲異常,則會中止攔截器鏈進一步執行。異常傳播回攔截器鏈。

返回所有方法的呼叫次數


public class CountingBeforeAdvice implements MethodBeforeAdvice {
​
    private int count;
​
    public void before(Method m, Object[] args, Object target) throws Throwable    {
        ++count;
    }
​
    public int getCount() {
        return count;
    }
}

Throws Advice(異常通知)

異常通知在返回連線點後,如果連線點跑出異常,則該通知被呼叫。ThrowsAdvice,該介面是一個標記介面,繼承了AfterAdvice。示例如下:異常引數必須存在,其他引數像method,arguments是否存在,取決你是否需要。


public void afterThrowing(Exception ex){
​
}
public void afterThrowing(RemoteException){
​
}
public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
​
}
public void afterThrowing(Method method, Object[] args, Object target, ServletException ex){
​
}

注意

不要丟擲與目標籤名方法不相容的未宣告的已檢查異常

後置返回通知(After Returning Advice)

後置返回通知需要實現`AfterReturningAdvice `。


public interface AfterReturningAdvice extends Advice {
​
    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}

後置返回通知可以訪問返回值(但是它不能修改),被呼叫的方法,方法引數,目標物件.如果它丟擲異常,

則丟擲攔截器鏈而不是返回值。

統計所有成功呼叫但是沒有丟擲異常的方法次數


public class CountingAfterReturningAdvice implements AfterReturningAdvice {
​
    private int count;
​
    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        ++count;
    }
​
    public int getCount() {
        return count;
    }
}

引入通知(Introduction Advice)

引入通知需要一個IntroductionAdvisorIntroductionInterceptor,如下實現


public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
​
}
​
public interface DynamicIntroductionAdvice extends Advice {
​
    /**
     * Does this introduction advice implement the given interface?
     * @param intf the interface to check
     * @return whether the advice implements the specified interface
     */
    boolean implementsInterface(Class<?> intf);
​
}

invoke()方法繼承了AOP aopalliance MethodInterceptor介面。如果呼叫的方法在引入的介面上,

則引入攔截器負責處理呼叫,它不能呼叫proceed()

引入通知不能適用於任意切點。它僅適用於類,而不是方法級別的。只能在IntroductionAdvisor中使用引入通知。


public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
​
    ClassFilter getClassFilter();
​
    void validateInterfaces() throws IllegalArgumentException;
}
​
public interface IntroductionInfo {
​
    Class[] getInterfaces();
}

僅僅含有class過濾邏輯。getInterfaces()返回切面引入的介面。validateInterfaces()方法被用於判斷引入的介面是否能被IntroductionInterceptor配置。

DelegatingIntroductionInterceptor 被設計成代理一個引入給實際實現引入的介面。

3:The Advisor API in Spring

在Spring中,Advisor是一個僅含有一個通知物件和一個切點表示式物件關聯切面

`DefaultPointcutAdvisor `是最通用advisor。

(2):使用ProxyFactoryBean建立AOP代理

在Spring中建立AOP代理的基本方法是使用ProxyFactoryBean。他可以完全控制切入點,任何適用通知以及它們順序。

JavaBean屬性

一些key屬性繼承自ProxyConfig

  • proxyTargetClass:如果代理的是目標類,而不是介面,該值為true。預設是false

  • optimize:控制是否將積極優化應用與通過CGLIB建立的代理。除非您完全瞭解相關AOP的優化,否則不應該輕易使用此設定。

  • frozen:如果代理配置被凍結,則不再允許更改配置。預設為false

  • exposeProxy:決定是否將當前代理物件暴露到ThreadLocal中,以便可以被目標物件訪問。可以通過

AopContext.currentProxy()獲取。

其他屬性來自於ProxyFactoryBean

  • proxyInterfaces:代理的介面陣列。如果沒有被支援,則CGLIB代理被使用.

  • interceptorNames:攔截器陣列(Advisor)。這些名字存在當前bean工廠中,包含祖先工廠。

  • singleton:決定工廠是否返回單例物件,預設為true

(3):JDK和CGLIB代理

  • 如果一個目標物件沒有實現任何介面,則使用CGLIB代理。

  • 如果一個目標物件實現了任何一個介面,預設使用JDK代理

  • 如果一個目標物件實現了任何一個介面,但是proxyTargetClass屬性為true,使用GGLIB代理。

(4):代理介面

interceptorNames屬性持有一個列表,這個列表是攔截器(MethoInterceptor)或者(Advisor)

在當前工廠的bean names

注意:

您可能想知道為什麼列表不包含bean引用。 原因是,如果ProxyFactoryBean的singleton屬性設定為false,則它必須能夠返回獨立的代理例項. 如果任何顧問本身就是原型,則需要返回一個獨立的例項,因此必須能夠從工廠獲得原型的例項. 持有引用是不夠的.

(5):代理類

CGLIB代理的侷限性

  • final方法不能被通知。由於它們不能被重寫

  • Spring3.2,CGOLIB被重新打包到spring-core jar中。

(6):使用全域性(Advisors)

通過在攔截器後附加星號,將所有與星號前面的部分匹配的bean名稱的advisor程式新增到攔截器鏈中。如下所示:


<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean><bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>

(7):使用ProxyFactory建立AOP代理

一個目標物件,一個通知,一個顧問。

   @Bean
    public AspectJProxyFactory aspectJProxyFactory() {
        AspectJProxyFactory proxyFactoryBean = new AspectJProxyFactory(new UserService());
        String expression = "execution(* com.ley.springboot.UserService.*(..))";
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(expression);
        Advice advice = new UserServiceMethodBeforeAdvice();
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
        proxyFactoryBean.addAdvisor(advisor);
        return proxyFactoryBean;
    }
​
​
    @Bean(name = "proxyFactoryBean")
    public ProxyFactoryBean proxyFactoryBean() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTarget(new UserService());
        String expression = "execution(* com.ley.springboot.UserService.*(..))";
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(expression);
        Advice advice = new UserServiceMethodBeforeAdvice();
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
        proxyFactoryBean.addAdvisors(advisor);
        return proxyFactoryBean;
    }
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

(8):操控通知物件

Advised介面是用來操控通知物件的。


Advisor[] getAdvisors();
​
void addAdvice(Advice advice) throws AopConfigException;
​
void addAdvice(int pos, Advice advice) throws AopConfigException;
​
void addAdvisor(Advisor advisor) throws AopConfigException;
​
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
​
int indexOf(Advisor advisor);
​
boolean removeAdvisor(Advisor advisor) throws AopConfigException;
​
void removeAdvisor(int index) throws AopConfigException;
​
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
​
boolean isFrozen();

 

 

可以新增DefaultPointcutAdvisor,它持有一個pointcut和advised。並且可以被用於新增任意一個

Advisor

(9):使用auto-proxy策略

  • BeanNameAutoProxyCreator:該類是一個BeanPostProcessor,根據bean的名稱自動建立AOP

    代理。


<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="jdk*,onlyJdk"/>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>
 
  • DefaultAdvisorAutoProxyCreator通用且功能更強大的自動代理建立器。會在當前上下文中自動應用符合新增的advisor程式,而無需在auto-proxy advisor的bean定義中包含特定的bean名稱。該類在將相同建議一致的應用於許多業務物件很有用。例如跟蹤或者效能監視方面。

(10):定義新的通知型別

org.springframework.aop.framework.adapter包是一個SPI包,擴充套件新的通知型別。自定義新的通知型別唯一約束是它必須實現org.aopalliance.aop.Advice標記介面。