Skip to content

@EnableAspectJAutoProxy

信息

最近在阅读Spring AOP的源码(基于Spring 5.2.8.RELEASE)中,发现@EnableAspectJAutoProxy注解中的proxyTargetClass参数并不如注释(是否创建基于子类的CGLIB代理)中说所的哪样生效。无论我设置成true/false都会使用CGLIB代理。

自定义配置代码

java
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("org.springframework.chapter13")
public class AopConfiguration {
}

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
                AopConfiguration.class);
        // ....
    }
}

源码解析

问题的切入口

我们知道Spring AOP是基于后置处理器实现对Bean的代理对象的创建,其核心类就是AbstractAutoProxyCreator。所以我们尝试从AOP核心类进行问题的解析。

java
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
    @Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        // 判断bean是否需要被代理
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}
    
    // wrapIfNecessary中会调用此方法创建代理对象
    protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {
		// 省略不相关代码
        // AOP核心判断逻辑
        // 上文我们没有设置proxyTargetClass,所以默认是false
		if (!proxyFactory.isProxyTargetClass()) {
            // 问题的核心入口!!!
            // 判断是否需要代理
			if (shouldProxyTargetClass(beanClass, beanName)) {
                // 设置proxyTargetClass属性为true,用于走cglib代理
				proxyFactory.setProxyTargetClass(true);
			}
			else {
                // 否则走jdk代理的校验逻辑
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}
		// 省略无关代码
        // 调用代理工厂创建代理
		return proxyFactory.getProxy(getProxyClassLoader());
	}
    
    protected void evaluateProxyInterfaces(Class<?> beanClass, ProxyFactory proxyFactory) {
        // 查找目标对象的所有实现接口,并筛选合适的代理接口
		Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(
            					beanClass, getProxyClassLoader());
		boolean hasReasonableProxyInterface = false;
		for (Class<?> ifc : targetInterfaces) {
			if (!isConfigurationCallbackInterface(ifc) && !isInternalLanguageInterface(ifc) &&
					ifc.getMethods().length > 0) {
				hasReasonableProxyInterface = true;
				break;
			}
		}
		if (hasReasonableProxyInterface) {
			// 设置proxyFactory的interface属性,用于实现jdk代理
			for (Class<?> ifc : targetInterfaces) {
				proxyFactory.addInterface(ifc);
			}
		}
		else {
            // 如果当前被代理的目标对象不是基于接口的,那么还是会设置proxyTargetClass=true走CGLIB代理
			proxyFactory.setProxyTargetClass(true);
		}
	}
}

从上面的代码切入,我们知道:

  1. 因为shouldProxyTargetClass返回了true,所以设置了proxyTargetClass=true,从而走了CGLIB代理。
  2. 即使shouldProxyTargetClass返回false,走到了evaluateProxyInterfaces中,也需要找到适合的代理接口(JDK代理基于接口),否则还是会设置proxyTargetClass=true

但我产生了一个疑惑:这个shouldProxyTargetClass()是做什么的,为什么在没有设置proxyTargetClass=true前提下返回了true来走CGLIB代理?

问题的深入

书接上文,针对上面的问题,我们继续阅读相关源码。

java
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
    
    protected boolean shouldProxyTargetClass(Class<?> beanClass, @Nullable String beanName) {
        // 我们是基于AnnotationConfigApplicationContext创建的上下文,默认是其父类         
        // GenericApplicationContext创建了DefaultListableBeanFactory类
        // 而前者是ConfigurableListableBeanFactory的默认实现,所以第一个是true
		return (this.beanFactory instanceof ConfigurableListableBeanFactory &&
				AutoProxyUtils.shouldProxyTargetClass(
                    (ConfigurableListableBeanFactory) this.beanFactory, beanName));
	}    
}

public abstract class AutoProxyUtils {
    
    public static final String PRESERVE_TARGET_CLASS_ATTRIBUTE =
			Conventions.getQualifiedAttributeName(AutoProxyUtils.class, "preserveTargetClass");
    
	public static boolean shouldProxyTargetClass(
			ConfigurableListableBeanFactory beanFactory, @Nullable String beanName) {
        // isNull判断 & 容器中是否包含当前bean
		if (beanName != null && beanFactory.containsBeanDefinition(beanName)) {
            // 如果包含,那么获取BeanDefinition判断是否包含PRESERVE_TARGET_CLASS_ATTRIBUTE属性
			BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
			return Boolean.TRUE.equals(bd.getAttribute(PRESERVE_TARGET_CLASS_ATTRIBUTE));
		}
		return false;
	}
}

阅读完上述代码,我们可以了解到为什么shouldProxyTargetClass()返回了true,因为当前的BeanDefinition中包含了PRESERVE_TARGET_CLASS_ATTRIBUTE属性。那么:这个属性是什么时候设置的呢?

问题继续深入

借助IDEA我们可以知道Spring在ConfigurationClassPostProcessor.enhanceConfigurationClasses()方法中设置了PRESERVE_TARGET_CLASS_ATTRIBUTE属性。

java
// 解析配置类注解的核心后置处理器,启动时注入到容器中
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
                   
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		// 省略无关代码
        // 最终调用了ConfigurationClassUtils.checkConfigurationClassCandidate设置了		
        // CONFIGURATION_CLASS_ATTRIBUTE属性
		processConfigBeanDefinitions(registry);
	}

	public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
        // 核心 如果当前类被具有full属性,那么最终会添加PRESERVE_TARGET_CLASS_ATTRIBUTE属性
        // 核心,在BeanDefinition通过registry注册的时候会设置参数
        // 最终调用ConfigurationClassUtils.checkConfigurationClassCandidate中设置
        if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
			// 省略无关代码
            // 添加成员到configBeanDefs中
			configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
			}
    	for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
            // 如果@Configuration的proxyBeanMethods=true那么会被设置PRESERVE_TARGET_CLASS_ATTRIBUTE
			beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            // 设置Bean增强时被回调的操作
            Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
			// 省略无关代码
		}
    }
}

abstract class ConfigurationClassUtils {
	public static boolean checkConfigurationClassCandidate(
			BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
    	// 省略无关代码
        // 获取配置类上@Configuration中的参数
        Map<String, Object> config = metadata.getAnnotationAttributes(
            										Configuration.class.getName());
        // 如果proxyBeanMethods=true,那么设置为full(默认)
		if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
        // 否则设置为lite
		else if (config != null || isConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}
    }
}

class ConfigurationClassEnhancer {
    // CGLIB callback回调的逻辑
	private static final Callback[] CALLBACKS = new Callback[] {
			new BeanMethodInterceptor(), // 拦截@Bean注解的核心类
			new BeanFactoryAwareMethodInterceptor(),
			NoOp.INSTANCE
	};
    
    private static final ConditionalCallbackFilter CALLBACK_FILTER = 
        									new ConditionalCallbackFilter(CALLBACKS);
    
    public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
		if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
			return configClass;
		}
        // 基于CGLIB创建增强代理类
		Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
		return enhancedClass;
	}
    
    private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(configSuperClass);
        // 配置类会增加对EnhancedConfiguration接口的实现
		enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
		enhancer.setUseFactory(false);
		enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
		enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
		enhancer.setCallbackFilter(CALLBACK_FILTER); // 设置CGLIB CALLBACK 核心!
		enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
		return enhancer;
	}
}

通过上述代码的解析,我们可知,因为目标类上的@Configuration(proxyBeanMethods=true)注解,设置了CONFIGURATION_CLASS_FULL属性,继而设置了PRESERVE_TARGET_CLASS_ATTRIBUTE,最终设置了proxyTargetClass=true属性,用于创建CGLIB代理并设置默认的CALLBACK回调(Spring CGLIB代理的通知类都是通过实现MethodInterceptor接口来实现的,CALLBACK中的BeanMethodInterceptor也是实现此接口,在proceed()执行时会被调用)。

我们继续思考:

BeanMethonInterceptor是做什么的呢?

为什么@Configuration(proxyBeanMethods=true)修饰的类需要被CGLIB代理进行增强呢??

@Configuration

经过上面的源码阅读,我们知道解决问题的关键在于@Configuration注解。

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    boolean proxyBeanMethods() default true;
}

翻译注释可知:

proxyBeanMethods属性是用来指定@Bean注解标注的方法是否使用代理(默认为true),如果开启代理那么会直接从IOC容器之中取得对象;如果关闭则每次调用@Bean标注的方法获取到的对象则是一个新的对象(与容器中不同)

我们还是写个demo测试下把:

java
@Configuration
public class SchoolConfiguration {

    @Bean
    public Teacher teacher() {
        return new Teacher();
    }

    @Bean
    public Student student() {
        // 我们在student方法中调用teacher()
        teacher();
        return new Student();
    }
    
    @Data
    static class Teacher {

        public Teacher() {
            System.out.println("teacher ...");
        }
    }
    
    @Data
    static class Student {
        public Student() {
            System.out.println("student ...");
        }
    }
    
  	public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new 
            AnnotationConfigApplicationContext(SchoolConfiguration.class);
        SchoolConfiguration.Teacher teacher = context.getBean(
            							SchoolConfiguration.Teacher.class);
        SchoolConfiguration.Student student = context.getBean(
            							SchoolConfiguration.Student.class);
        // 问teacher ...会被调用几次
    }
}

默认开启proxyBeanMethods那么会调用一次,如果关闭则会调用两次。

原理:开启proxyBeanMethods会在CGLIB的CALLBACK中设置了BeanMethodInterceptor回调,在@Bean修饰的方法被调用时候通过CGLIB代理进行拦截,从容器中返回@Bean创建的对象,保证了@Bean方法的单例原则

注意:如果@Bean的方法上加上了@Scope("prototype")注解时仍会生效,多次调用会返回多个对象。

总结

  1. 因为@EnableAspectJAutoProxy的proxyTargetClass属性不生效,从而引出的@ConfigurationproxyBeanMethods问题,如果我们需要使用JDK动态代理,那么我们除了设置proxyTargetClass=false、目标对象实现了接口,还需要设置proxyBeanMethods = false或不适用@Configuration注解修饰AOP相关配置类。
  2. 即使设置了proxyBeanMethods = true,如果在@Bean修饰的方法上添加了@Scope("prototype"),那么方法不会返回容器中的对象,对象会创建多次。