AOP失效的原因,及解決辦法


  • 解決辦法


import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

@Service
public class XXXServiceImpl implements ApplicationContextAware, XXXService {
private ApplicationContext applicationContext;
  
/**
* 注入ApplicationContext
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public ResponseVO save(Master master) {
  ....(業務方法)
  
//注入將bean
this.applicationContext.getBean(XXXServiceImpl.class);
}
}


 
        

 

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

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

為何致this調用的方法,注解會不生效

這些注解生效的原理又是什么

如果確實需要調用本類方法,且還需要注解生效,該怎么做?

代理是否可以做到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中。

這里導入了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 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

關於@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();

}

// 獲取攔截器鏈

List chain = 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是從DynamicAdvisedInterceptorgetTarget方法中獲得的,代碼如下

 

protected Object getTarget() throws Exception {

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

}

 

而這個advisedtarget則是在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注解中,@ImportAspectJAutoProxyRegistrar類又把這個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初始化時會自動初始化子類。

上面的關鍵是CALLBACKSCALLBACK_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生效的場景。只要大家能理解到原理面,應該都能夠分析出來原因。

平時一些需要遵守的代碼規范,在原理層面都是有其表現和原因的,分析真實原因得到最終結論,這個過程是對知識的升華過程,希望大家能夠看到開心。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM