開發與維運

Spring 5 中文解析核心篇-IoC容器之Spring AOP API

上一章通過@AspectJ和基於schema的切面定義描述了Spring對AOP的支持。在本章中,我們討論了較低級別的Spring AOP API。對於常見的應用程序,我們建議將Spring AOP與AspectJ切入點一起使用,如上一章所述。

6.1 本節描述了Spring如何處理關鍵切入點概念。
6.1.1 概念

Spring的切入點模型使切入點重用不受通知類型的影響。你可以使用相同的切入點來定位不同的通知。org.springframework.aop.Pointcut接口是核心接口,用於將通知定向到特定的類和方法。完整的接口如下:

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

Pointcut接口分為兩部分,可以重用類和方法匹配的部分以及細粒度的合成操作(例如與另一個方法匹配器執行“聯合”)。

ClassFilter接口用於將切入點限制為給定的一組目標類。如果matches()方法始終返回true,則將匹配所有目標類。以下清單顯示了ClassFilter接口定義:

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);
}

matchs(Method,Class)方法用於測試此切入點是否與目標類上的給定方法匹配。創建AOP代理時可以執行此評估,以避免需要對每個方法調用進行測試。如果兩個參數的match方法對於給定的方法返回true,並且MethodMatcherisRuntime()方法返回true,則在每次方法調用時都將調用三個參數的match方法。這樣,切入點就可以在執行目標通知之前立即查看傳遞給方法調用的參數。

大多數MethodMatcher實現都是靜態的,這意味著它們的isRuntime()方法返回false。在這種情況下,永遠不會調用三參數匹配方法。

如果可能,請嘗試使切入點成為靜態,以允許AOP框架在創建AOP代理時緩存切入點評估的結果。

6.1.2 切入點的操作

Spring支持切入點上的操作(特別是聯合和交集)。

聯合表示兩個切入點匹配其中一個的方法。交集是指兩個切入點都匹配的方法。聯合通常更有用。你可以通過使用org.springframework.aop.support.Pointcuts類中的靜態方法或使用同一包中的ComposablePointcut類來組成切入點。但是,使用AspectJ切入點表達式通常是一種更簡單的方法。但是,使用AspectJ切入點表達式通常是一種更簡單的方法。

6.1.3 AspectJ 表達式切入點

從2.0開始,Spring使用的最重要的切入點類型是org.springframework.aop.aspectj.AspectJExpressionPointcut。這是一個切入點,該切入點使用AspectJ提供的庫來解析AspectJ切入點表達式字符串。

有關支持的AspectJ切入點原語的討論,請參見上一章

6.1.4 便捷切入點實現

Spring提供了幾種方便的切入點實現。你可以直接使用其中一些。其他的則打算在特定於應用程序的切入點中被子類化。

靜態切入點

靜態切入點基於方法和目標類,並且不能考慮方法的參數。靜態切入點足以滿足大多數用途,並且最好。首次調用方法時,Spring只能評估一次靜態切入點。之後,無需在每次方法調用時再次評估切入點(備註:第一次評估後進行緩存)。

本節的其餘部分描述了Spring附帶的一些靜態切入點實現。

正則表達式切入點

指定靜態切入點的一種明顯方法是正則表達式。除了Spring之外,還有幾個AOP框架使之成為可能。org.springframework.aop.support.JdkRegexpMethodPointcut是一個通用的正則表達式切入點它使用JDK中的正則表達式支持。

使用JdkRegexpMethodPointcut類,可以提供模式字符串的列表。如果其中任何一個匹配,則切入點的評估結果為true。(因此,結果實際上是這些切入點的並集。)

以下示例顯示如何使用JdkRegexpMethodPointcut

<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

Spring提供了一個名為RegexpMethodPointcutAdvisor的便捷類,該類使我們還可以引用一個Advice(請記住,Advice可以是攔截器、前置通知、異常通知等)。在幕後,Spring使用了JdkRegexpMethodPointcut。使用RegexpMethodPointcutAdvisor簡化了連接,因為一個bean同時封裝了切入點和通知,如下面的示例所示:

<bean id="settersAndAbsquatulateAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref bean="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

你可以將RegexpMethodPointcutAdvisor與任何Advice類型一起使用。

屬性驅動的切入點

靜態切入點的一種重要類型是元數據驅動的切入點。這將使用元數據屬性的值(通常是源級別的元數據)。

動態切入點

動態切入點比靜態切入點更昂貴。它們考慮了方法參數以及靜態信息。這意味著必須在每次方法調用時對它們進行評估,並且由於參數會有所不同,因此無法緩存結果。

主要示例是control flow切入點。

控制流切入點

Spring控制流切入點在概念上類似於AspectJ cflow切入點,儘管功能不那麼強大。(目前還沒有辦法指定一個切入點在與另一個切入點匹配的連接點下面執行。)控制流切入點與當前調用堆棧匹配。例如,如果連接點是由com.mycompany.web包中的方法或SomeCaller類調用的,則可能會觸發。通過使用org.springframework.aop.support.ControlFlowPointcut類指定控制流切入點。通過使用org.springframework.aop.support.ControlFlowPointcut類指定控制流切入點。

與其他動態切入點相比,控制流切入點在運行時進行評估要昂貴得多。在Java 1.4中,成本大約是其他動態切入點的五倍。

6.1.5 切入點超類

Spring提供了有用的切入點超類,以幫助你實現自己的切入點。因為靜態切入點最有用,所以你可能應該子類化StaticMethodMatcherPointcut。這僅需要實現一個抽象方法(儘管你可以覆蓋其他方法以自定義行為)。下面的示例顯示如何對StaticMethodMatcherPointcut進行子類化:

class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }
}

動態切入點也有超類。你可以將自定義切入點與任何通知類型一起使用。

6.1.6 自定義切面

因為Spring AOP中的切入點是Java類,而不是語言功能(如AspectJ),所以你可以聲明自定義切入點,無論是靜態還是動態。Spring中的自定義切入點可以任意複雜。但是,如果可以的話,我們建議使用AspectJ切入點表達語言。

更高版本的Spring可能提供對JAC提供的“語義切入點”的支持,例如“所有更改目標對象中實例變量的方法”。

6.2 Spring中的通知API

現在,我們可以檢查Spring AOP如何處理通知。

6.2.1 通知生命週期

每個通知都是一個Spring bean。通知實例可以在所有通知對象之間共享,或者對於每個通知對象都是唯一的。這對應於每個類或每個實例的通知。

每個類通知最常用。適用於一般通知,例如事物advisors(切面和通知組合)。這些不依賴於代理對象的狀態或添加新狀態。它們僅作用於方法和參數。

每個實例的通知都適合引入,以支持mixins。在這種情況下,通知將狀態添加到代理對象。

你可以在同一AOP代理中混合使用共享通知和基於實例的通知。

6.2.2 Spring中通知類型

Spring提供了幾種通知類型,並且可以擴展以支持任意通知類型。本節介紹基本概念和標準通知類型。

攔截環繞通知

Spring中最基本的通知類型是環繞通知的攔截。

對於使用方法攔截的通知,Spring符合AOP Alliance接口。實現MethodInterceptor和環繞通知的類也應該實現以下接口:

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

invoke()方法的MethodInvocation參數公開了被調用的方法、目標連接點、AOP代理和方法的參數。invoke()方法應返回調用的結果:連接點的返回值。

以下示例顯示了一個簡單的MethodInterceptor實現:

public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}

請注意對MethodInvocationproceed()方法的調用。這沿著攔截器鏈向下到達連接點。大多數攔截器都調用此方法並返回其返回值。但是,MethodInterceptor就像其他的環繞通知一樣,可以返回不同的值或引發異常,而不是調用proceed方法。但是,你沒有充分的理由就不要這樣做。

MethodInterceptor實現提供與其他符合AOP Alliance要求的AOP實現的互操作性。本節其餘部分討論的其他通知類型將實現常見的AOP概念,但以特定於Spring的方式。儘管使用最具體的通知類型有一個優勢,但是如果你可能想在另一個AOP框架中運行切面,則在環繞通知使用MethodInterceptor。請注意,切入點當前無法在框架之間互操作,並且AOP Alliance當前未定義切入點接口。

前置通知

一種最簡單的通知類型是前置通知。這個不需要MethodInvocation對象,因為它僅僅在進入方法前被調用。

前置通知的主要優點在於,無需調用proceed()方法,因此,不會因疏忽而未能沿攔截器鏈繼續前進。

以下清單顯示了MethodBeforeAdvice接口:

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}

(儘管通常的對象適用於字段攔截,並且Spring不太可能實現它,但Spring的API設計允許前置通知。)

請注意,返回類型為void。通知可以在連接點執行之前插入自定義行為,但不能更改返回值。如果前置的通知引發異常,它將中止攔截器鏈的進一步執行。異常會傳播回攔截器鏈。如果是未檢查異常在調用的方法的簽名上,則將其直接傳遞給客戶端。否則,它將被AOP代理包裝在未經檢查的異常中。

以下示例顯示了Spring中的before通知,該通知計算所有方法調用:

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;
    }
}

前置通知可以被使用於任何切入點。

異常通知

如果連接點引發異常,則在連接點返回後調用引發通知。Spring提供拋出異常通知。注意這意味著org.springframework.aop.ThrowsAdvice接口不包含任何方法。這是一個標記接口,表示這個對象實現一個或多個拋出異常通知方法。這些應採用以下形式:

afterThrowing([Method, args, target], subclassOfThrowable)

僅最後一個參數是必需的。方法簽名可以具有一個或四個參數,具體取決於通知方法是否對該方法參數感興趣。接下來的兩個清單顯示類,它們是拋出異常通知的示例。

如果引發RemoteException(包括從子類),則調用以下通知:

public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}

與前面的通知不同,下一個示例聲明瞭四個參數,以便可以訪問被調用的方法、方法參數和目標對象。如果拋出ServletException,則調用以下通知:

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

最後一個示例說明如何在處理RemoteExceptionServletException的單個類中使用這兩種方法。可以將任意數量的異常通知方法組合到一個類中。以下清單顯示了最後一個示例:

public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

如果throws-advice方法本身引發異常,則它將覆蓋原始異常(也就是說,它將更改引發給用戶的異常)。重寫異常通常是RuntimeException,它與任何方法簽名都兼容。但是,如果throws-advice方法拋出一個已檢查的異常,則它必須與目標方法的聲明異常匹配,因此在某種程度上與特定的目標方法簽名耦合。不要拋出與目標方法簽名不兼容的未聲明的檢查異常!

異常通知可以被使用與任何切入點。

後置返回通知

在Spring中,後置返回通知必須實現org.springframework.aop.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;
    }
}

此通知不會更改執行路徑。如果它拋出異常,則拋出的是攔截器鏈,而不是返回值。

後置返回通知可以被用於任何切入點。

引入通知

Spring將引入通知視為一種特殊的攔截通知。

引入需要實現以下接口的IntroductionAdvisorIntroductionInterceptor

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

從AOP Alliance MethodInterceptor接口繼承的invoke()方法必須實現引入。也就是說,如果被調用的方法在引入的接口上,則引入攔截器負責處理方法調用,不能調用proceed()

引入通知不能與任何切入點一起使用,因為它僅適用於類,而不適用於方法級別。你只能通過IntroductionAdvisor使用引入通知,它具有以下方法:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}

沒有MethodMatcher,因此沒有與引入通知相關的Pointcut。只有類過濾是合乎邏輯的。

getInterfaces()方法返回此advisor引入的接口。

在內部使用validateInterfaces()方法來查看引入的接口是否可以由配置的IntroductionInterceptor實現。

考慮一下Spring測試套件中的一個示例,並假設我們想為一個或多個對象引入以下接口:

public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}

這說明了混合。我們希望能夠將通知對象強制轉換為Lockable,無論它們的類型和調用鎖和解鎖方法如何。如果我們調用lock()方法,我們希望所有的setter方法都拋出一個LockedException。因此,我們可以添加一個切面,使對象在不瞭解對象的情況下不可變:AOP的一個很好的例子。

首先,我們需要一個IntroductionInterceptor來完成繁重的工作。在這種情況下,我們擴展了org.springframework.aop.support.DelegatingIntroductionInterceptor便利類。我們可以直接實現IntroductionInterceptor,但是在大多數情況下,最好使用DelegatingIntroductionInterceptor

DelegatingIntroductionInterceptor被設計為將引入委託給所引入接口的實際實現,而隱藏了監聽的使用。你可以使用構造函數參數將委託設置為任何對象。默認委託(使用無參數構造函數時)是this。因此,在下一個示例中,委託是DelegatingIntroductionInterceptorLockMixin子類。給定一個委託(默認情況下為本身),DelegatingIntroductionInterceptor實例將查找由委託實現的所有接口(IntroductionInterceptor除外),並支持針對其中任何一個的引入。諸如LockMixin的子類可以調用suppressInterface(Class intf)方法來禁止不應公開的接口。但是,無論IntroductionInterceptor準備支持多少個接口,IntroductionAdvisor被使用控制實際公開哪些接口。引入的接口隱藏了目標對同一接口的任何實現。

因此,LockMixin擴展了DelegatingIntroductionInterceptor並實現了Lockable本身。超類會自動選擇可以支持Lockable進行引入的方法,因此我們不需要指定它。我們可以通過這種方式引入任意數量的接口。

注意locked實例變量的使用。這有效地將附加狀態添加到目標對象中保存。

下面的示例顯示LockMixin類:

public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
            throw new LockedException();
        }
        return super.invoke(invocation);
    }

}

通常,你無需重寫invoke()方法。通常足以滿足DelegatingIntroductionInterceptor實現(如果引入了方法,則調用委託方法,否則進行到連接點)。在當前情況下,我們需要添加一個檢查:如果處於鎖定模式,則不能調用任何setter方法。

所需的引入只需要保存一個不同的LockMixin實例並指定引入的接口(在本例中,僅為Lockable)。一個更復雜的示例可能引用了引入攔截器(將被定義為原型)。在這種情況下,沒有與LockMixin相關的配置,因此我們使用new創建它。以下示例顯示了我們的LockMixinAdvisor類:

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}

我們可以非常簡單地應用此advisor程序,因為它不需要配置。(但是,如果沒有IntroductionAdvisor,則無法使用IntroductionInterceptor。)像通常的介紹一樣,advisor必須是按實例的,因為它是有狀態的。對於每個被通知的對象,我們需要一個LockMixinAdvisor的不同實例,因此也需要LockMixin的不同實例。advisor包含被通知對象狀態的一部分。我們可以像其他任何advisor一樣,通過使用Advised.addAdvisor()方法或XML配置(推薦方式)以編程方式應用此advisor。下文討論的所有代理創建選擇,包括“自動代理創建器”,都可以正確處理引入和有狀態的混合。

6.3 在Spring中的Advisor API

在Spring中,Advisor是隻包含一個與切入點表達式關聯的通知對象的切面。

除了介紹的特殊情況外,任何advisor都可以與任何通知一起使用。org.springframework.aop.support.DefaultPointcutAdvisor是最常用的advisor類。它可以與MethodInterceptorBeforeAdviceThrowsAdvice一起使用。

可以在Spring中將advisoradvice類型混合在同一個AOP代理中。在一個代理配置中,可以使用環繞通知、異常通知和前置通知的攔截。Spring自動創建必要的攔截器鏈。

6.4 使用ProxyFactoryBean創建AOP代理

如果你的業務對象使用Spring IoC容器(一個ApplicationContextBeanFactory)(你應該這樣做!),那麼你想使用Spring的AOP FactoryBean實現之一。(請記住,工廠bean引入了一個間接層,使它可以創建其他類型的對象。)

Spring AOP支持還在後臺使用了工廠bean。

在Spring中創建AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。這樣可以完全控制切入點,任何適用的通知及其順序。但是,如果不需要這樣的控制,則有一些更簡單的選項比較可取。

6.4.1 基礎

像其他Spring FactoryBean實現一樣,ProxyFactoryBean引入了一個間接級別。如果定義一個名為fooProxyFactoryBean,則引用foo的對象將看不到ProxyFactoryBean實例本身,而是看到由ProxyFactoryBean中的getObject()方法的實現創建的對象。此方法創建一個包裝目標對象的AOP代理。

使用ProxyFactoryBean或另一個支持IoC的類創建AOP代理的最重要好處之一是通知和切入點也可以由IoC管理。這是一項強大的功能,可以實現某些其他AOP框架難以實現的方法。例如,通知本身可以引用應用程序對象(除了目標對象,它應該在任何AOP框架中都可用),這得益於依賴注入提供的所有可插入性。

6.4.2 JavaBean屬性

與Spring提供的大多數FactoryBean實現一樣,ProxyFactoryBean類本身也是一個JavaBean。其屬性用於:

  • 指定要代理的目標。
  • 指定是否使用CGLIB(稍後介紹,另請參見基於JDK和CGLIB的代理)。

一些關鍵屬性繼承自org.springframework.aop.framework.ProxyConfig (Spring中所有AOP代理工廠的超類)。這些關鍵屬性包括:

  • proxyTargetClass:如果要替代目標類而不是目標類的接口,則為true。如果此屬性值設置為true,則將創建CGLIB代理(另請參見基於JDK和CGLIB的代理)。
  • optimize: 控制主動優化是否應用於通過CGLIB創建的代理。除非你完全瞭解相關的AOP代理如何處理優化,否則不要隨意使用此設置。當前僅用於CGLIB代理。它對JDK動態代理無效。
  • frozen: 如果代理配置被凍結,則不再允許對配置進行更改。當你不希望調用者在創建代理後(通過已通知接口)能夠操作代理時,這對於輕微的優化是非常有用的。此屬性的默認值為false,因此允許進行更改(例如添加其他通知)。
  • exposeProxy:確定當前代理是否應該在ThreadLocal中暴露,以便目標可以訪問它。如果目標需要獲取代理,並且暴露代理屬性設置為true,則目標可以使用AopContext.currentProxy()方法。

ProxyFactoryBean特有的其他屬性包括:

  • proxyInterfaces:字符串接口名稱的數組。如果未提供,則使用目標類的CGLIB代理(另請參見基於JDK和CGLIB的代理)。
  • interceptorNames: Advisor,攔截器或要應用的其他通知名稱的字符串數組。順序很重要,先到先得。也就是說,列表中的第一個攔截器是第一個能夠攔截調用的攔截器。
  • 你可以在攔截器名稱後加上星號(*)。這樣做會導致所有advisor bean的應用程序的名稱都以要應用的星號之前的部分開頭。你可以在使用Global Advisors中找到使用此特性的示例。
  • singleton:無論getObject()方法被調用的頻率如何,工廠是否應返回單個對象。一些FactoryBean實現提供了這種方法。默認值是true。如果你想使用有狀態通知—例如,有狀態混合—使用原型通知和單例值false
6.4.3 基於JDK和CGLIB代理

本部分是有關ProxyFactoryBean如何選擇為特定目標對象(將被代理)創建基於JDK的代理或基於CGLIB的代理的權威性文檔。

在Spring的1.2.x版和2.0版之間,ProxyFactoryBean的行為與創建基於JDK或CGLIB的代理有關。現在,ProxyFactoryBean在自動檢測接口方面展示了與TransactionProxyFactoryBean類類似的語義。

如果要代理的目標對象的類(以下簡稱為目標類)沒有實現任何接口,則創建基於CGLIB的代理。這是最簡單的情況,因為JDK代理是基於接口的,並且沒有接口意味著甚至無法進行JDK代理。你可以插入目標bean並通過設置interceptorNames屬性來指定攔截器列表。請注意,即使ProxyFactoryBeanproxyTargetClass屬性已設置為false,也會創建基於CGLIB的代理。(這樣做沒有任何意義,最好將其從bean定義中刪除,因為它充其量是多餘,並且在最糟的情況下會造成混淆。)

如果目標類實現一個(或多個)接口,則創建的代理類型取決於ProxyFactoryBean的配置。

如果ProxyFactoryBeanproxyTargetClass屬性已設置為true,則將創建基於CGLIB的代理。這很有道理,也符合最小意外原則。即使ProxyFactoryBeanproxyInterfaces屬性被設置為一個或多個完全限定的接口名,proxyTargetClass屬性被設置為true也會使基於cglib的代理生效。

如果ProxyFactoryBeanproxyInterfaces屬性被設置為一個或多個完全限定的接口名稱,那麼將創建一個基於jdk的代理。創建的代理實現了proxyInterfaces屬性中指定的所有接口。如果目標類碰巧實現了比proxyInterfaces屬性中指定的更多的接口,那也沒什麼問題,但是那些額外的接口不是由返回的代理實現的。

如果沒有設置ProxyFactoryBeanproxyInterfaces屬性,但是目標類實現了一個(或多個)接口,那麼ProxyFactoryBean會自動檢測到目標類確實實現了至少一個接口,並創建一個基於jdk的代理。實際代理的接口是目標類實現的所有接口。實際上,這與將目標類實現的每個接口的列表提供給proxyInterfaces屬性相同。然而,這大大減少了工作量,也不容易出現書寫錯誤。

6.4.4 代理接口

考慮一個簡單的ProxyFactoryBean操作示例。此示例涉及:

  • 代理的目標bean。這是示例中的personTarget bean定義。
  • 用於提供通知的Advisor和攔截器。
  • 一個用於指定目標對象(personTarget bean)、代理接口和應用通知的AOP代理bean定義。

以下清單顯示了示例:

<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name" value="Tony"/>
    <property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person"
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>

    <property name="target" ref="personTarget"/>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

注意,interceptorNames屬性接受一個字符串列表,其中包含當前工廠中的攔截器或advisors的bean名稱。你可以使用advisors、攔截器、前置通知、後置通知和異常通知對象。advisors的順序很重要。

你可能想知道為什麼列表不保存bean引用。這樣做的原因是,如果ProxyFactoryBeansingleton屬性被設置為false,那麼它必須能夠返回獨立的代理實例。如果任何advisors本身是原型,則需要返回一個獨立的實例,因此必須能夠從工廠獲得原型的實例。

可以使用前面顯示的person Bean定義代替Person實現,如下所示:

Person person = (Person) factory.getBean("person");

與普通Java對象一樣,在同一IoC上下文中的其他bean可以表達對此的強類型依賴性。以下示例顯示瞭如何執行此操作:

<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person"/></property>
</bean>

在此示例中,PersonUser類暴露了Person類型的屬性。就其本身而言,AOP代理可以透明地代替真person實現。但是,其類將是動態代理類。可以將其轉換為Advised接口(稍後討論)。

你可以使用匿名內部bean隱藏目標和代理之間的區別。僅ProxyFactoryBean定義不同。該建議僅出於完整性考慮。以下示例顯示瞭如何使用匿名內部Bean:

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

使用匿名內部bean的優點是隻有一個Person類型的對象。如果我們想防止應用程序上下文的用戶獲得對未通知對象的引用,或者需要避免使用Spring IoC自動裝配產生歧義,這是很有用的。可以說,還有一個優點是ProxyFactoryBean定義是獨立的。然而,有時候,能夠從工廠獲得未通知的目標實際上可能是一種優勢(例如,在某些測試場景中)。

6.4.5 代理類

如果需要代理一個類,而不是一個或多個接口怎麼辦?

想象一下,在我們之前的示例中,沒有Person接口。我們需要通知一個名為Person的類,該類沒有實現任何業務接口。在這種情況下,你可以將Spring配置為使用CGLIB代理而不是動態代理。為此,請將前面顯示的ProxyFactoryBeanproxyTargetClass屬性設置為true。雖然最好是根據接口而不是類編程,但在處理遺留代碼時,通知沒有實現接口的類的能力可能很有用。(一般來說,Spring是沒有規定性的。雖然它使應用良好實踐變得容易,但它避免了強制使用特定的方式或方法。)

如果需要,即使有接口,也可以在任何情況下強制使用CGLIB。

CGLIB代理通過在運行時生成目標類的子類來工作。Spring配置此生成的子類以將方法調用委託給原始目標。子類用於實現Decorator模式,並編織在通知中。

CGLIB代理通常應對用戶透明。但是,有一些問題要考慮:

  • final 的方法不能被通知,因為它們不能被覆蓋(備註:子類不能覆蓋被final標記方法)。
  • 無需將CGLIB添加到你的類路徑中。從Spring 3.2開始,CGLIB被重新打包幷包含在spring-core JAR中。換句話說,基於CGLIB的AOP就像JDK動態代理一樣“開箱即用”。

CGLIB代理和動態代理之間幾乎沒有性能差異。

在這種情況下,性能不應作為決定性的考慮因素。

6.4.6 使用全局Advisors

通過向攔截器名稱附加星號,所有具有與星號之前的部分相匹配的bean名稱的advisor都會被添加到advisor鏈中。如果你需要添加一組標準的全局advisor,這將非常有用。以下示例定義了兩個全局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"/>

參考代碼:com.liyong.ioccontainer.starter.ProxyFactoryBeanIocContainer

6.5 簡潔的代理定義

特別是在定義事務代理時,你可能會得到許多類似的代理定義。使用父bean和子bean定義以及內部bean定義可以產生更乾淨、更簡潔的代理定義。

首先,我們為代理創建父模板,bean定義,如下所示:

<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

它本身從未實例化,因此實際上可能是不完整的。然後,需要創建的每個代理都是一個子bean定義,它將代理的目標包裝為一個內部bean定義,因為無論如何目標都不會單獨使用。以下示例顯示了這樣的子bean:

<bean id="myService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MyServiceImpl">
        </bean>
    </property>
</bean>

你可以從父模板覆蓋屬性。在以下示例中,我們將覆蓋事務傳播設置:

<bean id="mySpecialService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MySpecialServiceImpl">
        </bean>
    </property>
    <property name="transactionAttributes">
        <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

請注意,在父bean的示例中,我們通過將abstract屬性設置為true來將父bean定義顯式標記為抽象,如前所述,因此實際上可能不會實例化它。默認情況下,應用程序上下文(但不是簡單的bean工廠)預實例化所有單例。因此,重要的是(至少對於單例bean),如果你有一個(父)bean定義僅打算用作模板,並且此定義指定了一個類,則必須確保將abstract屬性設置為true。否則,應用程序上下文實際上會嘗試對其進行實例化。

6.6 通過ProxyFactory編程式地創建AOP代理

使用Spring以編程方式創建AOP代理很容易。這使你可以使用Spring AOP,而無需依賴Spring IoC。

由目標對象實現的接口將被自動代理。以下清單顯示了使用一個攔截器和一個advisor為目標對象創建代理的過程:

ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

第一步是構造一個類型為org.springframework.aop.framework.ProxyFactory的對象。你可以使用目標對象(如前面的示例所示)來創建它,也可以在替代構造函數中指定要代理的接口。

你可以添加通知(攔截器是一種專門的通知)、advisors,或者同時添加它們,並在ProxyFactory的生命週期中操作它們。如果添加了IntroductionInterceptionAroundAdvisor,則可以使代理實現其他接口。

ProxyFactory上還有一些方便的方法(繼承自AdvisedSupport),可以添加其他通知類型,比如beforethrow adviceAdvisedSupportProxyFactoryProxyFactoryBean的超類。

在大多數應用程序中,將AOP代理創建與IoC框架集成在一起是最佳實踐。通常,建議你使用AOP從Java代碼外部化配置。

6.7 操作通知對象

無論如何創建AOP代理,都可以通過使用org.springframework.aop.framework.Advised接口來操作它們。任何AOP代理都可以轉換到這個接口,不管它實現了哪個接口。該接口包含以下方法:

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();

getAdvisors()方法為已添加到工廠的每個advisor、攔截器或其他通知類型返回一個advisor。如果添加了advisor,則此索引處返回的advisor是你添加的對象。如果添加了攔截器或其他通知類型,Spring會將其包裝在帶有指向總是返回true的切入點的advisor中。因此,如果你添加一個MethodInterceptor,為這個索引返回的advisor是一個DefaultPointcutAdvisor,它返回你的MethodInterceptor和一個匹配所有類和方法的切入點。

addAdvisor()方法可用於添加任何Advisor。通常,持有切入點和通知的advisor是通用的DefaultPointcutAdvisor,你可以將其用於任何通知或切入點(但不用於introduction)。

默認情況下,即使已創建代理,也可以添加或刪除advisor或攔截器。唯一的限制是不可能添加或刪除一個introduction advisor,因為工廠中的現有代理不會顯示接口更改。(你可以從工廠獲取新的代理來避免此問題。)

以下示例顯示了將AOP代理投射到Advised接口並檢查和處理其通知:

Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());

// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);

在生產中修改業務對象上的通知是否可取(沒有雙關語)值得懷疑,儘管毫無疑問存在合法的使用案例。但是,它在開發中(例如在測試中)非常有用。有時我們發現以攔截器或其他通知的形式添加測試代碼,並進入我們要測試的方法調用中非常有用。(例如,在標記回滾事務之前,通知可以進入為該方法創建的事務,可能是為了運行SQL來檢查數據庫是否被正確更新。)

根據創建代理的方式,通常可以設置凍結標誌。在這種情況下,Advised isFrozen()方法返回true,並且任何通過添加或刪除來修改通知的嘗試都會導致AopConfigException。凍結已通知對象狀態的能力在某些情況下非常有用(例如,防止調用代碼刪除安全攔截器)。

6.8 使用“自動代理”功能

到目前為止,我們已經考慮過通過使用ProxyFactoryBean或類似的工廠bean來顯式創建AOP代理。

Spring還允許我們使用“自動代理” Bean定義,該定義可以自動代理選定的Bean定義。它構建在Spring的bean後處理器基礎設施上,該基礎設施允許在裝載容器時修改任何bean定義。

在這個模型中,你在XML bean定義文件中設置了一些特殊的bean定義來配置自動代理基礎設施。這使你可以聲明有資格進行自動代理的目標。你無需使用ProxyFactoryBean

有兩種方法可以做到這一點:

  • 通過使用在當前上下文中引用特定bean的自動代理創建器。
  • 自動代理創建的一個特殊情況值得單獨考慮:由源碼級別元數據屬性驅動的自動代理創建。
6.8.1 自定代理Bean定義

本節介紹了org.springframework.aop.framework.autoproxy包提供的自動代理創建器。

BeanNameAutoProxyCreator

BeanNameAutoProxyCreator類是一個BeanPostProcessor,可以自動為名稱與文字值或通配符匹配的bean創建AOP代理。以下示例顯示瞭如何創建BeanNameAutoProxyCreator bean:

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="jdk*,onlyJdk"/>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>

ProxyFactoryBean一樣,有一個interceptorNames屬性而不是一列攔截器,以允許原型advisors的正確行為。名為“攔截器”的可以是advisors或任何通知類型。

一般而言,與自動代理一樣,使用BeanNameAutoProxyCreator的要點是將相同的配置一致地應用於多個對象,並且配置量最少。將聲明式事務應用於多個對象是一種流行的選擇。

名稱匹配的Bean定義,例如前面示例中的jdkMyBeanonlyJdk,是帶有目標類的普通舊Bean定義。BeanNameAutoProxyCreator自動創建一個AOP代理。相同的通知適用於所有匹配的bean。注意,如果使用了advisors(而不是前面的示例中的攔截器),則切入點可能會不同地應用於不同的bean。

DefaultAdvisorAutoProxyCreator

DefaultAdvisorAutoProxyCreator是更通用,功能極其強大的自動代理創建器。這將自動在當前上下文中應用合格的advisor,而不需要在自動代理advisor bean定義中包含特定的bean名稱。與BeanNameAutoProxyCreator一樣,它具有一致的配置和避免重複的優點。

使用此機制涉及:

  • 指定DefaultAdvisorAutoProxyCreator bean定義。
  • 在相同或關聯的上下文中指定任何數量的advisor。請注意,這些必須是advisor,而不是攔截器或其他通知。這是必要的,因為必須有一個要評估的切入點來檢查每個通知到候選bean定義的資格。DefaultAdvisorAutoProxyCreator自動評估每個advisor中包含的切入點,以查看應該將什麼(如果有的話)通知應用到每個業務對象(例如示例中的businessObject1businessObject2)。

這意味著可以將任意數量的advisor自動應用於每個業務對象。如果任何advisor中沒有切入點匹配業務對象中的任何方法,則該對象不會被代理。當為新的業務對象添加Bean定義時,如有必要,它們會自動被代理。

通常,自動代理的優點是使調用者或依賴者無法獲得未通知的對象。在此ApplicationContext上調用getBean(“ businessObject1”)會返回AOP代理,而不是目標業務對象。(前面顯示的“ inner bean”也提供了這一好處。)

以下示例創建一個DefaultAdvisorAutoProxyCreator bean和本節中討論的其他元素:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>

<bean id="businessObject1" class="com.mycompany.BusinessObject1">
    <!-- Properties omitted -->
</bean>

<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>

如果要將相同的通知一致地應用於許多業務對象,則DefaultAdvisorAutoProxyCreator非常有用。一旦基礎設施定義就位,你就可以添加新的業務對象,而不包括特定的代理配置。你還可以很容易地刪除其他切面(例如,跟蹤或性能監視切面),只需對配置進行最小的更改。DefaultAdvisorAutoProxyCreator支持過濾(通過使用命名約定,只有特定的advisor被評估,這允許在同一個工廠中使用多個不同配置的AdvisorAutoProxyCreators)和排序。Advisor可以實現org.springframework.core.Ordered接口,以確保在出現問題時可以正確排序。前面示例中使用的TransactionAttributeSourceAdvisor具有可配置的順序值。默認設置為無序。

參考代碼:com.liyong.ioccontainer.starter.AdvisorAutoProxyCreatorIocContainer

6.9 使用TargetSource實現

Spring提供了TargetSource的概念,以org.springframework.aop.TargetSource接口表示。該接口負責返回實現連接點的“目標對象”。每當AOP代理處理方法調用時,就會向TargetSource實現詢問目標實例。

使用Spring AOP的開發人員通常不需要直接使用TargetSource實現,但是這提供了支持池、熱交換和其他複雜目標的強大方法。例如,通過使用池來管理實例,TargetSource可以為每次調用返回不同的目標實例。

如果未指定TargetSource,則將使用默認實現包裝本地對象。每次調用都返回相同的目標(與你期望的一樣)。

本節的其餘部分描述了Spring隨附的標準目標源以及如何使用它們。

使用自定義目標源時,目標通常需要是原型而不是單例bean定義。這樣,Spring可以在需要時創建一個新的目標實例。

6.9.1 可熱交換目標源

org.springframework.aop.target.HotSwappableTargetSource的存在讓AOP代理目標被切換,同時讓調用者保持對它的引用。

改變目標源的目標立即生效。HotSwappableTargetSource是線程安全的。

你可以在HotSwappableTargetSource上通過使用swap()方法改變目標,類似下面例子展示:

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);

下面的例子顯示所需要的XML定義:

<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="swapper"/>
</bean>

前面的swap()調用更改了可交換bean的目標。持有對該bean的引用的客戶端不知道更改,但會立即開始達到新目標。

儘管這個示例沒有添加任何通知(使用TargetSource不需要添加通知),但是可以將任何TargetSource與任意通知結合使用。

6.9.2 池目標源

使用池目標源可以提供類似於無狀態會話ejb的編程模型,其中維護相同實例的池,方法調用將釋放池中的對象。

Spring池和SLSB池之間的關鍵區別在於,Spring池可以應用於任何POJO。與Spring一般情況一樣,可以以非侵入性的方式應用此服務。

Spring提供對 Commons Pool 2.2支持,它提供一個相當地高效池實現。你需要在你的應用類路徑上添加commons-pool jar去使用這個特性。你也可以使用org.springframework.aop.target.AbstractPoolingTargetSource去支持其他的池化API。

還支持Commons Pool 1.5+,但從Spring Framework 4.2開始不推薦使用。

以下清單顯示了一個示例配置:

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
        scope="prototype">
    ... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
    <property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="poolTargetSource"/>
    <property name="interceptorNames" value="myInterceptor"/>
</bean>

請注意,目標對象(在前面的示例中為businessObjectTarget)必須是原型。這使PoolingTargetSource實現可以創建目標的新實例,以根據需要去擴展池中對象。有關其屬性的信息,請參見AbstractPoolingTargetSource的javadoc和希望使用的具體子類maxSize是最基本的,並且始終保證存在。

在這種情況下,myInterceptor是需要在同一IoC上下文中定義的攔截器的名稱。但是,你無需指定攔截器即可使用池。如果只希望池化而沒有其他通知,則完全不要設置interceptorNames屬性。

你可以將Spring配置為能夠將任何池化對象轉換到org.springframework.aop.target.PoolingConfig接口,該接口通過introduction來公開有關池的配置和當前大小的信息。

你需要定義類似於以下內容的advisor

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

通過在AbstractPoolingTargetSource類上調用便捷方法來獲得此advisor,因此可以使用MethodInvokingFactoryBean。該advisor的名稱(在此處為poolConfigAdvisor)必須位於暴露池對象的ProxyFactoryBean中的攔截器名稱列表中。

轉換的定義如下:

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());

池化無狀態的服務對象通常是不必要的。我們不認為它應該是默認選擇,因為大多數無狀態對象自然是線程安全的,並且如果緩存了資源,實例池會成問題。

通過使用自動代理,可以實現更簡單的池化。你可以設置任何自動代理創建者使用的TargetSource實現。

6.9.3 原型目標源

設置“原型”目標源類似於設置池化TargetSource。在這種情況下,每次方法調用都會創建目標的新實例。儘管在現代JVM中創建新對象的成本並不高,但連接新對象(滿足其IoC依賴項)的成本可能會更高。因此,沒有充分的理由就不應使用此方法。

為此,你可以修改前面顯示的poolTargetSource定義,如下所示(為清楚起見,我們也更改了名稱):

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
    <property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

唯一的屬性是目標Bean的名稱。在TargetSource實現中使用繼承來確保命名一致。與池化目標源一樣,目標bean必須是原型bean定義。

6.9.4 ThreadLocal目標源

如果需要為每個傳入請求(每個線程)創建一個對象,則ThreadLocal目標源很有用。ThreadLocal的概念提供了JDK範圍的功能,可以透明地將資源與線程一起存儲。設置ThreadLocalTargetSource幾乎與針對其他類型的目標源所說明的相同,如以下示例所示:

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>

在多線程和多類加載器環境中錯誤地使用ThreadLocal實例時,會帶來嚴重的問題(可能導致內存洩漏)。你應該始終考慮在其他一些類中包裝threadlocal,並且絕對不要直接使用ThreadLocal本身(包裝類中除外)。另外,你應該始終記住正確設置和取消設置線程本地資源的正確設置和取消設置(後者僅涉及對ThreadLocal.set(null)的調用)。在任何情況下都應進行取消設置,因為不取消設置可能會導致出現問題。Spring的ThreadLocal支持為你做到了這一點,應該始終考慮使用ThreadLocal實例,無需其他適當的處理代碼。

6.10 定義新通知類型

Spring AOP被設計為可擴展的。雖然目前在內部使用攔截實現策略,但是除了在環繞通知、前置通知、異常通知以及在返回通知進行攔截外,還可以支持任意的通知類型。

適配器包是一個SPI包,它允許在不更改核心框架的情況下添加對新的自定義通知類型的支持。對自定義Advice類型的唯一限制是它必須實現org.aopalliance.aop.Advice標記接口。

有關更多信息,請參見org.springframework.aop.framework.adapter javadoc。

參考代碼:com.liyong.ioccontainer.starter.TargetSourceIocContainer

作者

個人從事金融行業,就職過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就職於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公眾號和博客站點對知識體系進行分享。關注公眾號:青年IT男 獲取最新技術文章推送!

博客地址: http://youngitman.tech

CSDN: https://blog.csdn.net/liyong1028826685

微信公眾號:

技術交流群:

Leave a Reply

Your email address will not be published. Required fields are marked *