在 Component 类中定义 bean 元数据

一般,是在 @Configuration 注解的类中通过 @Bean 方法来定义 bean,同样,Spring 的 @Component 类中也可以用来向容器定义 bean 元数据,使用相同的 @Bean 方法就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class FactoryMethodComponent {

// 定义 bean,其它方法级别的 Spring 注解也可以指定在这里,如 @Lazy,@Scope
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}

// 正常的 Spring 组件方法
public void doWork() {
// Component method implementation omitted
}
}

将 InjectionPoint 作为 @Bean 方法的参数

从 Spring 4.3 开始,可以声明一个工厂方法,它的参数类型为 org.springframework.beans.factory.InjectionPoint,用来访问触发当前 bean 创建的请求注入点, 但是这只适用于 bean 实例实际的创建,而不是对已存在实例的注入。所以在原型(prototype)bean 的场景下意义比较大.

如下的例子中,将 userInfo 声明为一个原型 bean,并提供了 InjectionPoint 对象作为 @Bean 方法的参数,在另外两个 bean 的定义中注入 UserInfo 类型的 bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.example.springboot.demo.component;

import com.example.springboot.demo.entity.UserInfo;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;

/**
* @author guo
* @date 2021/7/10
*/
@Component
public class FactoryMethodComponent {

/**
* 声明一个 InjectionPoint(或者是子类 DependencyDescriptor)类型的对象作为工厂方法的参数,可以用来访问
* 触发当前 bean 创建的注入点, 但是这只适用于 bean 实例实际的创建,而不是对已存在实例的注入。所以在 prototype bean
* 的场景下意义比较大
*
* @param injectionPoint
* @return
*/
@Bean
@Scope("prototype")
public UserInfo userInfo(InjectionPoint injectionPoint) {
System.out.println("prototype instance for " + injectionPoint.getMember());
return new UserInfo();
}

@Bean
public PropertiesFactoryBean overrideProperties(@Autowired ResourceLoader resourceLoader,
@Autowired UserInfo userInfo) {

System.out.println("inject userInfo from overrideProperties:" + userInfo.hashCode());

PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(resourceLoader.getResource("classpath:override.properties"));
propertiesFactoryBean.setFileEncoding("UTF-8");
return propertiesFactoryBean;
}

@Bean
public PropertiesFactoryBean applicationProperties(@Autowired ResourceLoader resourceLoader,
@Autowired UserInfo userInfo) {

System.out.println("inject userInfo from applicationProperties:" + userInfo.hashCode());
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(resourceLoader.getResource("classpath:application.properties"));
propertiesFactoryBean.setFileEncoding("UTF-8");
return propertiesFactoryBean;
}

}

当容器启动后,可以在控制台中打印进行验证:

1
2
3
4
5
6
7
prototype instance for public org.springframework.beans.factory.config.PropertiesFactoryBean com.example.springboot.demo.component.FactoryMethodComponent.overrideProperties(org.springframework.core.io.ResourceLoader,com.example.springboot.demo.entity.UserInfo)

inject userInfo from overrideProperties:542895457

prototype instance for public org.springframework.beans.factory.config.PropertiesFactoryBean com.example.springboot.demo.component.FactoryMethodComponent.applicationProperties(org.springframework.core.io.ResourceLoader,com.example.springboot.demo.entity.UserInfo)

inject userInfo from applicationProperties:2049646260

Component 类和 Configuration 类中声明 bean 定义的区别

@Component 类中的 @Bean 方法的处理与 @Configuration 中的处理不同, 区别在于对于 @Component 类, CGLIB 并不会拦截对方法和字段的调用,而在 @Configuration 类中,CGLIB 的代理是通过对 @Bean 方法中的方法和字段调用来创建对协作对象(依赖)的 bean 元数据引用(bean metadata references), 这样的方法并不是通过一般的 Java 语义 进行调用的,而是通过容器来提供声明周期管理和 Spring beans 的代理, 即使是通过程序式的调用某个 @bean 方法来引用该 bean.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class AppConfig {

@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}

@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}

Component 类中的某个 @Bean 方法中,调用其它方法或字段,只是标准的 Java 方法调用语义,并没有特殊的 CGLIB 的处理和约束.

静态 @bean 方法

@Bean 方法也可以声明为静态的,这样就不需在创建所在 Configuration 或 Component 实例后,才能调用。当定义后置处理器 bean 时非常有意义,如 BeanFactoryPostProcessor 或者是 BeanPostProcessor, 这是因为这些 bean 需要在容器生命周期的早期就进行初始化,并且要避免对配置类的其它部分进行初始化。

如,Spring Boot 中的的自动配置类 PropertySourcesPlaceholderConfigurer (PropertySource/bean property externalized BeanFactoryPostProcessor )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* {@link EnableAutoConfiguration Auto-configuration} for
* {@link PropertySourcesPlaceholderConfigurer}.
*
* @author Phillip Webb
* @author Dave Syer
* @since 1.5.0
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {

@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}

}

对静态 @Bean 方法的调用,并不会被容器拦截,即使是定义在 @Configuration 类中,这是由于技术的限制:CGLIB 子类只能覆盖非静态方法,所以在某个 @Bean 方法中,对这些静态 @Bean 方法的调用遵循标准的 Java 语义,返回一个新的实例对象.

@Bean 方法的可见性

@Bean 方法的 Java 语言可见性不会对 Spring 容器中生成的 bean 定义产生直接影响。在非 @Configurtation 类中, 可以自由定义方法的可见性 (无论是静态的还是非静态的), 但是在 @Configuration 类中, 一般的 @Bean 方法式需要是可覆盖的 (代理子类), 所以不能被声明为 privatefinal.

@Bean 方法可以被声明在给定的 component 或者 configuration 类的基类中, 或者是这些类所实现的接口的默认方法上 (Java 8 default method). 这样在组合复杂的配置时, 可以具有更多的灵活性.

参考阅读

Core Technologies (spring.io) - Defining Bean Metadata within Components