学习 Spring Java 配置相关代码,探索编程注册 bean 定义的方法。 使用 spring-boot 1.5.9.RELEASE (spring 4.3.13.RELEASE) 测试。

使用 bean class 注册 bean 定义

AnnotationConfigApplicationContext 手动注册 bean class

调用 AnnotationConfigApplicationContext.register() 手动注册 AppConfig(空类):

AnnotatedBeanDefinitionReader.registerBean(Class<?>, String, Class<Annotation>...) line: 168	
AnnotatedBeanDefinitionReader.registerBean(Class<?>) line: 142	
AnnotatedBeanDefinitionReader.register(Class<?>...) line: 131	
AnnotationConfigApplicationContext.register(Class<?>...) line: 160	
... ...

执行到 AnnotatedBeanDefinitionReader.registerBean(),直接创建 AnnotatedGenericBeanDefinition:

	AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
	// ... ...
	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);

BeanDefinitionReaderUtils.registerBeanDefinition() 直接注册 bean 定义和 alias:

	// Register bean definition under primary name.
	String beanName = definitionHolder.getBeanName();
	registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

	// Register aliases for bean name, if any.
	String[] aliases = definitionHolder.getAliases();
	if (aliases != null) {
		for (String alias : aliases) {
			registry.registerAlias(beanName, alias);
		}
	}

默认注册的 bean

DefaultListableBeanFactory.registerBeanDefinition() 打断点,第一次运行到断点是注册 internalConfigurationAnnotationProcessor

DefaultListableBeanFactory.registerBeanDefinition(String, BeanDefinition) line: 799	
AnnotationConfigApplicationContext(GenericApplicationContext).registerBeanDefinition(String, BeanDefinition) line: 320	
AnnotationConfigUtils.registerPostProcessor(BeanDefinitionRegistry, RootBeanDefinition, String) line: 218	
AnnotationConfigUtils.registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object) line: 163	
AnnotationConfigUtils.registerAnnotationConfigProcessors(BeanDefinitionRegistry) line: 134	
AnnotatedBeanDefinitionReader.<init>(BeanDefinitionRegistry, Environment) line: 83	
AnnotatedBeanDefinitionReader.<init>(BeanDefinitionRegistry) line: 66	
AnnotationConfigApplicationContext.<init>() line: 61	
... ...

这个 bean 定义在创建 AnnotationConfigApplicationContext 时调用 AnnotationConfigUtils.registerAnnotationConfigProcessors() 注册, 其直接创建 RootBeanDefinition

	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);

	if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
	}
	// ... ...
	return beanDefs;

传入的 source 为 null,忽略返回值 beanDefs:

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
	registerAnnotationConfigProcessors(registry, null);
}

RootBeanDefinition 文档, 建议使用 GenericBeanDefinition 更好:

Root bean definitions may also be used for registering individual bean definitions in the configuration phase. 
However, since Spring 2.5, the preferred way to register bean definitions programmatically is the GenericBeanDefinition class. 
GenericBeanDefinition has the advantage that it allows to dynamically define parent dependencies, 
not 'hard-coding' the role as a root bean definition.

使用 @Bean 注解定义 bean

AppConfig 添加 @Bean 注解定义 bean host:

@Bean
protected String host() {
	return "example.com";
}

先禁用 DefaultListableBeanFactory.registerBeanDefinition() 断点, 运行到 AnnotatedBeanDefinitionReader.registerBean() 断点后再打开前者,以跳过默认注册 bean 的逻辑。 或者使用 beanName 设置条件断点。 运行到注册上述 bean host 的代码处:

DefaultListableBeanFactory.registerBeanDefinition(String, BeanDefinition) line: 799	
ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(BeanMethod) line: 266	
ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClass, ConfigurationClassBeanDefinitionReader$TrackedConditionEvaluator) line: 140	
ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(Set<ConfigurationClass>) line: 116	
ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry) line: 320	
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry) line: 228	
PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(Collection<BeanDefinitionRegistryPostProcessor>, BeanDefinitionRegistry) line: 272	
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List<BeanFactoryPostProcessor>) line: 92	
AnnotationConfigApplicationContext(AbstractApplicationContext).invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory) line: 687	
AnnotationConfigApplicationContext(AbstractApplicationContext).refresh() line: 525	
... ...

是执行 invokeBeanDefinitionRegistryPostProcessors() 时,ConfigurationClassPostProcessor 注册的。

查看 processConfigBeanDefinitions(), 其找到 @Configuration class(用 ConfigurationClass 描述)然后调用 ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()

		Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
		configClasses.removeAll(alreadyParsed);

		// Read the model and create bean definitions based on its content
		if (this.reader == null) {
			this.reader = new ConfigurationClassBeanDefinitionReader(
					registry, this.sourceExtractor, this.resourceLoader, this.environment,
					this.importBeanNameGenerator, parser.getImportRegistry());
		}
		this.reader.loadBeanDefinitions(configClasses);
		alreadyParsed.addAll(configClasses);

ConfigurationClassBeanDefinitionReader 调用到 loadBeanDefinitionsForConfigurationClass(ConfigurationClass, TrackedConditionEvaluator)

	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
		loadBeanDefinitionsForBeanMethod(beanMethod);
	}

调用到 loadBeanDefinitionsForBeanMethod(BeanMethod),创建 ConfigurationClassBeanDefinition,解析注解进行相关设置:

	ConfigurationClass configClass = beanMethod.getConfigurationClass();
	MethodMetadata metadata = beanMethod.getMetadata();
	String methodName = metadata.getMethodName();
	// ... ...
	
	ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
	beanDef.setResource(configClass.getResource());
	beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));

	if (metadata.isStatic()) {
		// static @Bean method
		beanDef.setBeanClassName(configClass.getMetadata().getClassName());
		beanDef.setFactoryMethodName(methodName);
	}
	else {
		// instance @Bean method
		beanDef.setFactoryBeanName(configClass.getBeanName());
		beanDef.setUniqueFactoryMethodName(methodName);
	}
	beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
	beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);

	AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

	Autowire autowire = bean.getEnum("autowire");
	if (autowire.isAutowire()) {
		beanDef.setAutowireMode(autowire.value());
	}

	String initMethodName = bean.getString("initMethod");
	if (StringUtils.hasText(initMethodName)) {
		beanDef.setInitMethodName(initMethodName);
	}

	String destroyMethodName = bean.getString("destroyMethod");
	if (destroyMethodName != null) {
		beanDef.setDestroyMethodName(destroyMethodName);
	}

	// Consider scoping
	ScopedProxyMode proxyMode = ScopedProxyMode.NO;
	AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
	if (attributes != null) {
		beanDef.setScope(attributes.getString("value"));
		proxyMode = attributes.getEnum("proxyMode");
		if (proxyMode == ScopedProxyMode.DEFAULT) {
			proxyMode = ScopedProxyMode.NO;
		}
	}

	// Replace the original bean definition with the target one, if necessary
	BeanDefinition beanDefToRegister = beanDef;
	if (proxyMode != ScopedProxyMode.NO) {
		BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
				new BeanDefinitionHolder(beanDef, beanName), this.registry,
				proxyMode == ScopedProxyMode.TARGET_CLASS);
		beanDefToRegister = new ConfigurationClassBeanDefinition(
				(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
	}

	if (logger.isDebugEnabled()) {
		logger.debug(String.format("Registering bean definition for @Bean method %s.%s()",
				configClass.getMetadata().getClassName(), beanName));
	}

	this.registry.registerBeanDefinition(beanName, beanDefToRegister);

梳理简单场景编程创建 bean 定义的主要逻辑:

  • 创建 ConfigurationClassBeanDefinition, 设置 resource 和 source.
  • setFactoryBeanName(), setUniqueFactoryMethodName().
  • 调用 AnnotationConfigUtils.processCommonDefinitionAnnotations().

如何创建 ConfigurationClass, 类 ConfigurationClassParser 看到方法 ConfigurationClassParser.processConfigurationClass(ConfigurationClass), 在此打断点,发现直接使用 config bean 定义就可以创建之。

代码运行到 ConfigurationClassParser.parse(Set<BeanDefinitionHolder>)

		BeanDefinition bd = holder.getBeanDefinition();
		try {
			if (bd instanceof AnnotatedBeanDefinition) {
				parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
			}
			// ... ...
		}

随后到:

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
	processConfigurationClass(new ConfigurationClass(metadata, beanName));
}

同时看到另一个方法:

protected final void parse(Class<?> clazz, String beanName) throws IOException {
	processConfigurationClass(new ConfigurationClass(clazz, beanName));
}

查看代码发现 public 方法 ConfigurationClassParser.parse(Set<BeanDefinitionHolder>) 会调用到此方法:

		BeanDefinition bd = holder.getBeanDefinition();
		try {
			if (bd instanceof AnnotatedBeanDefinition) {
				parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
			}
			else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
				parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
			}
			else {
				parse(bd.getBeanClassName(), holder.getBeanName());
			}
		}

编程使用 config class 注册 bean

回头看前面的代码,只要注册 config bean 定义,spring 就会自动解析其 @Bean 注解注册对应的 bean 定义。 config bean 定义果然跟 FactoryBean 有相似之处,不同的是:

  • FactoryBean 是“运行时”(构建 bean 时),config bean 定义是“编译时”(注册 bean 定义时)。
  • FactoryBean 只为产生最终的 bean 而生,而 config bean 本身也是一个独立的 bean。
  • FactoryBean 只产生一个 bean,config bean 定义则可以有多个 @Bean 方法 bean 定义,并最终产生多个 bean。

是否可以编程方式注册 config bean(较简单),并最终(使用 @Bean 方法)产生自定义的 bean(较复杂)? 简单尝试重复注册 config class,跟踪到代码 DefaultListableBeanFactory.registerBeanDefinition(String, BeanDefinition), 同名 bean 可能报错(设置 ctx.setAllowBeanDefinitionOverriding(false))或覆盖(默认行为,警告不同的 bean 定义)。

	oldBeanDefinition = this.beanDefinitionMap.get(beanName);
	if (oldBeanDefinition != null) {
		if (!isAllowBeanDefinitionOverriding()) {
			throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
					"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
					"': There is already [" + oldBeanDefinition + "] bound.");
		}
		else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
			// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
			if (this.logger.isWarnEnabled()) {
				this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
						"' with a framework-generated bean definition: replacing [" +
						oldBeanDefinition + "] with [" + beanDefinition + "]");
			}
		}
		else if (!beanDefinition.equals(oldBeanDefinition)) {
			if (this.logger.isInfoEnabled()) {
				this.logger.info("Overriding bean definition for bean '" + beanName +
						"' with a different definition: replacing [" + oldBeanDefinition +
						"] with [" + beanDefinition + "]");
			}
		}
		else {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Overriding bean definition for bean '" + beanName +
						"' with an equivalent definition: replacing [" + oldBeanDefinition +
						"] with [" + beanDefinition + "]");
			}
		}
		this.beanDefinitionMap.put(beanName, beanDefinition);
	}

AnnotationConfigApplicationContext 不支持注册 config class 时设置 beanName, 其 reader 支持 registerBean(Class<?>, String, Class<? extends Annotation>...), 但 AnnotationConfigApplicationContext 不支持获取 reader。 同时其有 3 个操作会同时操作 reader 和 scanner:

  • setEnvironment(ConfigurableEnvironment)
  • setBeanNameGenerator(BeanNameGenerator)
  • setScopeMetadataResolver(ScopeMetadataResolver)

prepareRefresh() 前还会额外执行 scanner.clearCache()

可否拿到 reader,或手动自己创建 reader 呢? 看了下 spring-boot 的 AnnotationConfigEmbeddedWebApplicationContext,基本也是重复了类似逻辑。 可考虑拷贝修改(或字节码改写)AnnotationConfigApplicationContext,添加 register(Class<?>, String) 方法。

设置禁止覆盖 bean 定义,使用不同的 beanName 注册两次 config class,发现 @Bean 方法定义的 bean 还是只有一个,也没有报冲突。 看 AnnotationConfigApplicationContext 文档说明,@Bean 定义的 bean 总是允许覆盖,这样应用可以覆盖三方库的 @Bean 定义。

In case of multiple @Configuration classes, 
@Bean methods defined in later classes will override those defined in earlier classes. 
This can be leveraged to deliberately override certain bean definitions via an extra @Configuration class.

追踪代码发现 loadBeanDefinitionsForBeanMethod() 只执行到一次。 在 ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry)

	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
	Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
	do {
		parser.parse(candidates);
		parser.validate();

		Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
		configClasses.removeAll(alreadyParsed);
	// ... ...

candidates 有两个,而 configClasses 就只剩一个了。 跟踪到代码 ConfigurationClassParser.processConfigurationClass(ConfigurationClass)

	ConfigurationClass existingClass = this.configurationClasses.get(configClass);
	if (existingClass != null) {
		if (configClass.isImported()) {
			if (existingClass.isImported()) {
				existingClass.mergeImportedBy(configClass);
			}
			// Otherwise ignore new imported config class; existing non-imported class overrides it.
			return;
		}
		else {
			// Explicit bean definition found, probably replacing an import.
			// Let's remove the old one and go with the new one.
			this.configurationClasses.remove(configClass);
			for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
				if (configClass.equals(it.next())) {
					it.remove();
				}
			}
		}
	}

这里做了去重处理。 而看 ConfigurationClassequals() 方法,只是单纯比较对应的 config class 类名(String)。

@Override
public boolean equals(Object other) {
	return (this == other || (other instanceof ConfigurationClass &&
			getMetadata().getClassName().equals(((ConfigurationClass) other).getMetadata().getClassName())));
}
  • 这样同名的 config class 即只有一个可以作为 config class 解析 @Bean 定义(TODO 不同 class loader 也不行?)。 因此注意 config class 类名很关键,完整类名不能重名。
  • 使用 @Bean 定义 bean(或 bean 模板)时,注意 config class 只能注册一次,不然会重复处理(然后做去重)。这是 @Configuration class 特殊的地方。

从这里也可以看出,不同 beanName 多次注册 config class 不是 spring 官方建议的用法, 因此也不是编程注册 @Bean 定义的建议做法(?)。 ConfigurationClassBeanDefinition 本身也是内部类,不建议编程方式构造和注册 @Bean 方法定义的 bean。 ConfigurationClassBeanDefinition 继承 RootBeanDefinition, 也实现了 cloneBeanDefinition() 方法, 但不支持 setParentName(String) (抛异常),root bean 不能设置 parent 。

编程方式使用 bean 定义模板注册 bean

编程注册 bean 定义的关键点是:

  • 控制 bean 实例化过程。
  • 设置 bean name, 依赖等。

编程注册 bean 定义的主要需求是批量化(自动化), 此场景下 bean 实例化方式通常是固定的(否则就需要手动写不同的 config class 和 @Bean 方法), 而设置 bean name, 依赖等操作很容易使用已有的 bean 定义作为模板修改。 可以特意注册一个 bean 作为模板,模板 bean 同时定义好通用的实例化方式。 在 invokeBeanDefinitionRegistryPostProcessors() 时(或 refresh() 前)获取模板 bean 定义,并编程注册新的 bean 定义。 使用 @Bean 定义的模板 bean 定义,需要在 ConfigurationClassPostProcessor 后才能访问。

@Bean 定义模板(非 static 方法时)可使用 setFactoryBeanName() 指定 config bean,这样其依赖管理可转移到 config bean 上。 也可以编程方式直接设置依赖(?)。

打印 bean 定义,添加依赖声明后对比 bean 定义变化:

  • config bean 定义无变化,推测其依赖在实例化 bean 时再检查注解确定(?)。
  • @Bean bean 如下字段变化,增加了方法参数注入的依赖信息。其中 parameterAnnotations 是 jdk 动态代理(怎么作用的?)。

    "factoryMethodMetadata":{
    	"@type":"org.springframework.core.type.StandardMethodMetadata",
    	"introspectedMethod":{
    		"annotatedParameterTypes":[{
    			"@type":"sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl",
    			"annotations":[],
    			"declaredAnnotations":[],
    			"type":"java.lang.Object"
    		},{
    			"@type":"sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl",
    			"annotations":[],
    			"declaredAnnotations":[],
    			"type":"java.lang.Integer"
    		}],
    		"genericParameterTypes":["java.lang.Object","java.lang.Integer"],
    		"parameterAnnotations":[[{"@type":"com.sun.proxy.$Proxy8"}],[{"@type":"com.sun.proxy.$Proxy10"}]],
    		"parameterCount":2,
    		"parameterTypes":["java.lang.Object","java.lang.Integer"],
    	}
    }

是否可以直接设置或修改依赖呢? 看了下 bean 定义,有两个字段来自 AbstractBeanDefinition,应该是通用的 bean 定义,貌似跟设置依赖(属性或参数)相关:

private ConstructorArgumentValues constructorArgumentValues;
private MutablePropertyValues propertyValues;

但在上述 config bean 和 @Bean 方法 bean 中,这两个字段都为空,java 配置未直接使用这两个字段(?)。 因为 XML bean 定义方式比较清晰,尝试用 XML 定义依赖查看解析结果。

添加 XML bean 配置:

<bean name="hostConfig" class="atest.spring_java_config.AppConfig">
	<property name="environment" ref="environment"></property>
	<property name="testN" value="33333"></property>
</bean>

查看产生依赖定义如下:

	"propertyValueList":[
		{
			"converted":false,
			"name":"environment",
			"optional":false,
			"originalPropertyValue":{"$ref":"@"},
			"value":{
				"@type":"org.springframework.beans.factory.config.RuntimeBeanReference",
				"beanName":"environment",
				"toParent":false
			}
		},
		{
			"converted":false,
			"name":"testN",
			"optional":false,
			"originalPropertyValue":{"$ref":"@"},
			"value":{
				"@type":"org.springframework.beans.factory.config.TypedStringValue",
				"dynamic":false,
				"value":"33333"
			}
		}
	],
	"propertyValues":[{"$ref":"$.propertyValues.propertyValueList[0]"},{"$ref":"$.propertyValues.propertyValueList[1]"}]
  • 特殊类型 RuntimeBeanReference(运行时 bean 引用)定义依赖的 bean 。
  • 特殊类型 TypedStringValue 定义字符串表示的属性值。

另外对比此时 config bean (class) 中 @Bean 方法产生的 bean 定义,注意有两处区别:

  • factoryMethodMetadata.introspectedMethod,XML 配置时未拿到详细的 java 类元信息(?)。
  • resource,加载来源不同。

使用 bean class 定义 bean(比 @Bean 方法)更简单清晰,因此 推荐使用 bean class 作为模板 beanGenericBeanDefinition 实现了 cloneBeanDefinition() 方法,可以用来复制模板 bean 定义。 另外,BeanDefinition 接口有个 setParentName(String parentName) 方法,是否可以用来复用 bean 定义(?)。

复制 bean 定义模板注册新 bean,测试代码如下:

	// 注册 bean 定义模板
	final String hostConfig_templateName = "appConfig";
	final GenericBeanDefinition hostConfig_template;
	{
		ctx.register(AppConfig.class);
		hostConfig_template = (GenericBeanDefinition) ctx.getBeanDefinition(hostConfig_templateName);
		ctx.removeBeanDefinition(hostConfig_templateName);
	}
	// 使用 bean 定义模板注册新 bean
	AbstractBeanDefinition hostConfig_def = hostConfig_template.cloneBeanDefinition();
	ctx.registerBeanDefinition("hostConfig", hostConfig_def);
  • 为简化逻辑可以直接用 class 创建 AnnotatedGenericBeanDefinition, 不过 register 包含一些附加逻辑,参考 AnnotatedBeanDefinitionReader.registerBean(Class<?>, String, Class<? extends Annotation>...)

尝试编程方式定义依赖。

查看 MutablePropertyValues.addPropertyValue(String, Object) 方法定义如下:

/**
 * Overloaded version of {@code addPropertyValue} that takes
 * a property name and a property value.
 * <p>Note: As of Spring 3.0, we recommend using the more concise
 * and chaining-capable variant {@link #add}.
 * @param propertyName name of the property
 * @param propertyValue value of the property
 * @see #addPropertyValue(PropertyValue)
 */
public void addPropertyValue(String propertyName, Object propertyValue) {
	addPropertyValue(new PropertyValue(propertyName, propertyValue));
}

ConstructorArgumentValues.addIndexedArgumentValue(int, Object) 方法定义如下:

/**
 * Add an argument value for the given index in the constructor argument list.
 * @param index the index in the constructor argument list
 * @param value the argument value
 */
public void addIndexedArgumentValue(int index, Object value) {
	addIndexedArgumentValue(index, new ValueHolder(value));
}
  • 这里 ValueHolder 类型与 PropertyValue 结构类似。

编程方式设置 bean 依赖,测试代码如下:

	MutablePropertyValues prop = hostConfig_def.getPropertyValues();
	prop.addPropertyValue("environment", new RuntimeBeanReference("environment"));
	prop.addPropertyValue("testN", 55555);
  • 与 RuntimeBeanReference 名字接近的还有个 RuntimeBeanNameReference,后者表示注入 bean 名字(而不是 bean)(使用场景极少),注意区分。
  • 参考 javadock,Spring 3.0 推荐使用更简短的链式方法 add(String, Object)
  • 编程方式还可以直接设置已知 java 对象,非常灵活。

设置 bean class 对应的 bean name

使用 bean class 注册模板 bean 时,如何设置对应的 bean name 呢?

  • 添加 @Component 注解,指定 bean name。设置静态 bean name。
  • 调用 reader.registerBean(Class<?>, String, Class<? extends Annotation>...)。需要 hack 拿到 reader。
  • 使用 javassist 等动态(在新建增强类上?)添加 @Component 注解。操作相对复杂。

拿到模板 bean 定义后,ctx.registerBeanDefinition() 可自由指定新 bean 的 name 。

自定义 bean 实例化方法

有时候需要自定义创建 bean 对象的过程,而不是直接用 bean class 注册 bean, 这可以使用 @Bean 方法或 FactoryBean 定义模板 bean。区别: 1. @Bean 要在 config class 被处理后才可见,FactoryBean 注册后即可见。 访问 @Bean 定义需要(在 ConfigurationClassPostProcessor 之后)注册 BeanDefinitionRegistryPostProcessor。 2. @Bean 依附于 config bean,需要额外引入 config bean。FactoryBean 则需要单独实现一个 FactoryBean 类型。 3. @Bean 相对现代,代码结构相对清晰。FactoryBean 相对古老,利用了对 FactoryBean 类型的特殊处理。 4. FactoryBean 依赖其 isSingleton()(动态)判断是否原型 bean(?),而不是通过元信息(静态),这又是一个特殊处理。 5. @Bean 可以编程方式控制是否加载 config class,FactoryBean 一旦添加 @Component(为了自定义 bean name)就会被自动扫描处理。

跟踪到代码 AbstractAutowireCapableBeanFactory.createBeanInstance(String, RootBeanDefinition, Object[]) 可知, Spring 创建 bean 对象时,要么通过工厂方法,要么通过构造函数,不能直接输入一个构造好的对象(?)。

尝试包装 FactoryBean,传入已创建的对象,再返回给 Spring 管理。 Spring 提供了创建组合对象的包装 ListFactoryBean,但没有返回简单单例的包装,可自己定义一个。 但经测试使用 FactoryBean 还有个问题,声明周期管理在 FactoryBean 上进行,返回 bean 类型的 @PostConstruct 注解等未生效。 FactoryBean 用了特殊处理,其返回的 bean 也不能完善支持 Spring 相关功能,不推荐使用。

是否可以传入已创建对象给 @Bean 方法呢? 调试跟踪到代码 ConstructorResolver.instantiateUsingFactoryMethod(String, RootBeanDefinition, Object[]), 发现无论是构造函数还是工程方法,都会尝试使用 bean 定义中配置的构造函数。

		int minNrOfArgs;
		if (explicitArgs != null) {
			minNrOfArgs = explicitArgs.length;
		}
		else {
			// We don't have arguments passed in programmatically, so we need to resolve the
			// arguments specified in the constructor arguments held in the bean definition.
			ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
			resolvedValues = new ConstructorArgumentValues();
			minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
		}

此外,ctx.getBean() 的显式参数,自动查找参数类型 bean,解析参数注解等方法都可以传入构造函数。 这样的我们就可以直接使用 @Bean 定义作为模板,不必修改 config bean,直接修改相关依赖及构造函数参数即可。 但仍需注意 @Bean 方法 bean 定义需要在 ConfigurationClassPostProcessor 处理后(通常在容器调用 refresh() 后)才可见。 可使用 @Bean 方法定义 singleton 模板 bean 如下:

public class BeanConfigTemplate {

	public static final String SINGLETON = "singletonTemplate";
	
	@Bean(SINGLETON)
	public Object singleton(@Value("") Object object) {
		return object;
	}
	
}
  • 参数添加 @Value 注解,这样没有传入构造函数时也能解析到有效值,而不是报错。

经测试可使用此模板注册 bean,bean class 上的相关注解也会正确生效。 另外,在容器 refresh() 后仍可以继续注册和创建 bean,即可以支持动态创建 bean,测试代码如下。

	AbstractBeanDefinition singletonTemplate = (AbstractBeanDefinition) ctx.getBeanDefinition(BeanConfigTemplate.SINGLETON);
	ctx.removeBeanDefinition(BeanConfigTemplate.SINGLETON);
	AbstractBeanDefinition beanDef = null;
	
	beanDef = singletonTemplate.cloneBeanDefinition();
	beanDef.getConstructorArgumentValues().addIndexedArgumentValue(0, new Task(1));
	ctx.registerBeanDefinition("task", beanDef);
	Task task = ctx.getBean("task", Task.class);

这样,使用 @Bean 方法定义 bean 模板,可以动态创建任意 bean。 更进一步,可尝试添加 bean 注册处理器,获取并移除模板 bean,避免实例化模板 bean,就更完美了。 不过默认模板 bean 返回一个空字符串,也可以直接忽略,不必处理。