1. 使用@Bean定义单个Bean
基于 @Bean
注解导入单个Bean。这种方式跟xml中 <bean>
标签等价,可以添加外部自定义Bean,但是需要自己创建Bean实例,而且只能导入单个Bean。注解定义如下:
@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不是首选被自动注入的
@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
注解定义如下:
@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. 过滤器
includeFilters
和 excludeFilters
属性配置一个过滤器,它是 ComponentScan.Filter
类型。过滤器的作用在于,可以设置一些过滤规则,Spring在扫描Bean的时候应用这些规则,然后灵活的对扫描的bean进行筛选。
过滤器 Filter
是 @ComponentScan
注解的子注解,它的定义如下:
@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为我们提供了多种过滤类型(即 Filter
的 type
属性),每种的作用如下表所示:
过滤类型 | 作用 | 说明 |
---|---|---|
ANNOTATION | 基于注解来定义过滤规则 | 需要定义classes属性,表示注解类型 |
ASSIGNABLE_TYPE | 按照类继承关系来进行过滤,即类和它的所有子类 | 需要定义classes属性,表示祖先类 |
ASPECTJ | 基于aspectj表达式过滤 | 需要定义pattern属性,表示AspectJ表达式 |
REGEX | 按照正则表达式过滤 | 需要定义pattern属性,表示正则表达式 |
CUSTOM | 自定义过滤规则 | 需要定义classes属性,表示自定义过滤器类 |
一般而言,我们只需要使用 @ComponentScan
配置好需要扫描的基础包即可,就像下边这样:
@Configuration
@ComponentScan(basePackages = "com.belonk.componentscan")
public class BasicComponentScanConfig {
// ……
}
有时,我们需要按照条件设置过滤器,比如在配置Spring MVC时,需要指定父子容器,我们需要父容器配置不扫描Controller,而子容器仅扫描Controller,配置就像下边这样:
@Configurable
@ComponentScan(basePackages = "com.belonk", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)}) (1)
public class RootConfig {
// ……
}
1 | 扫描Bean时排除 @Controller 注解标记的类 |
@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
接口:
@FunctionalInterface
public interface TypeFilter {
// 检查是否符合匹配规则,匹配返回true。
// 包含两个参数: metadataReader - 当前正在扫描的类元信息, metadataReaderFactory - 可以获取其他类信息的工厂类
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException;
}
我们编写一个自定义过滤器,用来设置仅扫描 classname
包含了 user
的类,示例代码如下:
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;
}
}
然后,应用该自定义过滤器:
@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,也支持自定义 ImportSelector
和 ImportBeanDefinitionRegistrar
。
导入多个Bean的写法一般是这样的:
@Import(value = {Cat.class, Dog.class}
ImportSelector
也可以自定义 ImportSelector
,可以根据类的注解信息判断类是否应该被导入,接口定义如下:
public interface ImportSelector {
// 设置需要导入的类名称,importingClassMetadata参数为被导入的类上的注解信息
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
Springboot的自动配置功能实现类 AutoConfigurationImportSelector 就实现了 ImportSelector 来导入自动配置类。 |
一个 ImportSelector
实例代码如下:
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:
@Import(value = {AnimalImportSelector.class})
ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar
接口提供了 BeanDefinitionRegistry
类,可以更灵活的导入Bean定义:
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) {
}
}
一个简单的示例如下:
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);
}
}
}
另外,一个典型的使用 AspectJAutoProxyRegistrar定义
|
3.2. @ImportResource 导入其他资源中的Bean
Spring也支持将其他资源中的bean导入到容器,使用 @ImportResource
注解来实现,只需要传入资源的url地址即可。一个示例代码如下:
在xml文件定义bean:
<?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。 |
然后,在配置类上导入:
@ImportResource(locations = {"classpath:beans.xml"})
4. 实现FactoryBean接口注册bean
另一种注册Bean的方式是使用 FactoryBean
接口,它是一种特殊的Bean,专门用来生产Bean。一般情况下,Spring通过反射机制来实例化Bean。Spring也通过 FactoryBean
接口提供了另一种创建Bean的方式,这需要用户通过编程的方式来定制实例化Bean的逻辑。FactoryBean
定义如下:
public interface FactoryBean<T> {
// 生产对象的实例
@Nullable
T getObject() throws Exception;
// 生产对象的类型
@Nullable
Class<?> getObjectType();
// 生产的对象是否是单例的
default boolean isSingleton() {
return true;
}
}
如果类实现了 FactoryBean 接口,那么类就作为一个特殊的工厂Bean,获取其实例时得到的是 实际生产的Bean对象 而不是 FactoryBean 本身。如果需要获取 FactoryBean 本身的示例,需要在其bean名称前添加一个 & 符号。 |
来看一个示例。
先定义一个Bean:
public class Car {
}
然后编写 CarFactoryBean
专门用于生产 Car
类:
public class CarFactoryBean implements FactoryBean<Car> {
public Car getObject() {
return new Car();
}
public Class<?> getObjectType() {
return Car.class;
}
public boolean isSingleton() {
return true;
}
}
然后,就可以从容器获取 Car
对象实例了:
Object car = context.getBean("carFactoryBean"); (1)
1 | 此时拿到的是 Car 实例,不是 CarFactoryBean 。 |
如果要获取 CarFactoryBean
实例,则需要添加 &
前缀:
Object carFactoryBean = context.getBean("&carFactoryBean");
assert carFactoryBean.getClass().equals(CarFactoryBean.class);
5. 示例代码
本文示例代码参见 Github。