admin管理员组

文章数量:815885

@Configuration的使用与源码分析

@Configuration的使用

@Configuration是一个被@Component注解修饰的注解,先来看一个现象:

package com.morris.spring.config;import com.morris.spring.entity.A;
import com.morris.spring.entity.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;@Configuration
//@Component
public class ConfigurationConfig {@Beanpublic A a() {System.out.println("a");return new A();}@Beanpublic B b() {return new B(a());}}

现象:

  1. ConfigurationConfig类上面使用@Configuration
    ,a()只会被调用一次。

  2. ConfigurationConfig类上面使用@Component

,a()只会被调用两次,也就是会创建两个A对象,破坏了Spring中Bean的单例。

为什么会出现上面这种现象呢?初步猜测使用@Configuration会使用动态代理,下面从源码来分析原理。

@Configuration的源码分析

标识@Configuration为full

在BeanDefinition会有一个属性来标识是否有@Configuration注解:

org.springframework.context.annotation.ConfigurationClassUtils#checkConfigurationClassCandidate

public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
... ...Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {// 这里对@Configuration做了特殊处理,往BD中设置了属性fullbeanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);}else if (config != null || isConfigurationCandidate(metadata)) {// 带有@Component、@ComponentScan、@Import、@ImportResource、@Bean注解的BD,设置属性litebeanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);}else {return false;}// It's a full or lite configuration candidate... Let's determine the order value, if any.Integer order = getOrder(metadata);if (order != null) {beanDef.setAttribute(ORDER_ATTRIBUTE, order);}return true;
}

@Bean注解的收集

org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass

...
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
...

将@Bean的方法封装为MethodMetadata对象放入到ConfigClass中。

解析BeanMethod注入BeanDefinition

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {ConfigurationClass configClass = beanMethod.getConfigurationClass();MethodMetadata metadata = beanMethod.getMetadata();String methodName = metadata.getMethodName();... ...if (metadata.isStatic()) {// static @Bean method// 静态 @Bean methodif (configClass.getMetadata() instanceof StandardAnnotationMetadata) {beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());}else {beanDef.setBeanClassName(configClass.getMetadata().getClassName());}beanDef.setUniqueFactoryMethodName(methodName);}else {// 实例 @Bean method// instance @Bean methodbeanDef.setFactoryBeanName(configClass.getBeanName());beanDef.setUniqueFactoryMethodName(methodName);}
...	...this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

从上面的源码可以发现@Bean注解注入的BeanDefinition都会有个FactoryMethodName,也就是后面实例化时将会使用factory-method流程进行实例化。

替换BeanClass为CGlib代理类

org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {int factoryId = System.identityHashCode(beanFactory);if (this.factoriesPostProcessed.contains(factoryId)) {throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + beanFactory);}this.factoriesPostProcessed.add(factoryId);if (!this.registriesPostProcessed.contains(factoryId)) {// BeanDefinitionRegistryPostProcessor hook apparently not supported...// Simply call processConfigurationClasses lazily at this point then.processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);}// 对@Configuration注解的类做增强,CGlib代理enhanceConfigurationClasses(beanFactory);beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();// 收集所有带有@Configuration的BD,也就是有个attribute为full的属性for (String beanName : beanFactory.getBeanDefinitionNames()) {BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);MethodMetadata methodMetadata = null;if (beanDef instanceof AnnotatedBeanDefinition) {methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();}if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {// Configuration class (full or lite) or a configuration-derived @Bean method// -> resolve bean class at this point...AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;if (!abd.hasBeanClass()) {try {abd.resolveBeanClass(this.beanClassLoader);}catch (Throwable ex) {throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);}}}if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {if (!(beanDef instanceof AbstractBeanDefinition)) {throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +beanName + "' since it is not stored in an AbstractBeanDefinition subclass");}else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {logger.info("Cannot enhance @Configuration bean definition '" + beanName +"' since its singleton instance has been created too early. The typical cause " +"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +"return type: Consider declaring such methods as 'static'.");}configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);}}if (configBeanDefs.isEmpty()) {// nothing to enhance -> return immediatelyreturn;}ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();// 遍历所有带有@Configuration的BDfor (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {AbstractBeanDefinition beanDef = entry.getValue();// If a @Configuration class gets proxied, always proxy the target classbeanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);// Set enhanced subclass of the user-specified bean classClass<?> configClass = beanDef.getBeanClass();// 生成CGlib代理类Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);if (configClass != enhancedClass) {if (logger.isTraceEnabled()) {logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));}beanDef.setBeanClass(enhancedClass);}}
}

org.springframework.context.annotation.ConfigurationClassEnhancer#enhance

public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {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 " +"<context:annotation-config>). 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.isTraceEnabled()) {logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",configClass.getName(), enhancedClass.getName()));}return enhancedClass;
}private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(configSuperClass);enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});enhancer.setUseFactory(false);enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));// 这里会注入几个Callbackenhancer.setCallbackFilter(CALLBACK_FILTER);enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());return enhancer;
}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;
}private static final Callback[] CALLBACKS = new Callback[] {new BeanMethodInterceptor(),// 重点new BeanFactoryAwareMethodInterceptor(),NoOp.INSTANCE
};private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);

上面一堆代码就是为了给@Configuration注解的类生成CGlib代理类,所以关键就是这个类长什么样,是怎么做增强的,重点就在上面的BeanMethodInterceptor中。

@Bean方法的调用

org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept

public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,MethodProxy cglibMethodProxy) throws Throwable {ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);// Determine whether this bean is a scoped-proxyif (BeanAnnotationHelper.isScopedProxy(beanMethod)) {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.// 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.if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&factoryContainsBean(beanFactory, beanName)) {Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);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 enhancementreturn enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);}}if (isCurrentlyInvokedFactoryMethod(beanMethod)) {// 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.isInfoEnabled() &&BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {logger.info(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);}// 第二次调用走这return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#resolveBeanReference

private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,ConfigurableBeanFactory beanFactory, String beanName) {// The user (i.e. not the factory) is requesting this bean through a call to// the bean method, direct or indirect. The bean may have already been marked// as 'in creation' in certain autowiring scenarios; if so, temporarily set// the in-creation status to false in order to avoid an exception.boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);try {if (alreadyInCreation) {beanFactory.setCurrentlyInCreation(beanName, false);}boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);if (useArgs && beanFactory.isSingleton(beanName)) {// Stubbed null arguments just for reference purposes,// expecting them to be autowired for regular singleton references?// A safe assumption since @Bean singleton arguments cannot be optional...for (Object arg : beanMethodArgs) {if (arg == null) {useArgs = false;break;}}}// 第二次调用直接从BeanFactory中取Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :beanFactory.getBean(beanName));if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {// Detect package-protected NullBean instance through equals(null) checkif (beanInstance.equals(null)) {if (logger.isDebugEnabled()) {logger.debug(String.format("@Bean method %s.%s called as bean reference " +"for type [%s] returned null bean; resolving to null value.",beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),beanMethod.getReturnType().getName()));}beanInstance = null;}else {String msg = String.format("@Bean method %s.%s called as bean reference " +"for type [%s] but overridden by non-compatible bean instance of type [%s].",beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),beanMethod.getReturnType().getName(), beanInstance.getClass().getName());try {BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription();}catch (NoSuchBeanDefinitionException ex) {// Ignore - simply no detailed message then.}throw new IllegalStateException(msg);}}Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();if (currentlyInvoked != null) {String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);beanFactory.registerDependentBean(beanName, outerBeanName);}return beanInstance;}finally {if (alreadyInCreation) {beanFactory.setCurrentlyInCreation(beanName, true);}}
}

本文标签: Configuration的使用与源码分析