SpringBoot成长记8:SpringBoot如何实现自动装配配置和扩展

file

前面我们摸清楚了整个invokeBeanFactoryPostProcessors方法的if-else逻辑和3个for循环的核心脉络逻辑。

接下来我们来看下细节,我会通过抓大放小的思想,带大家看到在扩展点执行的过程中,最最要的有哪一些。

SpringBoot的自动装配配置如何做到的、第三方技术如何进行扩展的。

SpringBoot的自动装配配置如何做到的?

if-else逻辑中哪些BeanFactoryPostProcessor执行了?

之前我们提到过,invokeBeanFactoryPostProcessors执行的BeanFactoryPostProcessor主要来源是容器的两个属性
beanFactoryPostProcessorsBeanDefinitionMap

首先这两个属性,会在之前执行扩展操作,比如listener或者initializer的方法时,设置进去值的。执行到invokeBeanFactoryPostProcessors时,之前会设置如下图所示的值:

file

从上图可以看出来,执行invokeBeanFactoryPostProcessors的时候已经有4个BeanFactoryPostProcessor。

当执行invokeBeanFactoryPostProcessors,核心脉络上一节我们分析出了是主要一个if-else+3个for循环组成的,这个if-else中有分了内部的、实现PriorityOrderd、Ordered、NonOrder这个四个顺序执行。结合上面4个BeanFactoryPostProcessor,整体执行如下图所示:

file

从图中可以看出来,非常关键的一点那就是:在执行扩展方法1的过程中,通过Spring内部的一个ConfigurationClassPostProcessor,补充了新的BeanDefinition,增加了新的BeanFactoryPostProcessor。

ConfigurationClassPostProcessor这个执行非常关键,因为它补充了新的BeanDefinition。

它核心用来进行加载ClassPath下所有java注解定义的BeanDefinition。比如:自己包下定义的@Service,@Component,@Controller@Configuration @Bean等注解定义的Bean,也包括外部的starter中@Configuration @Bean等配置。

也就是你定义的大多数bean和外部starter定义的Bean的BeanDefinition都会被放入到容器中。

另外,补充了新的BeanDefinition,这里我们简化了下,假设当前应用,只依赖了一个myBatis-starter,之后只会补充一个MyBatis相关的BeanDefinition,一个BeanFactoryPostProcessor—MapperScannerConfigurer。从名字上猜测,它应该是用来扫描MyBatis相关bean的。

invokeBeanFactoryPostProcessors的if-else逻辑中,触发了2个扩展操作,最后还会执行扩展方法2,之前的所有BeanFactoryPostProcessor,统一会执行扩展方法2。

扩展方法2执行的逻辑,基本没有什么核心的,这里我们就直接过了,你知道invokeBeanFactoryPostProcessors这里会触发这个扩展点,并且在扩展方法1之后执行就行了。

最终执行完if-else后,BeanFactory中的主要有如下的beanDefination:

beanDefinitionNames = {ArrayList@3752}  size = 164
 0 = "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"
 1 = "org.springframework.context.annotation.internalAutowiredAnnotationProcessor"
 2 = "org.springframework.context.annotation.internalCommonAnnotationProcessor"
 3 = "org.springframework.context.event.internalEventListenerProcessor"
 4 = "org.springframework.context.event.internalEventListenerFactory"
 5 = "learnSpringBootApplication"
 6 = "org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory"
 7 = "userController"
 8 = "myBeanPostProcessor"
 9 = "userServiceImpl"
 10 = "userMapper"
 11 = "org.springframework.boot.autoconfigure.AutoConfigurationPackages"
 12 = "org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration"
 13 = "propertySourcesPlaceholderConfigurer"
 14 = "org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration"
 15 = "websocketServletWebServerCustomizer"
 16 = "org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration"
 17 = "org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat"
 18 = "tomcatServletWebServerFactory"
 19 = "org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration"
 20 = "servletWebServerFactoryCustomizer"
 21 = "tomcatServletWebServerFactoryCustomizer"
 22 = "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor"
 23 = "org.springframework.boot.context.internalConfigurationPropertiesBinderFactory"
 24 = "org.springframework.boot.context.internalConfigurationPropertiesBinder"
 25 = "org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator"
 26 = "org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata"
 27 = "server-org.springframework.boot.autoconfigure.web.ServerProperties"
 28 = "webServerFactoryCustomizerBeanPostProcessor"
 29 = "errorPageRegistrarBeanPostProcessor"
 30 = "org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration"
 31 = "dispatcherServlet"
 32 = "spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties"
 33 = "spring.http-org.springframework.boot.autoconfigure.http.HttpProperties"
 34 = "org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration"
 35 = "dispatcherServletRegistration"
 36 = "org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration"
 37 = "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration"
 38 = "taskExecutorBuilder"
 39 = "applicationTaskExecutor"
 40 = "spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties"
 41 = "org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration"
 42 = "defaultValidator"
 43 = "methodValidationPostProcessor"
 44 = "org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration"
 45 = "error"
 46 = "beanNameViewResolver"
 47 = "org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration"
 48 = "conventionErrorViewResolver"
 49 = "org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration"
 50 = "errorAttributes"
 51 = "basicErrorController"
 52 = "errorPageCustomizer"
 53 = "preserveErrorControllerTargetClassPostProcessor"
 54 = "spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties"
 55 = "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration"
 56 = "requestMappingHandlerAdapter"
 57 = "requestMappingHandlerMapping"
 58 = "welcomePageHandlerMapping"
 59 = "mvcConversionService"
 60 = "mvcValidator"
 61 = "mvcContentNegotiationManager"
 62 = "mvcPathMatcher"
 63 = "mvcUrlPathHelper"
 64 = "viewControllerHandlerMapping"
 65 = "beanNameHandlerMapping"
 66 = "routerFunctionMapping"
 67 = "resourceHandlerMapping"
 68 = "mvcResourceUrlProvider"
 69 = "defaultServletHandlerMapping"
 70 = "handlerFunctionAdapter"
 71 = "mvcUriComponentsContributor"
 72 = "httpRequestHandlerAdapter"
 73 = "simpleControllerHandlerAdapter"
 74 = "handlerExceptionResolver"
 75 = "mvcViewResolver"
 76 = "mvcHandlerMappingIntrospector"
 77 = "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter"
 78 = "defaultViewResolver"
 79 = "viewResolver"
 80 = "requestContextFilter"
 81 = "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration"
 82 = "formContentFilter"
 83 = "org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari"
 84 = "dataSource"
 85 = "org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration$Hikari"
 86 = "org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration"
 87 = "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$PooledDataSourceConfiguration"
 88 = "org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration"
 89 = "hikariPoolDataSourceMetadataProvider"
 90 = "org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration"
 91 = "org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker"
 92 = "org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration"
 93 = "dataSourceInitializerPostProcessor"
 94 = "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"
 95 = "spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties"
 96 = "com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration"
 97 = "sqlSessionFactory"
 98 = "sqlSessionTemplate"
 99 = "mybatis-plus-com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties"

SpringBoot的自动装配配置实际是通过starter+@Conditional注解+@Import注解+invokeBeanFactoryPostProcessors触发扩展点共同实现的。

上面每个点都有一些比较有意思的设计。

这句话你第一次听肯定不理解,没关系,等我讲完这这一节你就会明白SpringBoot核心装配置的的几个设计点了。

既然从容器中获取到之前放入的对象ConfigurationClassPostProcessor这个对象非常核心,用来增加BeanDefination的。

接下来,我们就来详细分析下它的核心原理,看看它是如何增加BeanDefination的,并且扫描出外部的starter的。

术语普及starter是什么?

starter是SpringBoot定义的已经有一些默认javaConfig配置好类,通过封装成jar,定义好maven的依赖,为我们提供了便利的配置起步依赖。

也就是说,我们可以定义了常见的Spring和第三方技术整合的默认配置,或者我们自己定义默认的整合第三方技术或者自研技术的配置,这样的功能是非常便利的。

在上面我们提到了invokeBeanFactoryPostProcessors中核心触发的扩展操作postProcessBeanDefinitionRegistry是:通过Spring内部的一个ConfigurationClassPostProcessor,补充了新的BeanDefinition。

当执行完这个类的扩展操作后,容器的BeanDefinitionMap中多了很多BeanDefinition,有SpringMVC相关的,有MyBatis相关、有我们自己定义的Controller和Service相关等。

那我们大体就可以分为两步来看,

  1. 自己定义的Controller和Service相关的BeanDefinition添加
  2. starter、其他框架的BeanDefinition添加

file

自己定义的Controller和Service相关的BeanDefinition如何添加的?

在看第一步之前,你可以思考下,我们定义的Bean是不是通常有以下几种

@Bean+@Configuration注解定义的,@Service,@Controller等注解定义的、也有用xml、groovy定义的bean

而且可以通过@Import,@ImportResource导入其他的xml或者JavaConfig定义的Bean,@ComponentScan指定扫描Bean的路径等等。

也就是说有一大堆注解需要我们分析和解析。这个是需要一个解析器和扫描器来进行的。就类似于之前我们提到的Reader和Scanner。

那么接下来,我们看下第一步,自己定义的Controller和Service相关的BeanDefinition是如何被添加的?

刚才我们分析到,有一大堆注解需要ConfigurationClassPostProcessor分析和解析,那ConfigurationClassPostProcessor中核心的那几个组件来负责做这些事情的呢?

让我先来看下ConfigurationClassPostProcessor它扩展方法的脉络:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   int registryId = System.identityHashCode(registry);
   if (this.registriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
            "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
   }
   if (this.factoriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
            "postProcessBeanFactory already called on this post-processor against " + registry);
   }
   this.registriesPostProcessed.add(registryId);

   processConfigBeanDefinitions(registry);
}

方法脉络其实很简单,核心触发了processConfigBeanDefinitions这个方法,其余的if判断只是些校验而已。

而触发的这个方法内容就比较多了:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			parser.parse(candidates);
			parser.validate();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}

		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
			// Clear cache in externally provided MetadataReaderFactory; this is a no-op
			// for a shared cache since it'll be cleared by the ApplicationContext.
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
	}

方法长或者不好理解,没有关系。你用我教你的思想分析就好,

按照for循环和if,划分脉络,抓大放小后,最核心的逻辑可以分为如下3点:

1)第一个for循环:获取BeanDefinitionMap所有的beanName,查找出标记了@Configuration注解的BeanDefinition,这里找到的其实是learnSpringBootApplication

2)if逻辑,选择了BeanNameGenerator,bean的名称生成器相关的设置

3)do-while,创建了ConfigurationClassParser和ConfigurationClassBeanDefinitionReader去扫描和解析ClassPath下的BeanDefinition

整体如下图所示:

file

这里我们要强调的是,上面这段逻辑在处理的是我们自定义的Bean,包括Controller、Service和整合其他技术配置的Bean的。

你可以思考下,上面的处理思路就是,查找到BeanDefination,解析和添加BeanDefination。

从哪里开始查找BeanDefination呢?

通过之前prepare和create容器context的时候,通过 Initializers增加了默认的internalBeanDefination和LearnSpringBootApplication的BeanDefination。

(忘记的同学,可以回顾下prepare和create容器,成长记5和6)

ConfigurationClassPostProcessor这里就是遍历之前的BeanDefinationMap集合,它查找的是所有包含@Configuration的BeanDefination

最终查找到的只有一个LearnSpringBootApplication的BeanDefination。

如下图所示:

file

怎么解析出BeanDefination?

当知道了主要是找到了LearnSpringBootApplication这个BeanDefination,以这个为入口开始分析解析

javaConfig定义的bean,其实本质是找到所有@Configuration的配置类,@ComponentScan等注解的类,挨个解析它们定义范围的Bean。

xml和groovy也有对应的查找方式,就不赘述了。

具体怎么处解析我们定义的Bean的呢?

肯定要处理很多注解的,比如@ComponentScan注解、@ImportSource等等注解。

主要使用了的组件主要就是ConfigurationClassBeanDefinitionReader、ConfigurationClassParser,通过组件的parse方法来执行解析的。

概况如下图所示:

file

执行到这里,自己定义的Controller和Service相关的BeanDefinition就添加完了。

基本就是从Resource->ClassLoader->注解解析,筛选->ConfigurationClass->BeanDefinition

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
resources = {Resource[7]@4174} 
 0 = {FileSystemResource@4181} "file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\LearnSpringBootApplication.class]"
 1 = {FileSystemResource@4185} "file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\controller\UserController.class]"
 2 = {FileSystemResource@4186} "file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\mapper\UserMapper.class]"
 3 = {FileSystemResource@4187} "file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\model\User.class]"
 4 = {FileSystemResource@4188} "file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\service\IUserService.class]"
 5 = {FileSystemResource@4189} "file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\service\MyBeanPostProcessor.class]"
 6 = {FileSystemResource@4190} "file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\service\UserServiceImpl.class]"
{ConfigurationClass@4654} "ConfigurationClass: beanName 'userController', class path resource [org/mfm/learn/springboot/controller/UserController.class]" -> {ConfigurationClass@4654} "ConfigurationClass: beanName 'userController', class path resource [org/mfm/learn/springboot/controller/UserController.class]"
{ConfigurationClass@4853} "ConfigurationClass: beanName 'myBeanPostProcessor', class path resource [org/mfm/learn/springboot/service/MyBeanPostProcessor.class]" ->
beanDefinitionNames = {ArrayList@4316}  size = 10
 0 = "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"
 1 = "org.springframework.context.annotation.internalAutowiredAnnotationProcessor"
 2 = "org.springframework.context.annotation.internalCommonAnnotationProcessor"
 3 = "org.springframework.context.event.internalEventListenerProcessor"
 4 = "org.springframework.context.event.internalEventListenerFactory"
 5 = "learnSpringBootApplication"
 6 = "org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory"
 7 = "userController"
 8 = "myBeanPostProcessor"
 9 = "userServiceImpl"

file

starter和其他框架的BeanDefinition如何添加的?

通过上面的分析你应该知道了ConfigurationClassPostProcessor是通过ConfigurationClassParser的parse方法查找和解析ClassPath下的BeanDefination

主要分为两步:

第一步主要是添加自己定义的Controller和Service相关的BeanDefinition

第二步主要做了什么?

答案是:添加了starter中其他框架的BeanDefinition如何添加的

那通过什么添加starter中的BeanDefinition呢?

在之前第一步解析import注解的时候,会扫描出来一个AutoConfigurationImportSelector。通过这个Selector会补充添加starter中的BeanDefinition

如下图所示:

file

AutoConfigurationImportSelector具体怎么补充starter中的BeanDefinition,其实一句话概括就是查找了所有ClassPath下的META-INF/spring.factories定义的EnableAutoConfiguration.class,通过条件注解解析加载出对应的自动配置,将配置中的@Bean加载为BeanDefinition

我通过一张图给大家解释下:

file

这里有大堆的封装,不是非常重要,可以跳过,大家知道封装了之前扫描import注解获取的AutoConfigurationImportSelector这个组件就可以了。

上图核心就是读取了META-INF/spring.factories中定义的一对AutoConfiguration。有了这些AutoConfiguration,自然就可以解析出对应的BeanDefinition了。

file

思考:ConfigurationClassPostProcessor的添加BeanDefination的设计

ConfigurationClassParser,内部使用了Scanner,扫描了ClassPath下几乎所有的BeanDefination,自己定义的注解bean也好,外部的基于条件注解的自动配置也好,都会扫描到,它们统统为Resource资源,只不过这些Resource资源表示的是Bean的配置类而已,一般使用Java定义的话都是ConfigurationClasss,它们作为候选者,通过Reader解析器解析后,将所有的BeanDefination放入到容器中。

其实符合我们之前将Spring容器抽象设计的思想,Resource-->Reader-->BeanDefination,只不过它封装很多组件类来实现这个过程而已。

经过思考后,你可以抓住重点,概况总结下。最终SpringBoot的Starter进行自动装配配置的核心流程和设计我们可以概况如下图所示:

file

第三方技术如何进行扩展的

思考:mybatis的Mapper如何被添加的?

之前我们添加了很多通过注解定义bean,无论是我们自己些的@Service、@Controller定义的Bean,还是starter中的@Bean。

可是还有一些Bean没有注解,也能被添加,比如mybatis的mapper。

public interface UserMapper extends BaseMapper<User> {

}

@MapperScan("org.mfm.learn.springboot.mapper")
@SpringBootApplication
public class LearnSpringBootApplication {

}

其实如果你了解无论是注解定义的还是没有注解的,其实在Spring看来都是一样的,因为它们都是Resource,我是根据不同的Scanner扫描到不同的Resource资源,通过指定的方式解析出来一些想要的Resource为BeanDefination而已。

那么你如果想添加额外的BeanDefination,可以自己定义Scanner来补充资源,定义指定的方式添加BeanDefination就行。

mybatis就是这么干的。

通过上面的代码你其实可以看到@MapperScan注解其实定义了自己的Scanner:MapperScannerRegistrar

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

MapperScannerRegistrar类实现了关键的两个接口ImportBeanDefinitionRegistrar, ResourceLoaderAware。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}

你可以猜想下:

一个应该Spring设计对添加额外BeanDefinition设计的接口ImportBeanDefinitionRegistrar

一个应该是ResourceLoaderAware,设置好ResourceLoader这个属性,你还记得ResourceLoader,它是不是封装ClassLoader,可以Classpath查找Resource。

因为Scanner主要的作用就是查找和过滤资源。

知道了这个逻辑理解Mapper怎么添加的就不难了。我们来看下代码:

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry);
    }
  }

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value"))
            .filter(StringUtils::hasText)
            .collect(Collectors.toList()));

    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("basePackages"))
            .filter(StringUtils::hasText)
            .collect(Collectors.toList()));

    basePackages.addAll(
        Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
            .map(ClassUtils::getPackageName)
            .collect(Collectors.toList()));

    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }
  }

可以看到MapperScannerRegistrar中主要执行是通过ClassPathMapperScanner来执行的,而且解析了@MapperScan中的属性,看看是否要过滤那些Bean之类的。

有了MapperScannerRegistrar,那它什么时候执行的呢?通过一张图我给大家讲下:

file

可以看到就是在我们之前分析触发扩展操作的时候,在执行ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()是,会检查到标记了@MapperScan注解的ConfigurationClass,处理这个类的时候,会执行MapperScannerRegistrar。

基于ImportBeanDefinitionRegistrar的扩展

Spring容器有很多扩展点,刚才看到MyBatis利用了ImportBeanDefinitionRegistrar进行扩展,还有很多技术都是用这种方式进行一些扩展。

比如Qconfig 、Sedis 、QSchedule 等

Qconfig QConfigAutoConfiguration

@Configuration
@Import(QConfigAutoConfiguration.Register.class)
@Internal
public class QConfigAutoConfiguration {

}

Sedis DbaccessAutoConfiguration

@Configuration
@Import(DbaccessAutoConfiguration.Register.class)
public class DbaccessAutoConfiguration {
}

QSchedule QScheduleAutoConfiguration

@Configuration
@Import(QScheduleAutoConfiguration.Register.class)
public class QScheduleAutoConfiguration {
}

我们以Qconfig 举例,通过ImportBeanDefinitionRegistrar可以进行什么样的扩展呢?如下所示:

@Configuration
@Import(QConfigAutoConfiguration.Register.class)
public class QConfigAutoConfiguration {
    
    class Register implements ImportBeanDefinitionRegistrar{
        public Register() {
            //读取本地配置
          
            //从配置中心获取配置
         
        }

        //添加关键的beanDefinition
        @Override
        public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
           
        }
        
   }
}

其实无论如何,你要透过现象看到本质,扩展操作最终,本质都是为了给容器补充一些BeanDefinition、一些Bean,为Bean设置一些属性。

而这些Bean实现什么功能,都可以,可以mq相关,配置相关,数据访问相关都可以,这就是Spring容器帮我们创建对象,对象的维护又不失去灵活性,这一点是非常好的。当然前提是你得熟练掌握Spring容器。

file

基于BeanFactoryPostProcessor的扩展

我们现在一直在整个run方法的流程中分析invokeBeanFactoryPostProcessors它的核心逻辑,前面通过invokeBeanFactoryPostProcessors中的if-else逻辑执行了很多扩展逻辑。

而invokeBeanFactoryPostProcessors除了if-else中的逻辑执行,其实还有外层的3个for循环需要执行。

上一节我们分析过for循环是在执行BeanFactoryPostProcessor的一个扩展操作。可以是之前我们已经将SpringBoot内部的BeanFactoryPostProcessor按照顺序执行过了。后面要执行的这些BeanFactoryPostProcessor哪里来的呢?

其实很简单,我们执行if-else的逻辑后,触发了很多自动装配配置和第三方的扩展操作,补充了一大堆BeanDefinition,这些BeanDefinition如果有新的BeanFactoryPostProcessor,就需要进行再次触发下BeanFactoryPostProcessor的扩展操作了。

至于触发第三方技术定义的这些BeanFactoryPostProcessor,可以做什么,其实就很多了。比如添加、修改BeanDefinition,解析自己第三方的配置文件等等。

这里我就不具体举例了,简单概况了下它执行的逻辑,整体如下图所示:

file

小结

到这里,我们就分析完了SpringBoot非常核心的功能,通过分析invokeBeanFactoryPostProcessors的扩展点的执行,分析了SpringBoot自动装配配置的原理。

其实这里最关键的不是SpringBoot自动装配的原理,最关键的主要是两点:

第一点:自动装配的关键其实是基于扩展点接口BeanFactoryPostProcessor的触发设计,根于Resource到BeanDefinition的抽象设计。其他的不过是绑定到了某个类或者方法的执行流程中而已,或者基于约定查找了固定名称的配置文件而已。

第二点:一个好的框架,是不断完善的,在一些核心的操作中,如果想要灵活,仍可以做额外的扩展设计。ConfigurationClass解析就是很好的例子,通过ImportBeanDefinitionRegistrar等扩展设计,灵活的开放出来,方便让第三方技术对Bean配置的做补充等。

最后留一个小的思考:SpringMVC常用的核心组件的BeanDefinition是什么时候加载的呢?如果理解了今天的内容,相信你肯定能回答上来。

答上来的话,其实你也就懂了SpringMVC如何和SpringBoot整合的了。

好,我们下节再见!

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

posted @ 2021-10-03 20:55  _繁茂  阅读(439)  评论(0编辑  收藏  举报