歡迎光臨
每天分享高質量文章

透過現象看原理:詳解 Spring 中 Bean 的 this 呼叫導致 AOP 失效的原因

(點選上方公眾號,可快速關註)


來源:光閃 ,

my.oschina.net/guangshan/blog/1807721

前言

在我們使用Spring時,可能有前輩教導過我們,在bean中不要使用this來呼叫被@Async、@Transactional、@Cacheable等註解標註的方法,this下註解是不生效的。

那麼大家可曾想過以下問題

  1. 為何致this呼叫的方法,註解會不生效

  2. 這些註解生效的原理又是什麼

  3. 如果確實需要呼叫本類方法,且還需要註解生效,該怎麼做?

  4. 代理是否可以做到this呼叫註解就直接生效?

透過本文,上面的疑問都可以解決,而且可以學到很多相關原理知識,資訊量較大,那麼就開始吧

現象

以@Async註解為例,@Async註解標記的方法,在執行時會被AOP處理為非同步呼叫,呼叫此方法處直接傳回,@Async標註的方法使用其他執行緒執行。

使用Spring Boot驅動

@SpringBootApplication

@EnableAsync

public class Starter {

 

    public static void main(String[] args) {

        SpringApplication.run(Starter.class, args);

    }

}

 

@Component

public class AsyncService {

 

    public void async1() {

        System.out.println(“1:” + Thread.currentThread().getName());

        this.async2();

    }

 

    @Async

    public void async2() {

        System.out.println(“2:” + Thread.currentThread().getName());

    }

}

 

@RunWith(SpringRunner.class) 

@SpringBootTest(classes = Starter.class)

public class BaseTest {

 

    @Autowired

    AsyncService asyncService;

 

    @Test

    public void testAsync() {

        asyncService.async1();

        asyncService.async2();

    }

}

輸出內容為:

1:main

2:main

2:SimpleAsyncTaskExecutor-2

第一行第二行對應async1()方法,第三行對應async2()方法,可以看到直接使用asyncService.async2()呼叫時使用的執行緒為SimpleAsyncTaskExecutor,而在async1()方法中使用this呼叫,結果卻是主執行緒,原呼叫執行緒一致。這說明@Async在this呼叫時沒有生效。

思考&猜測

已知對於AOP動態代理,非介面的類使用的是基於CGLIB的動態代理,而CGLIB的動態代理,是基於現有類建立一個子類,並實體化子類物件。在呼叫動態代理物件方法時,都是先呼叫子類方法,子類方法中使用方法增強Advice或者攔截器MethodInterceptor處理子類方法呼叫後,選擇性的決定是否執行父類方法。

那麼假設在呼叫async1方法時,使用的是動態生成的子類的實體,那麼this其實是基於動態代理的子類實體物件,this呼叫是可以被Advice或者MethodInterceptor等處理邏輯攔截的,那麼為何理論和實際不同呢?

這裡大膽推測一下,其實async1方法中的this不是動態代理的子類物件,而是原始的物件,故this呼叫無法透過動態代理來增強。

關於上面AOP動態代理使用CGLIB相關的只是,可以參考完全讀懂Spring框架之AOP實現原理這篇文章。

https://my.oschina.net/guangshan/blog/1797461

下麵開始詳細分析。

原始碼除錯分析原理

首先要弄清楚@Async是如何生效的:

1. 分析Async相關元件

從生效入口開始看,@EnableAsync註解上標註了@Import(AsyncConfigurationSelector.class)

@Import的作用是把後面的@Configuration類、ImportSelector類或者ImportBeanDefinitionRegistrar類中import的內容自動註冊到ApplicationContext中。關於這三種可Import的類,這裡先不詳細說明,有興趣的讀者可以自行去Spring官網檢視檔案或者等待我的後續文章。

這裡匯入了AsyncConfigurationSelector,而AsyncConfigurationSelector在預設情況下,會選擇出來ProxyAsyncConfiguration類進行匯入,即把ProxyAsyncConfiguration類作為@Configuration類配置到ApplicationContext中。那麼這裡的關鍵就是ProxyAsyncConfiguration類,看程式碼

@Configuration

@Role(BeanDefinition.ROLE_INFRASTRUCTURE)

public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {

 

    @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)

    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)

    public AsyncAnnotationBeanPostProcessor asyncAdvisor() {

        Assert.notNull(this.enableAsync, “@EnableAsync annotation metadata was not injected”);

        AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();

        Class extends Annotation> customAsyncAnnotation = this.enableAsync.getClass(“annotation”);

        if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, “annotation”)) {

            bpp.setAsyncAnnotationType(customAsyncAnnotation);

        }

        if (this.executor != null) {

            bpp.setExecutor(this.executor);

        }

        if (this.exceptionHandler != null) {

            bpp.setExceptionHandler(this.exceptionHandler);

        }

        bpp.setProxyTargetClass(this.enableAsync.getBoolean(“proxyTargetClass”));

        bpp.setOrder(this.enableAsync.getNumber(“order”));

        return bpp;

    }

 

}

這段程式碼的作用是把AsyncAnnotationBeanPostProcessor作為Bean註冊到Context中。那麼核心就是把AsyncAnnotationBeanPostProcessor這個BeanPostProcessor,也就是Spring大名鼎鼎的BPP。

在一個Bean實體生成後,會交給BPP的postProcessBeforeInitialization方法進行加工,此時可以傳回與此Bean相相容的其他Bean實體,例如最常見的就是在這裡傳回原物件的動態代理物件。

在這個方法執行後,會呼叫Bean實體的init相關方法。呼叫的方法是InitializingBean介面的afterPropertiesSet方法,以及@Bean宣告中initMethod指定的初始化方法。

在呼叫init方法之後,會呼叫BPP的postProcessAfterInitialization方法進行後置處理。此時處理同postProcessBeforeInitialization,也可以替換原bean的實體。

我們看下這個Async相關的BPP做了什麼操作:

// 潛質處理不做任何動作,可保證在呼叫bean的init之前,bean本身沒有任何變化。

@Override

public Object postProcessBeforeInitialization(Object bean, String beanName) {

    return bean;

}

 

@Override

public Object postProcessAfterInitialization(Object bean, String beanName) {

    // 如果是AOP相關的基礎元件bean,如ProxyProcessorSupport類及其子類,則直接傳回。

    if (bean instanceof AopInfrastructureBean) {

        // Ignore AOP infrastructure such as scoped proxies.

        return bean;

    }

 

    if (bean instanceof Advised) {

        // 如果已經是Advised的,即已經是被動態代理的實體,則直接新增advisor。

        Advised advised = (Advised) bean;

        if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {

            // 如果沒有被frozen(即冷凍,不再做改動的動態代理實體)且是Eligbile(合適的),則把其新增到advisor中。根據配置決定插入位置。

            // Add our local Advisor to the existing proxy’s Advisor chain…

            if (this.beforeExistingAdvisors) {

                advised.addAdvisor(0, this.advisor);

            }

            else {

                advised.addAdvisor(this.advisor);

            }

            return bean;

        }

    }

 

    if (isEligible(bean, beanName)) {

        // 如果是Eligible合適的,且還不是被代理的類,則建立一個代理類的實體並傳回。

        ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);

        if (!proxyFactory.isProxyTargetClass()) {

            evaluateProxyInterfaces(bean.getClass(), proxyFactory);

        }

        proxyFactory.addAdvisor(this.advisor);

        customizeProxyFactory(proxyFactory);

        return proxyFactory.getProxy(getProxyClassLoader());

    }

 

    // No async proxy needed.

    return bean;

}

// 準備ProxyFactory物件

protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {

    ProxyFactory proxyFactory = new ProxyFactory();

    proxyFactory.copyFrom(this);

    // 設定被代理的bean為target,這個bean是真實的bean。

    proxyFactory.setTarget(bean);

    return proxyFactory;

}

Spring在對一個類進行AOP代理後,會為此類加上Advised介面,傳回的動態代理物件都會帶上Advised介面修飾,那麼第一段邏輯判斷bean instanceof Advised的目的就是判斷是否已經是被動態代理的類,如果是,則為其新增一個Advisor增強器。

如果不是動態代理的物件,因為@Async要為方法增加代理,並轉換為非同步執行,故需要把原始bean轉換為被AOP動態代理的bean。也就是下麵的邏輯。

有關上面這一段程式碼建立動態代理的詳細原理,請參考我的這篇文章:完全讀懂Spring框架之AOP實現原理。

關於@Async再多提一點:上面註冊進去的advisor型別是AsyncAnnotationAdvisor。其中包括了PointCut,型別是AnnotationMatchingPointcut,指定了只有@Async標記的方法或者類此AOP增強器才生效。還有一個Advice,用於增強@Async標記的方法,轉換為非同步,型別是AnnotationAsyncExecutionInterceptor,其中的invoke方法是真正呼叫真實方法的地方,大家有興趣可以仔細研究其中的內容,這樣就能摸清楚@Async方法的真實執行邏輯了。

相關元件上面都已經提及併進行了簡單的分析,現在我們進入下一階段,透過真正的執行邏輯來分析this呼叫不生效的原因。

2. 深入真實呼叫邏輯

@Async大多數都是標記的類中的方法,故AOP的實現也多是基於CGLIB的,下麵以CGLIB動態代理為例分析真實呼叫邏輯。

透過完全讀懂Spring框架之AOP實現原理這篇文章,可以得知,一個基於CGLIB的AOP動態代理bean,真實的執行邏輯是在DynamicAdvisedInterceptor中:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

    Object oldProxy = null;

    boolean setProxyContext = false;

    Class > targetClass = null;

    Object target = null;

    try {

        if (this.advised.exposeProxy) {

            // 需要則暴露

            // Make invocation available if necessary.

            oldProxy = AopContext.setCurrentProxy(proxy);

            setProxyContext = true;

        }

        // May be null. Get as late as possible to minimize the time we

        // “own” the target, in case it comes from a pool…

        // 重點:獲取被代理的標的物件

        target = getTarget();

        if (target != null) {

            targetClass = target.getClass();

        }

        // 獲取攔截器鏈

        Listchain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        Object retVal;

        // Check whether we only have one InvokerInterceptor: that is,

        // no real advice, but just reflective invocation of the target.

        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {

            // We can skip creating a MethodInvocation: just invoke the target directly.

            // Note that the final invoker must be an InvokerInterceptor, so we know

            // it does nothing but a reflective operation on the target, and no hot

            // swapping or fancy proxying.

            // 如果鏈是空且是public方法,則直接呼叫

            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);

            retVal = methodProxy.invoke(target, argsToUse);

        }

        else {

            // We need to create a method invocation…

            // 否則建立一個CglibMethodInvocation以便驅動攔截器鏈

            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();

        }

        // 處理傳回值,同JDK動態代理

        retVal = processReturnType(proxy, target, method, retVal);

        return retVal;

    }

    finally {

        if (target != null) {

            releaseTarget(target);

        }

        if (setProxyContext) {

            // Restore old proxy.

            AopContext.setCurrentProxy(oldProxy);

        }

    }

}

註意上面真實呼叫的部分,在沒有advisor的情況下,使用的其實是:

methodProxy.invoke(target, argsToUse)

在有代理的情況下,使用的是:

new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();

而在CglibMethodInvocation中,檢查到呼叫鏈執行完之後,會呼叫真實的方法:invokeJoinpoint。在CglibMethodInvocation中,該方法的實現是

// CglibMethodInvocation中的實現

protected Object invokeJoinpoint() throws Throwable {

    if (this.publicMethod) {

        return this.methodProxy.invoke(this.target, this.arguments);

    }

    else {

        return super.invokeJoinpoint();

    }

}

// 父類實現是

protected Object invokeJoinpoint() throws Throwable {

    return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);

}

可以看到呼叫方法時,傳入的實體都是target,這個target是從DynamicAdvisedInterceptor的getTarget方法中獲得的,程式碼如下

protected Object getTarget() throws Exception {

    return this.advised.getTargetSource().getTarget();

}

而這個advised的target則是在ProxyFactory的實體方法中設定的:proxyFactory.setTarget(bean);

也就是說這個target其實是真實的被代理的bean。

透過上面的分析,我們可以得到結論,在一個被動態代理的物件,在執行完AOP所有的增強邏輯之後,最終都會使用被代理物件作為實體呼叫真實的方法,即相當於呼叫了:target.method()方法。由此得出結論,在target.method()方法中,this取用必然是target自身,而不是生成的動態代理物件實體。

補充一下,Spring在建立一個Bean之後,對其包裝並生成動態代理物件都是後置的舉動,故會先生成真實類的實體bean,再動態建立動態代理bean,在動態代理bean中,會持有真實的bean的實體。

就拿最上面的@Async程式碼實體舉例,我們可以看到this其實是AsyncService的原始實體,而不是代理物件實體:

總結: 因為AOP動態代理的方法真實呼叫,會使用真實被代理物件實體進行方法呼叫,故在實體方法中透過this獲取的都是被代理的真實物件的實體,而不是代理物件自身。

3. 解決this呼叫的幾個替代方法

既然已知原因,那麼解決的方法就有定向了,核心就是如何獲得動態代理物件,而不是使用this去呼叫。

提供以下幾種方法:

1. 透過ApplicationContext來獲得動態代理物件

@Component

public class AsyncService implements ApplicationContextAware {

 

    private ApplicationContext applicationContext;

 

    public void async1() {

        System.out.println(“1:” + Thread.currentThread().getName());

        // 使用AppicationContext來獲得動態代理的bean

        this.applicationContext.getBean(AsyncService.class).async2();

    }

 

    @Async

    public void async2() {

        System.out.println(“2:” + Thread.currentThread().getName());

    }

 

    // 註入ApplicationContext

    @Override

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        this.applicationContext = applicationContext;

    }

}

執行結果是:

1:main

2:SimpleAsyncTaskExecutor-2

2:SimpleAsyncTaskExecutor-3

可以看到完美達到了我們的目的。同理是用BeanFactoryAware可達到同樣的效果。

2. 透過AopContext獲取動態代理物件

@Component

public class AsyncService {

 

    public void async1() {

        System.out.println(“1:” + Thread.currentThread().getName());

        ((AsyncService) AopContext.currentProxy()).async2();

    }

 

    @Async

    public void async2() {

        System.out.println(“2:” + Thread.currentThread().getName());

    }

}

這種做法非常簡潔,但是在預設情況下是不起作用的! 因為AopContext中拿不到currentProxy,會報空指標。

透過上面的動態代理執行原始碼的地方可以看到邏輯:

if (this.advised.exposeProxy) {

    // Make invocation available if necessary.

    oldProxy = AopContext.setCurrentProxy(proxy);

    setProxyContext = true;

}

而在ProxyConfig類中,有如下註釋用來說明exposeProxy的作用,就是用於在方法中獲取動態代理的物件的。

/**

 * Set whether the proxy should be exposed by the AOP framework as a

 * ThreadLocal for retrieval via the AopContext class. This is useful

 * if an advised object needs to call another advised method on itself.

 * (If it uses {@code this}, the invocation will not be advised).

 *

Default is “false”, in order to avoid unnecessary extra interception.

 * This means that no guarantees are provided that AopContext access will

 * work consistently within any method of the advised object.

 */

public void setExposeProxy(boolean exposeProxy) {

    this.exposeProxy = exposeProxy;

}

即只有exposeProxy為true時,才會把proxy動態代理物件設定到AopContext背景關係中,這個配置預設是false。那麼這個配置怎麼修改呢?

在xml時代,我們可以透過配置:

來修改全域性的暴露邏輯。

在基於註解的配置中,我們需要使用

@EnableAspectJAutoProxy(proxyTargteClass = true, exposeProxy = true)

來配置。

遺憾的是,對於@Async,如此配置下依然不能生效。因為@Async使用的不是AspectJ的自動代理,而是使用程式碼中固定的建立代理方式進行代理建立的。

如果是@Transactional事務註解的話, 則是生效的。具體生效機制是透過@EnableTransactionManagement註解中的TransactionManagementConfigurationSelector類宣告,其中宣告匯入了AutoProxyRegistrar類,該類獲取註解中proxy相關註解配置,並根據配置情況,在BeanDefinition中註冊一個可用於自動生成代理物件的AutoProxyCreator:

AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);

public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {

    return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);

}

而在@EnableAspectJAutoProxy註解中,@Import的AspectJAutoProxyRegistrar類又把這個BeanDefinition修改了類,同時修改了其中的exposeProxy屬性。

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {

    return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);

}

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {

    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);

}

後面替換掉了前面的AutoProxyCreator,替換邏輯是使用優先順序替換,優先順序分別為:

APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);

APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);

APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);

這個邏輯都在registerOrEscalateApcAsRequired中,讀者可以自己再看一下。

因為@Transactional註解和AspectJ相關註解的生成動態代理類都是使用的同一個Bean即上面的AutoProxyCreator處理的,該bean的name是org.springframework.aop.config.internalAutoProxyCreator,他們公用相同的屬性,故對於@Transactional來說,@EnableAspectJAutoProxy的屬性exposeProxy=true也是生效的。但是@Async的註解生成的代理類並不是透過這個autoProxyCreator來生成的,故不能享受到上面的配置。

3. 基於上面的原始碼,我們可以得到第三種處理方法

在某個切入時機,手動執行AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);靜態方法,當然前提是有一個BeanDefinitionRegistry,且時機要在BeanDefinition已經建立且動態代理物件還沒有生成時呼叫。

使用這種方式,無需使用@EnableAspectJAutoProxy即可。

這種方式同樣不適用於@Async,適用於@Transactional。

4. 手動修改各種BeanPostProcessor的屬性

以@Async為例,其透過AsyncAnnotationBeanPostProcessor來生成動態代理類,我們只要在合適時機即該BPP已建立,但是還未被使用時,修改其中的exposeProxy屬性,使用AsyncAnnotationBeanPostProcessor.setExposeProxy(true)即可。

這種方式要針對性的設定特定的bean的exposeProxy屬性true。適用於@Async,觀察原理可以知道3和4其實核心都是相同的,就是設定AutoProxyCreater的exposed屬性為true。AsyncAnnotationBeanPostProcessor其實也是一個AutoProxyCreater,他是ProxyProcessorSupport的子類。

對於@Async可以使用1、4方式,對於@Transactional則可以使用這四種任意方式。

歡迎大家補充其他方法。

4. 是否可以做到this呼叫使動態代理生效

基於我們的推測,如果this取用是動態代理物件的話,則this呼叫其實是可以呼叫到父類的方法的,只要呼叫的是父類方法,那麼在父類重寫的方法中加入的動態代理攔截就是可以生效的。此種場景在Spring中是否存在呢?答案是肯定的,就在Spring提供的@Configuration配置類中,就有這種場景的應用,下麵見示例:

@Configuration

public class TestConfig {

     

    @Bean

    public Config config() {

        return new Config();

    }

     

    @Bean

    public ConfigOut configOut() {

        Config c1 = this.config();

        Config c2 = this.config();

        System.out.println(c1 == c2);

        ConfigOut configOut = new ConfigOut(this.config());

        return configOut;

    }

 

    public static class Config {}

     

    public static class ConfigOut {

         

        private Config config;

         

        private ConfigOut(Config config) {

            this.config = config;

        }

         

    }

     

}

在configOut方法中加入斷點,除錯觀察c1與才 的值,也即this.config()傳回的值,可以看到c1和c2是同一個物件取用,而不是每次呼叫方法都new一個新的物件。 

那麼這裡是怎麼做到this呼叫多次都傳回同一個實體的呢?我們繼續跟蹤除錯斷點,檢視整體的呼叫堆疊,發現這個方法configOut的呼叫處以及config方法的真實呼叫處是在ConfigurationClassEnhancer的內部類BeanMethodInterceptor中,為什麼是這個方法呢?因為真實的Configuration類被動態替換為基於CGLIB建立的子類了。而這個@Configuration類的處理,是基於ConfigurationClassPostProcessor這個BeanFactoryPostProcessor處理器來做的,在ConfigurationClassPostProcessor中的postProcessBeanDefinitionRegistry方法中,檢查所有的bean,如果bean是被@Configuration、@Component、@ComponentScan、@Import、@ImportResource其中一個標註的,那麼此類就會被視為Configuration類。在postProcessBeanDefinition方法中,會把@Configuration類動態代理為一個新類,使用CGLIB的enhancer來增強Configuration類。使用ConfigurationClassEnhancer的enhance方法處理為原有類的子類,參考程式碼:

/**

 * Loads the specified class and generates a CGLIB subclass of it equipped with

 * 載入特殊的Configuration類時,為其生成一個CGLIB的子類

 * container-aware callbacks capable of respecting scoping and other bean semantics.

 * 以便實現對@Bean方法的攔截或者增強

 * @return the enhanced subclass

 */

public Class > enhance(Class > configClass, ClassLoader classLoader) {

    if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {

        // 如果已經是被增強的Configuration,則直接跳過

        if (logger.isDebugEnabled()) {

            logger.debug(String.format(“Ignoring request to enhance %s as it has ” +

                    “already been enhanced. This usually indicates that more than one ” +

                    “ConfigurationClassPostProcessor has been registered (e.g. via ” +

                    “). This is harmless, but you may ” +

                    “want check your configuration and remove one CCPP if possible”,

                    configClass.getName()));

        }

        return configClass;

    }

    // 否則生成增強後的新的子類

    Class > enhancedClass = createClass(newEnhancer(configClass, classLoader));

    if (logger.isDebugEnabled()) {

        logger.debug(String.format(“Successfully enhanced %s; enhanced class name is: %s”,

                configClass.getName(), enhancedClass.getName()));

    }

    return enhancedClass;

}

 

/**

 * Creates a new CGLIB {@link Enhancer} instance.

 * 建立增強的CGLIB子類

 */

private Enhancer newEnhancer(Class > superclass, ClassLoader classLoader) {

    Enhancer enhancer = new Enhancer();

    enhancer.setSuperclass(superclass);

    // 增加介面以標記是被增強的子類,同時增加setBeanFactory方法,設定內部成員為BeanFactory。

    enhancer.setInterfaces(new Class >[] {EnhancedConfiguration.class});

    enhancer.setUseFactory(false);

    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);

    // BeanFactoryAwareGeneratorStrategy生成策略為生成的CGLIB類中新增成員變數$$beanFactory

    // 同時基於介面EnhancedConfiguration的父介面BeanFactoryAware中的setBeanFactory方法,設定此變數的值為當前Context中的beanFactory

    // 該BeanFactory的作用是在this呼叫時攔截該呼叫,並直接在beanFactory中獲得標的bean。

    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));

    // 設定CALLBACK_FILTER,

    enhancer.setCallbackFilter(CALLBACK_FILTER);

    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());

    return enhancer;

}

 

// 增強時要使用的filters

// The callbacks to use. Note that these callbacks must be stateless.

private static final Callback[] CALLBACKS = new Callback[] {

        // 用於攔截@Bean方法的呼叫,並直接從BeanFactory中獲取標的bean,而不是透過執行方法。

        new BeanMethodInterceptor(),

        // 用於攔截BeanFactoryAware介面中的setBeanFactory方法的嗲用,以便設定$$beanFactory的值。

        new BeanFactoryAwareMethodInterceptor(),

        // 不做任何操作

        NoOp.INSTANCE

};

 

/**

 * Uses enhancer to generate a subclass of superclass,

 * ensuring that callbacks are registered for the new subclass.

 * 設定callbacks到靜態變數中,因為還沒有實體化,所以只能放在靜態變數中。

 */

private Class > createClass(Enhancer enhancer) {

    Class > subclass = enhancer.createClass();

    // Registering callbacks statically (as opposed to thread-local)

    // is critical for usage in an OSGi environment (SPR-5932)…

    Enhancer.registerStaticCallbacks(subclass, CALLBACKS);

    return subclass;

}

可以看到這裡的callbacks是註冊到生成的子類的static中,這裡只生成class而不實體化。

把此類設定到BeanDefinition中的beanClass屬性中,在BeanDefinition初始化時會自動初始化子類。

上面的關鍵是CALLBACKS、CALLBACK_FILTER,分別代表增強器和增強器的過濾器。

關於Configuration類的CGLIB動態代理建立可以與SpringAOP體系建立的CGLIB動態代理做一個對比,區別是這裡的動態代理的CALLBACKS和CALLBACK_FILTER。

這裡我們以上面提到的BeanMethodInterceptor為例,來說明他的作用,以及this呼叫在這種情況下可以被動態代理攔截的原因。程式碼如下:

/**

 * enhancedConfigInstance: 被CGLIB增強的config類的實體,即CGLIB動態生成的子類的實體

 * beanMethod : @Bean標記的方法,即當前呼叫的方法,這個是透過CallbackFilter的accept方法篩選出來的,只可能是@Bean標註的方法。

 * beanMethodArgs : 方法呼叫的引數

 * cglibMethodProxy : cglib方法呼叫的代理,可以用來直接呼叫父類的真實方法。

*/

@Override

public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,

            MethodProxy cglibMethodProxy) throws Throwable {

    // 透過enhancedConfigInstance中cglib生成的成員變數$$beanFactory獲得beanFactory。

    ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);

    // 確認真實的beanName,用於在beanFactory中獲得bean實體

    String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

 

    // Determine whether this bean is a scoped-proxy

    // 後面這個是確認是否是scoped作用域的bean,這裡暫時不考慮,後續文章詳細分析Scoped相關的邏輯和bean。

    Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);

    if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {

        String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);

        if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {

            beanName = scopedBeanName;

        }

    }

 

    // To handle the case of an inter-bean method reference, we must explicitly check the

    // container for already cached instances.

    // 攔截內部bean方法的呼叫,檢查bean實體是否已經生成

 

    // First, check to see if the requested bean is a FactoryBean. If so, create a subclass

    // proxy that intercepts calls to getObject() and returns any cached bean instance.

    // This ensures that the semantics of calling a FactoryBean from within @Bean methods

    // is the same as that of referring to a FactoryBean within XML. See SPR-6602.

    // 檢查是否是FactoryBean,當是FactoryBean時,即使是this呼叫也不能生成多次

    // 更特殊的,呼叫FactoryBean的getObject方法時,也不能生成多次新的Bean,否則取到的bean就是多個了,有違單例bean的場景。

    // 所以這裡判斷如果當前方法傳回的bean,如果是FactoryBean的話,對FactoryBean進行代理

    // 代理的結果是攔截factoryBean實體的getObject方法,轉化為透過BeanFactory的getBean方法來呼叫

    if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&

            factoryContainsBean(beanFactory, beanName)) {

        // 上面加入BeanFactory.FACTORY_BEAN_PREFIX + beanName用來判斷當前bean是否是一個FactoryBean。在BeanFactory中是透過FACTORY_BEAN_PREFIX字首來區分當前要判斷的標的型別的,

        // 如果是FACTORY_BEAN_PREFIX字首的beanName,則獲取之後會判斷是否是FactoryBean,是則為true,否則為false。

        // 同時還判斷了當前的Bean是否是在建立中,只有不是在建立中,才會傳回true。第一個拿FactoryBean的name去判斷,則肯定不在建立中。第二個的判斷才是真正生效的可判斷出是否在建立中的方法。

        Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);

        // 只有不在建立中,才能呼叫BeanFactory去獲取或者建立,否則會無限遞迴呼叫。

        // 上面的呼叫獲取時,才會進行真正的初始化,實體化時還會再進一次這個方法,但是並不會執行到這個邏輯中,因為再進入時,會被標記為正在建立。真正的初始化時呼叫@Bean方法進行的,是在下麵的邏輯中。

        if (factoryBean instanceof ScopedProxyFactoryBean) {

            // Scoped proxy factory beans are a special case and should not be further proxied

        }

        else {

            // It is a candidate FactoryBean – go ahead with enhancement

            return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);

        }

    }

 

    if (isCurrentlyInvokedFactoryMethod(beanMethod)) {

        // 上面這個用於判斷當前的工廠方法,也就是@Bean標註的方法是否是在呼叫中。如果是在呼叫中,則說明需要真正的實體化了,此時呼叫父類真是方法來建立實體。

        // The factory is calling the bean method in order to instantiate and register the bean

        // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually

        // create the bean instance.

        if (logger.isWarnEnabled() &&

                BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {

            // 如果是BeanFactoryPostProcessor型別的話則提出警告,表明可能並不能正確執行BeanFactoryPostProcessor的方法。

            logger.warn(String.format(“@Bean method %s.%s is non-static and returns an object ” +

                            “assignable to Spring’s BeanFactoryPostProcessor interface. This will ” +

                            “result in a failure to process annotations such as @Autowired, ” +

                            “@Resource and @PostConstruct within the method’s declaring ” +

                            “@Configuration class. Add the ‘static’ modifier to this method to avoid ” +

                            “these container lifecycle issues; see @Bean javadoc for complete details.”,

                    beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));

        }

        // 呼叫父類真實方法實體化。

        return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);

    }

    // 這個方法嘗試從beanFactory中獲得標的bean,這樣便可另所有此方法呼叫獲得bean最終都是從beanFactory中獲得的,達到了單例的目的。

    return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);

    // 在Bean的方法A使用this取用呼叫方法B時,會先進入一次這個方法的邏輯,此時因為還沒真正進行實體化,

    // isCurrentlyInvokedFactoryMethod(beanMethod)得到的結過是false,故會呼叫obtainBeanInstanceFromFactory,此時會從beanFactory中獲得bean。

    // 在獲得Bean時,會再次呼叫B方法,因為這個Bean需要呼叫@Bean的方法才能生成。呼叫前先打上正在呼叫的標記,同時再次進入這個方法邏輯,此時上面判斷isCurrentlyInvokedFactoryMethod結過為true,呼叫父類方法進行真實的實體化。

}

 

/**

 * 該方法為FactoryBean傳回被代理的新實體,新的實體攔截getObject方法,並從beanFactory中獲得單例bean。

 */

private Object enhanceFactoryBean(final Object factoryBean, Class > exposedType,

        final ConfigurableBeanFactory beanFactory, final String beanName) {

 

    try {

        Class > clazz = factoryBean.getClass();

        boolean finalClass = Modifier.isFinal(clazz.getModifiers());

        boolean finalMethod = Modifier.isFinal(clazz.getMethod(“getObject”).getModifiers());

        // 判斷真實FactoryBean的型別和getObject方法,如果是final的,說明不能透過CGLIB代理,則嘗試使用JDK代理

        if (finalClass || finalMethod) {

            if (exposedType.isInterface()) {

                // 如果方法傳回型別,即exposedType是介面,則這個介面一般都是FactoryBean,則透過jdk動態代理建立代理

                if (logger.isDebugEnabled()) {

                    logger.debug(“Creating interface proxy for FactoryBean ‘” + beanName + “‘ of type [” +

                            clazz.getName() + “] for use within another @Bean method because its ” +

                            (finalClass ? “implementation class” : “getObject() method”) +

                            ” is final: Otherwise a getObject() call would not be routed to the factory.”);

                }

                return createInterfaceProxyForFactoryBean(factoryBean, exposedType, beanFactory, beanName);

            }

            else {

                // 不是介面就沒辦法了,只能直接傳回原始的factoryBean,如果在這個factoryBean裡getObject生成了新物件,多次呼叫生成的結果bean將不會是同一個實體。

                if (logger.isInfoEnabled()) {

                    logger.info(“Unable to proxy FactoryBean ‘” + beanName + “‘ of type [” +

                            clazz.getName() + “] for use within another @Bean method because its ” +

                            (finalClass ? “implementation class” : “getObject() method”) +

                            ” is final: A getObject() call will NOT be routed to the factory. ” +

                            “Consider declaring the return type as a FactoryBean interface.”);

                }

                return factoryBean;

            }

        }

    }

    catch (NoSuchMethodException ex) {

        // No getObject() method -> shouldn’t happen, but as long as nobody is trying to call it…

    }

    // 可以使用CGLIB代理類。

    return createCglibProxyForFactoryBean(factoryBean, beanFactory, beanName);

    // 假設A方法呼叫了@Bean的B方法,B方法傳回FactoryBean實體

    // 那麼在A呼叫B時,會先進入BeanMethodInterceptor.intercept方法

    // 在方法中判斷標的bean是一個FactoryBean,且不是在建立中,則呼叫beanFactory的getBean嘗試獲取標的bean。

    // 在獲取的過程中,最終又會執行方法B,此時被攔截再次進入這個intercept方法

    // 由於標記為建立中,故這裡會進入下麵的建立中邏輯,透過invokeSuper呼叫了真實的方法邏輯傳回真實的FactoryBean。

    // 這個真實的FactoryBean傳回之後,在第一次的intercept方法中,對這個FactoryBean實體進行代理,傳回一個被代理的FactoryBean物件給方法A中的邏輯使用,這樣就可以保證在A中呼叫FactoryBean.getObject時拿到的是beanFactory的bean實體了。

}

透過BeanMethodInterceptor.intercept方法,我們可以看到,真實的方法呼叫是透過cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs)來執行的,enhancedConfigInstance是動態代理產生的子類的實體,這裡直接呼叫該物件的父類方法,即相當於呼叫的真實方法,這一點與Spring AOP體系中的把真實物件target作為真實呼叫實體來呼叫是有區別的,也就是這個區別,給this呼叫帶來的上面的特性。

即在這種情況下this都是被CGLIB動態代理產生的子類的實體,在呼叫this.method()時,其實是呼叫了子類實體的該方法,此方法可以被方法攔截器攔截到,在攔截的邏輯中做一定的處理,如果需要呼叫真實物件的相應方法,直接使用invokeSuper來進行父類方法呼叫,而不是傳入真實被動態代理物件的實體來進行呼叫。真實物件其實並沒有建立,也就是說對應於Spring AOP,其中的target是不存在的,只有子類物件動態代理自身的實體,而沒有真實物件實體。

由此我們便明瞭了this呼叫被動態攔截的實現方式。

對於上面Configuration的類的呼叫,可參考如下例子,對比除錯後可以更加深入的理解這個問題。

import org.springframework.beans.factory.FactoryBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

 

@Configuration

public class TestConfig {

 

    @Bean

    public ConfigOut configOut() {

        Config c1 = this.config();

        Config c2 = this.config();

        // 這裡傳回同一個實體

        System.out.println(c1 == c2);

        ConfigOut configOut = new ConfigOut(this.config());

        FactoryBean ob1 = this.objectFactoryBean();

        FactoryBean ob2 = this.objectFactoryBean();

        // 這裡也是 同一個實體

        System.out.println(ob1 == ob2);

        MyObject myObject1 = this.objectFactoryBean().getObject();

        MyObject myObject2 = this.objectFactoryBean().getObject();

        // 如果objectFactoryBean方法傳回型別為FactoryBean則這兩個相同

        // 如果是ObjectFactoryBean則兩個不相同,上面已分析過原因

        System.out.println(myObject1 == myObject2);

        return configOut;

    }

 

    @Bean

    public Config config() {

        return new Config();

    }

 

    @Bean

    public FactoryBean objectFactoryBean() {

        return new ObjectFactoryBean();

    }

 

    public static class Config {}

 

    public static class ConfigOut {

 

        private Config config;

 

        private ConfigOut(Config config) {

            this.config = config;

        }

 

    }

 

    public static final class ObjectFactoryBean implements FactoryBean {

 

        @Override

        public final MyObject getObject() {

            return new MyObject();

        }

 

        @Override

        public Class > getObjectType() {

            return MyObject.class;

        }

 

        @Override

        public boolean isSingleton() {

            return true;

        }

    }

 

    public static class MyObject {}

 

}

後記

本文根據實際場景,詳細的分析了this呼叫導致AOP失效的原因,以及如何解決這個問題。並擴充套件了this呼叫可使AOP生效的場景。只要大家能理解到原理面,應該都能夠分析出來原因。平時一些需要遵守的程式碼規範,在原理層面都是有其表現和原因的,分析真實原因得到最終結論,這個過程是對知識的升華過程,希望大家能夠看到開心。

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂