文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring的@Configuration使用与原理

2023-05-20 05:42

关注

@Configuration的使用

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

package com.morris.spring.config;
import com.morris.spring.entity.Message;
import com.morris.spring.entity.Order;
import com.morris.spring.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
//@Component
@Configuration
public class UserConfig {
	@Bean
	public User user() {
		System.out.println("create user");
		return new User();
	}
	@Bean
	public Message message() {
		return new Message(user());
	}
}

现象:

  1. UserConfig类上面使用@Configuration,user()只会被调用一次。
  2. UserConfig类上面使用@Component,user()会被调用两次,也就是会创建两个user对象,破坏了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中设置了属性full
		beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
	}
	else if (config != null || isConfigurationCandidate(metadata)) {
		// 带有@Component、@ComponentScan、@Import、@ImportResource、@Bean注解的BD,设置属性lite
		beanDef.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 method
		if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
			beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
		}
		else {
			beanDef.setBeanClassName(configClass.getMetadata().getClassName());
		}
		beanDef.setUniqueFactoryMethodName(methodName);
	}
	else {
		// 实例 @Bean method
		// instance @Bean method
		beanDef.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 immediately
		return;
	}
	ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
	// 遍历所有带有@Configuration的BD
	for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
		AbstractBeanDefinition beanDef = entry.getValue();
		// If a @Configuration class gets proxied, always proxy the target class
		beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
		// Set enhanced subclass of the user-specified bean class
		Class<?> 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));
	// 这里会注入几个Callback
	enhancer.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-proxy
	if (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 enhancement
			return 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);
}

isCurrentlyInvokedFactoryMethod何时返回true?

private boolean isCurrentlyInvokedFactoryMethod(Method method) {
	// SimpleInstantiationStrategy这个值哪里设置进去的
	
	Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
	return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
			Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
}

第一次调用发生在Spring容器启动时实例化user对象,@Bean注解注入的对象会通过factory-method进行实例化,此处是UserConfig.user(),调用此方法前会往ThreadLocal中放入当前的方法:

org.springframework.beans.factory.support.SimpleInstantiationStrategy#instantiate

...
try {
	// 放入ThreadLocal,@Bean走cglib代理会使用这个ThreadLocal
	currentlyInvokedFactoryMethod.set(factoryMethod);
	Object result = factoryMethod.invoke(factoryBean, args);
	if (result == null) {
		result = new NullBean();
	}
	return result;
}
...

然后再反射调用factoryMethod,此时调用的是代理对象,进入BeanMethodInterceptor#intercept,然后判断当前ThreadLocal中有FactoryMethod,就会调用父类也就是目标类的目标方法,此处是UserConfig.user()创建对象。

第二次进入BeanMethodInterceptor#intercept时,是实例化Message,此时虽然ThreadLocal中有FactoryMethod,但是这个Method是message(),而不是user(),所以会调用下面的方法从BeanFactory中获得对象,而不是调用FactoryMethod创建对象。

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) check
			if (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);
		}
	}
}

如果将user()方法改为static,那么又会创建两个user对象,因为spring并没有为@Configuration修饰的类创建代理,只是为@Configuration修饰的类的对象创建了代理。

FactoryBean的代理

如果@Bean注入的是一个FactoryBean,Spring会为FactoryBean生成代理,保证getObject()方法只会被调用一次。

package com.morris.spring.config;
import com.morris.spring.entity.Message;
import com.morris.spring.entity.UserFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserFactoryBeanConfig {
	@Bean
	public UserFactoryBean userFactoryBean() {
		System.out.println("userFactoryBean");
		return new UserFactoryBean();
	}
	@Bean
	public Message message() throws Exception {
		return new Message(userFactoryBean().getObject());
	}
}

关键源码如下: org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept

...
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 enhancement
		// 为FactoryBean创建代理
		return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
	}
}
...

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

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());
		if (finalClass || finalMethod) {
			if (exposedType.isInterface()) {
				if (logger.isTraceEnabled()) {
					logger.trace("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.");
				}
				// 创建jdk动态代理
				return createInterfaceProxyForFactoryBean(factoryBean, exposedType, beanFactory, beanName);
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("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);
}

到此这篇关于Spring的@Configuration使用与原理的文章就介绍到这了,更多相关Spring的@Configuration内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯