文档章节

Spring Boot通过ImportBeanDefinitionRegistrar动态注入Bean

程序新视界
 程序新视界
发布于 2019/12/05 20:20
字数 1226
阅读 72
收藏 0

在阅读Spring Boot源码时,看到Spring Boot中大量使用ImportBeanDefinitionRegistrar来实现Bean的动态注入。它是Spring中一个强大的扩展接口。本篇文章来讲讲它相关使用。

Spring Boot中的使用

在Spring Boot 内置容器的相关自动配置中有一个ServletWebServerFactoryAutoConfiguration类。该类的部分代码如下:

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

	// ...
	
	/**
	 * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
	 * {@link ImportBeanDefinitionRegistrar} for early registration.
	 */
	public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

		private ConfigurableListableBeanFactory beanFactory;

		// 实现BeanFactoryAware的方法,设置BeanFactory
		@Override
		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
			if (beanFactory instanceof ConfigurableListableBeanFactory) {
				this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
			}
		}

		// 注册一个WebServerFactoryCustomizerBeanPostProcessor
		@Override
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
				BeanDefinitionRegistry registry) {
			if (this.beanFactory == null) {
				return;
			}
			registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
					WebServerFactoryCustomizerBeanPostProcessor.class);
			registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
					ErrorPageRegistrarBeanPostProcessor.class);
		}

		// 检查并注册Bean
		private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
			// 检查指定类型的Bean name数组是否存在,如果不存在则创建Bean并注入到容器中
			if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
				RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
				beanDefinition.setSynthetic(true);
				registry.registerBeanDefinition(name, beanDefinition);
			}
		}
	}
}

在这个自动配置类中,基本上展示了ImportBeanDefinitionRegistrar最核心的用法。这里该接口主要用来注册BeanDefinition。

BeanPostProcessorsRegistrar实现了ImportBeanDefinitionRegistrar接口和BeanFactoryAware接口。其中BeanFactoryAware接口的实现是用来暴露Spring的ConfigurableListableBeanFactory对象。

而实现registerBeanDefinitions方法则是用来对Bean的动态注入,这里注入了WebServerFactoryCustomizerBeanPostProcessor和ErrorPageRegistrarBeanPostProcessor。

简单了解了Spring Boot中的一个使用实例,下面我们总结一下使用方法,并自己实现一个类似的功能。

ImportBeanDefinitionRegistrar使用

Spring官方通过ImportBeanDefinitionRegistrar实现了@Component、@Service等注解的动态注入机制。

很多三方框架集成Spring的时候,都会通过该接口,实现扫描指定的类,然后注册到spring容器中。 比如Mybatis中的Mapper接口,springCloud中的FeignClient接口,都是通过该接口实现的自定义注册逻辑。

所有实现了该接口的类的都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其的bean初始化,也能被aop、validator等机制处理。

基本步骤:

  • 实现ImportBeanDefinitionRegistrar接口;
  • 通过registerBeanDefinitions实现具体的类初始化;
  • 在@Configuration注解的配置类上使用@Import导入实现类;

简单示例

这里实现一个非常简单的操作,自定义一个@Mapper注解(并非Mybatis中的Mapper实现),实现类似@Component的功能,添加了@Mapper注解的类会被自动加载到spring容器中。

首先创建@Mapper注解。

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface Mapper {
}

创建UserMapper类,用于使用@Mapper注。

@Mapper
public class UserMapper {
}

定义ImportBeanDefinitionRegistrar的实现类MapperAutoConfigureRegistrar。如果需要获取Spring中的一些数据,可实现一些Aware接口,这实现了ResourceLoaderAware。

public class MapperAutoConfigureRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
	
	private ResourceLoader resourceLoader;

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		MapperBeanDefinitionScanner scanner = new MapperBeanDefinitionScanner(registry, false);
		scanner.setResourceLoader(resourceLoader);
		scanner.registerFilters();
		scanner.addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
		scanner.doScan("com.secbro2.learn.mapper");
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}
}

在上面代码中,通过ResourceLoaderAware接口的setResourceLoader方法获得到了ResourceLoader对象。

在registerBeanDefinitions方法中,借助ClassPathBeanDefinitionScanner类的实现类来扫描获取需要注册的Bean。

MapperBeanDefinitionScanner的实现如下:

public class MapperBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

	public MapperBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
		super(registry, useDefaultFilters);
	}

	protected void registerFilters() {
		addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
	}

	@Override
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		return super.doScan(basePackages);
	}
}

MapperBeanDefinitionScanner继承子ClassPathBeanDefinitionScanner,扫描被@Mapper的注解的类。

在MapperBeanDefinitionScanner中指定了addIncludeFilter方法的参数为包含Mapper的AnnotationTypeFilter。

当然也可以通过excludeFilters指定不加载的类型。这两个方法由它们的父类ClassPathScanningCandidateComponentProvider提供的。

完成了上面的定义,则进行最后一步引入操作了。创建一个自动配置类MapperAutoConfig,并通过@Import引入自定义的Registrar。

@Configuration
@Import(MapperAutoConfigureRegistrar.class)
public class MapperAutoConfig {
}

至此,整个代码的功能已经编写完成,下面写一个单元测试。

@RunWith(SpringRunner.class)
@SpringBootTest
public class MapperAutoConfigureRegistrarTest {

	@Autowired
	UserMapper userMapper;

	@Test
	public void contextLoads() {
		System.out.println(userMapper.getClass());
	}
}

执行单元测试代码,会发现打印如下日志:

class com.secbro2.learn.mapper.UserMapper

说明UserMapper已经被实例化成功,并注入Spring容器当中。

小结

当然,这里的UserMapper并不接口,这里的实现也并不是Mybatis中的实现形式。只是为了演示该功能的简单示例。需要注意的是文中提到了两种实现的实例,第一种是Spring Boot中的实现,第二种是我们的Mapper实例。展现了两种不同方法的注册的操作,但整个使用流程是一致的,读者注意仔细品味,并在此基础上进行拓展更复杂的功能。

原文链接:《Spring Boot通过ImportBeanDefinitionRegistrar动态注入Bean


<center><b>程序新视界</b>:精彩和成长都不容错过</center>

程序新视界-微信公众号

© 著作权归作者所有

程序新视界
粉丝 1
博文 73
码字总数 122414
作品 0
东城
私信 提问
加载中

评论(0)

spring--ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar是spring对外提供动态注册beanDefinition的接口,spring内部大部分套路也是用该接口进行动态注册beanDefinition的。 ImportBeanDefinitionRegistrar接口不是直...

卢先生程序员
2019/08/29
61
0
Spring Bean 的注册和注入的几种常用方式和区别

Spring 注册Bean: 包扫描 + 组件标注注解(@Controller、@Service、@Repository、@Component),一般项目里面使用。 使用@Bean注解,一般导入第三方组件的时候使用。 使用@Import注解,一般...

xiaolyuh
2019/09/23
68
0
Mybatis-Spring源码分析

分析Mybatis如何利用Spring的扩展点集成到框架中的,Mybatis本身的扩展点不再本次分析范畴 构建环境 上Github上下载https://github.com/mybatis/spring。通过Git的方式试了几次没成功,后来直...

特拉仔
2019/11/20
58
0
携程大佬带你写一个可扩展的Spring插件

# 背景介绍 Spring现在几乎已经成为了Java开发的必备框架,在享受Spring框架本身强大能力的同时,有时我们也会希望自己研发的组件和Spring进行整合,从而使得组件更易于上手,而且配合Sprin...

Java架构师CAT
2019/08/16
0
0
编程实战篇——Spring Boot 自动配置实现

基于Spring Boot自动配置的思想封装起来,使其他Spring Boot项目引入后能够进行快速配置。 AutoConfiguration Spring Boot的一个重要特性就是提供了各种各样的AutoConfiguration。例如DataS...

阿里云云栖社区
2018/10/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

00-Java 面试准备

面试之前 面试前准备简历需要注意的几个方面: 写简历、改简历,这个一定要干的。简历有两个作用,一个是吸引别人,能让别人邀请你去面试,这是前提;另一个是引导面试的人,让面试的人问你所...

源程序
今天
54
0
OSChina 周二乱弹 —— 大王(@罗马的王)颜值制霸Osc社区

Osc乱弹歌单(2020)请戳(这里) 【今日歌曲】 @巴拉迪维 :Lunik的单曲《Seeing You Soar》 I hope you’re smiling,When seeing me soar. #今日歌曲推荐# 《Seeing You Soar》- Lunik 手...

小小编辑
今天
75
0
wordcount代码

1.写出map类 public class WCMapper extends Mapper<LongWritable,Text,Text,LongWritable>{ @Override protected void map(LongWritable key,Text value,Context context)throws IOExcepti......

七宝1
今天
59
0
Spring Batch 小任务(Tasklet)步骤

Chunk-Oriented Processing不是处理 step 的唯一方法。 考虑下面的一个场景,如果你仅仅需要调用一个存储过程,你可以在 ItemReader 中实现这个调用,然后在存储过程完成调用后返回 null。这...

honeymoose
今天
67
0
Linux日志分析

1. Linux日志文件的类型 2. 系统服务日志 2.1 syslogd的简介 2.2 syslogd的配置和使用 2.3 日志的安全性设置 2.4 远程日志记录服务 3. 日志的轮替 3.1 logrotate简介 3.2 logrotate的配置 3....

JiaMing
昨天
67
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部