Spring Boot通过ImportBeanDefinitionRegistrar动态注入Bean
在阅读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》
程序新视界:精彩和成长都不容错过

Spring Boot通过ImportBeanDefinitionRegistrar动态注入Bean的更多相关文章
- Spring 梳理-运行时动态注入bean
		
动态注入的方法 使用占位符 使用Spring表达式
 - 基于ImportBeanDefinitionRegistrar和FactoryBean动态注入Bean到Spring容器中
		
基于ImportBeanDefinitionRegistrar和FactoryBean动态注入Bean到Spring容器中 一.背景 二.实现方案 1.基于@ComponentScan注解实现 2.基 ...
 - spring boot 动态注入bean
		
方法一 SpringContextUtil public class SpringContextUtil { private static ApplicationContext application ...
 - Spring boot将配置属性注入到bean类中
		
一.@ConfigurationProperties注解的使用 看配置文件,我的是yaml格式的配置: // file application.yml my: servers: - dev.bar.c ...
 - 记录Spring Boot大坑一个,在bean中如果有@Test单元测试,不会注入成功
		
记录Spring Boot大坑一个,在bean中如果有@Test单元测试,不会注入成功 记录Spring Boot大坑一个,在bean中如果有@Test单元测试,不会注入成功 记录Spring Boo ...
 - Spring boot将配置属性注入到bean  专题
		
https://blog.csdn.net/wangmx1993328/article/details/81002901 Error starting ApplicationContext. To d ...
 - Spring Boot @Autowired 没法自动注入的问题
		
Application 启动类: @SpringBootApplication @EnableConfigurationProperties @ComponentScan(basePackages = ...
 - Spring入门(4)-注入Bean属性
		
Spring入门(4)-注入Bean属性 本文介绍如何注入Bean属性,包括简单属性.引用.内部Bean.注入集合等. 0. 目录 注入简单值 注入引用 注入内部Bean 装配集合 装配空值 使用命名 ...
 - Spring Boot之配置文件值注入(@ConfigurationProperties)
		
前言:Spring Boot配置文件值的注入有两种方式,分别是 @ConfigurationProperties @Value 这里我们使用第一种 首先我们创建一个application.yml文件, ...
 
随机推荐
- SpringBootCLI 命令行工具
			
Spring Boot CLI 是用于快速开发 Spring 应用的命令行工具.用来运行 Groovy (与 Java 风格类似)脚本. spring-cli 似乎不是可以各种diy spring-b ...
 - token和session
			
什么是token? token是服务端生成的一串字符串,目的是作为客户端进行请求的一个令牌.当第一次登录后,服务器生成一个token(一串字符串),并将此token返回给客户端,此后页面接收到请求后, ...
 - Linux tar命令解压时提示时间戳异常的处理办法
			
在Linux服务器上的文件会有3个时间戳信息 访问时间(Access).修改时间(Modify).改变时间(Change),都是存放在该文件的Inode里面 问题描述: 公司网站是前后端分离的,所有的 ...
 - Covenant cc 用法
			
新出现的cc框架,之前看hack the box有人用过,不过还是用cs比较多, 这里把之前的笔记搬运过来 --- 设置covenant git clone --recurse-submodule ...
 - thinkpad p1 gen2 扬声器音量异常问题解决过程
			
在弹出 "用户帐户控制" 对话框时的声音明显不对,测试后发现规律:音量在30以内,1分钟内扬声器无声音发出,运行ccleaner弹出 "用户帐户控制" 对话框, ...
 - Vue使用antV G2制作看板
			
工作中需要制作一个看板,选型选用antV G2进行开发. 由于项目前端是使用Vue,于是研究了antVG2在Vue中的使用. 1.安装antv/g2 npm install @antv/g2 --sa ...
 - 大厂面试经:说一下你们线上JVM是如何优化的?
			
JVM(Java虚拟机)简单来说就是运行Java代码的解释器,作为螺丝钉程序员JVM其实了解下就差不多啦,不懂JVM内部细节照样能写出优质的代码!但是一到造火箭.飞机的场景(面试)不懂JVM的你,会被 ...
 - 2. 彤哥说netty系列之IO的五种模型
			
你好,我是彤哥,本篇是netty系列的第二篇. 欢迎来我的公从号彤哥读源码系统地学习源码&架构的知识. 简介 本文将介绍linux中的五种IO模型,同时也会介绍阻塞/非阻塞与同步/异步的区别. ...
 - fatal: Authentication failed for 'http://git
			
git pull 出现 fatal: Authentication failed for 'http://git... git config --system --unset credential.h ...
 - lenovo ubuntu18.04 找不到网络适配器
			
链接: https://pan.baidu.com/s/1YJl-MfG0tVy9sLx4_otmnA 提取码: smfp https://blog.csdn.net/John_chaos/artic ...