logo头像

👨‍💻冷锋のIT小屋

给Spring中注册Bean的几种方式

1. 使用@Bean定义单个Bean

基于 @Bean 注解导入单个Bean。这种方式跟xml中 <bean> 标签等价,可以添加外部自定义Bean,但是需要自己创建Bean实例,而且只能导入单个Bean。注解定义如下:

@Bean注解定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
// 自定义bean的名称
@AliasFor("name")
String[] value() default {};

// 同value属性,自定义bean的名称
@AliasFor("value")
String[] name() default {};

// 设置当前注入的bean是否可用于自动注入,默认是true。
// 如果设置为false,那么即使该bean注入到Spring了,在自动注入时也会找不到bean而抛出NoSuchBeanDefinitionException异常。
// 5.1版本新增
boolean autowireCandidate() default true; (1)

// 自定义Bean的初始化方法名称,Spring 在Bean初始化时会调用该方法
String initMethod() default "";

// 自定义Bean的销毁方法名称,Spring在容器关闭时会调用该方法进行自定义Bean销毁工作
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
1 功能与 @Primary 注解相关,都用于自动注入时Bean的选择,而 @Primary 用于指定注入时存在多个Bean实例时优先用哪个,而 autowireCandidate 属性则是设置Bean是否参与自动注入,true 则参与,false 则不参与(即使有Bean实例也可能在自动注入时抛出 NoSuchBeanDefinitionException 异常)

@Bean 一般标注在方法上,Bean的名称默认就是方法名,也可以通过 name 属性来指定名称。每次只能注册一个Bean,默认注册的Bean都是单例的,可以通过 @Scope 注解来定义Bean的范围。另外,尽管 @Bean 定义了生命周期的初始化和销毁方法,但是这种方式是硬编码的方式配置,不推荐使用。

示例代码如下:

@Bean示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 设置当前bean不是首选被自动注入的
@Bean(autowireCandidate = false)
public PrimaryBean primaryBean1() {
return new PrimaryBean("primaryBean1");
}

@Bean
@Primary // 设置当前bean是首选自动注入bean
public PrimaryBean primaryBean2() {
return new PrimaryBean("primaryBean2");
}

// LifeCycleBean包含了init()和destroy()两个方法
@Bean(initMethod = "init", destroyMethod = "destroy")
public LifeCycleBean lifeCycleBean() {
return new LifeCycleBean();
}

2. @ComponentScan扫描bean

@Bean 注解每次仅能注册一个Bean,如果Bean太多怎么办?Spring为我们提供了Bean扫描机制,使用 @ComponentScan 注解。

@ComponentScan 注解的作用是,从定义的包下扫描Bean,这些Bean需要能够被Spring识别,即标注 @Component@Service@Controller@Repository 注解。

componentscan
Figure 1. @ComponentScan注解

@ComponentScan 注解定义如下:

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
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan
{
// 同basePackages
@AliasFor("basePackages")
String[] value() default {};

// 开始扫描Bean的包位置,Spring会从该定义包位置扫描Bean,包括子包
@AliasFor("value")
String[] basePackages() default {};

// 从指定类的包开始扫描
Class<?>[] basePackageClasses() default {};

// 指定扫描出的Bean的名称生成器,默认是AnnotationBeanNameGenerator
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

// 定义Scope解析器
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

// 定义是否为扫描的Bean创建代理,默认不代理,该值会覆盖scopeResolver属性设置
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; (1)

// 定义合法资源的匹配规则,默认是"**/*.class",即路径下的class文件
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

// 使用默认的过滤规则,默认扫描被 `@Component`、`@Repository`、`@Service`、`@Controller` 注解标记的bean
boolean useDefaultFilters() default true;

// 过滤器,用来设置仅需要被扫描到的条件
Filter[] includeFilters() default {}; (2)

// 过滤器,定义排除扫描的条件
Filter[] excludeFilters() default {}; (3)

// 扫描出的bean是否是懒加载的
boolean lazyInit() default false;
}
1 scopedProxy 属性主要解决依赖Bean之间 scope 不同的问题,详情看 Spring官方文档Scoped Beans as Dependencies 一节
2 定义Filter,用来指定需要被扫描到的Bean条件
3 定义Filter,用来指定需要排除扫描的Bean条件

2.1. 过滤器

includeFiltersexcludeFilters 属性配置一个过滤器,它是 ComponentScan.Filter 类型。过滤器的作用在于,可以设置一些过滤规则,Spring在扫描Bean的时候应用这些规则,然后灵活的对扫描的bean进行筛选。

过滤器 Filter@ComponentScan 注解的子注解,它的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {

// 定义过滤类型,包括ANNOTATION、ASSIGNABLE_TYPE、ASPECTJ、REGEX和CUSTOM
FilterType type() default FilterType.ANNOTATION;

// 同classes
@AliasFor("classes")
Class<?>[] value() default {};

// 过滤的类型,只有type为ANNOTATION、ASSIGNABLE_TYPE、CUSTOM可以定义类型
@AliasFor("value")
Class<?>[] classes() default {};

// 过滤的表达式,type为ASPECTJ、REGEX可以定义表达式
String[] pattern() default {};
}

Spring为我们提供了多种过滤类型(即 Filtertype 属性),每种的作用如下表所示:

Table 1. Spring提供的过滤类型
过滤类型 作用 说明

ANNOTATION

基于注解来定义过滤规则

需要定义classes属性,表示注解类型

ASSIGNABLE_TYPE

按照类继承关系来进行过滤,即类和它的所有子类

需要定义classes属性,表示祖先类

ASPECTJ

基于aspectj表达式过滤

需要定义pattern属性,表示AspectJ表达式

REGEX

按照正则表达式过滤

需要定义pattern属性,表示正则表达式

CUSTOM

自定义过滤规则

需要定义classes属性,表示自定义过滤器类

一般而言,我们只需要使用 @ComponentScan 配置好需要扫描的基础包即可,就像下边这样:

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "com.belonk.componentscan")
public class BasicComponentScanConfig {
// ……
}

有时,我们需要按照条件设置过滤器,比如在配置Spring MVC时,需要指定父子容器,我们需要父容器配置不扫描Controller,而子容器仅扫描Controller,配置就像下边这样:

父容器配置
1
2
3
4
5
6
@Configurable
@ComponentScan(basePackages = "com.belonk", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)}) (1)
public class RootConfig
{
// ……
}
1 扫描Bean时排除 @Controller 注解标记的类
子容器配置
1
2
3
4
5
6
@Configuration
@ComponentScan(basePackages = "com.belonk", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,
value = Controller.class)}, useDefaultFilters = false) (1)
public class SubConfig {
// ……
}
1 扫描Bean时仅扫描 @Controller 注解标记的类
注意
使用 includeFilters 自定义包含规则时,useDefaultFilters 必须设置为 false,以禁用默认过滤器。

2.2. 自定义过滤器

如果默认提供的几种过滤类型不满足要求,我们还可以自定义过滤器,需要实现Spring提供的 TypeFilter 接口:

TypeFilter接口定义
1
2
3
4
5
6
7
@FunctionalInterface
public interface TypeFilter {
// 检查是否符合匹配规则,匹配返回true。
// 包含两个参数: metadataReader - 当前正在扫描的类元信息, metadataReaderFactory - 可以获取其他类信息的工厂类
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException
;
}

我们编写一个自定义过滤器,用来设置仅扫描 classname 包含了 user 的类,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CustomTypeFilter implements TypeFilter {
/**
* 判断正被扫描的类是否匹配并加入到spring容器。
*
* @param metadataReader 当前正在扫描的类信息
* @param metadataReaderFactory 可以获取其他类信息的工厂类
* @return true则匹配,加入Spring容器,false则排除
*/

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 被扫描类上的注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 被扫描的类的类元信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 被扫描的类的资源信息,如类文件路径
Resource resource = metadataReader.getResource();
// 通过上边三个信息来定义扫描规则

// 被扫描的class类名包含user,则会被扫描到
if (classMetadata.getClassName().contains("user") || classMetadata.getClassName().contains("User")) {
return true;
}
return false;
}
}

然后,应用该自定义过滤器:

1
2
3
4
5
6
7
8
@Configuration
@ComponentScan(basePackages = "com.belonk.componentscan",
// 定义按照类型过滤规则, 只会扫描实现了MyFilter接口的bean
includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = CustomTypeFilter.class)}, useDefaultFilters = false
)
public class CustomFilterConfig {
// ……
}

此时,Spring只会根据配置的自定义过滤器进行扫描过滤。

3. 使用@Import导入bean

还可以通过导入Bean的方式来添加Bean到Spring,Spring提供了两个注解: @Import@ImportResource

3.1. @Import导入Bean

@Import 注解支持导入多个Bean,也支持自定义 ImportSelectorImportBeanDefinitionRegistrar

导入多个Bean的写法一般是这样的:

1
@Import(value = {Cat.class, Dog.class}

ImportSelector

也可以自定义 ImportSelector,可以根据类的注解信息判断类是否应该被导入,接口定义如下:

1
2
3
4
5
public interface ImportSelector {

// 设置需要导入的类名称,importingClassMetadata参数为被导入的类上的注解信息
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
Springboot的自动配置功能实现类 AutoConfigurationImportSelector 就实现了 ImportSelector 来导入自动配置类。

一个 ImportSelector 实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class AnimalImportSelector implements ImportSelector {
/**
* 导入多个类
*
* @param importingClassMetadata 被导入的类上的注解信息
* @return 导入的类全类名
*/

public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 根据importingClassMetadata做一些条件判断,实现具体的业务逻辑
return new String[]{"com.belonk.imports.bean.Fox", "com.belonk.imports.bean.Tiger"};
}
}

然后在配置类上直接导入该selector:

1
@Import(value = {AnimalImportSelector.class})

ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar 接口提供了 BeanDefinitionRegistry 类,可以更灵活的导入Bean定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface ImportBeanDefinitionRegistrar {

// 根据注解元数据注册bean定义, 支持自定义beanName生成器
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator)
{
registerBeanDefinitions(importingClassMetadata, registry);
}

/**
* 使用BeanDefinitionRegistry来向IOC容器注册或者移除bean。
*
* @param importingClassMetadata 当前类的注解信息
* @param registry bean注册表
*/

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}

一个简单的示例如下:

1
2
3
4
5
6
7
8
9
10
public class AnimalImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 主要容器中注册了Fox和Tiger,那就再注册一个Zoo类
if (registry.containsBeanDefinition("com.belonk.imports.bean.Fox")
&& registry.containsBeanDefinition("com.belonk.imports.bean.Tiger")) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(Zoo.class);
registry.registerBeanDefinition("zoo", beanDefinition);
}
}
}

另外,一个典型的使用 ImportBeanDefinitionRegistrar 例子就是Spring的AOP实现,使用 @EnableAspectJAutoProxy 注解开启AOP代理过后,会导入 AspectJAutoProxyRegistrar 类,该类实现了 ImportBeanDefinitionRegistrar,向Spring容器注册 AnnotationAwareAspectJAutoProxyCreator 对象来实现Aop功能。

AspectJAutoProxyRegistrar定义
1
2
3
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
// ……
}

3.2. @ImportResource 导入其他资源中的Bean

Spring也支持将其他资源中的bean导入到容器,使用 @ImportResource 注解来实现,只需要传入资源的url地址即可。一个示例代码如下:

在xml文件定义bean:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="forXml" class="com.belonk.imports.bean.ForXml"> (1)
<property name="tag" value="1.0"/>
</bean>
</beans>
1 在xml中配置一个bean。

然后,在配置类上导入:

1
@ImportResource(locations = {"classpath:beans.xml"})

4. 实现FactoryBean接口注册bean

另一种注册Bean的方式是使用 FactoryBean 接口,它是一种特殊的Bean,专门用来生产Bean。一般情况下,Spring通过反射机制来实例化Bean。Spring也通过 FactoryBean 接口提供了另一种创建Bean的方式,这需要用户通过编程的方式来定制实例化Bean的逻辑。FactoryBean 定义如下:

FactoryBean接口定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface FactoryBean&lt;T> {
// 生产对象的实例
@Nullable
T getObject() throws Exception;

// 生产对象的类型
@Nullable
Class<?> getObjectType();

// 生产的对象是否是单例的
default boolean isSingleton() {
return true;
}
}
如果类实现了 FactoryBean 接口,那么类就作为一个特殊的工厂Bean,获取其实例时得到的是 实际生产的Bean对象 而不是 FactoryBean 本身。如果需要获取 FactoryBean 本身的示例,需要在其bean名称前添加一个 & 符号。

来看一个示例。

先定义一个Bean:

1
2
public class Car {
}

然后编写 CarFactoryBean 专门用于生产 Car 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CarFactoryBean implements FactoryBean&lt;Car> {
public Car getObject() {
return new Car();
}

public Class<?> getObjectType() {
return Car.class;
}

public boolean isSingleton() {
return true;
}
}

然后,就可以从容器获取 Car 对象实例了:

1
Object car = context.getBean("carFactoryBean"); (1)
1 此时拿到的是 Car 实例,不是 CarFactoryBean

如果要获取 CarFactoryBean 实例,则需要添加 & 前缀:

1
2
Object carFactoryBean = context.getBean("&carFactoryBean");
assert carFactoryBean.getClass().equals(CarFactoryBean.class);

5. 示例代码

本文示例代码参见 Github

参考文档

支付宝打赏 微信打赏

赞赏是不耍流氓的鼓励