SpringBoot 自动装配

SpringBoot自动装配

      • 自动装配做了什么?
      • 自动装配源码分析:
        • 处理@Import的注解,获取AutoConfigurationImportSelector类
        • 调用AutoConfigurationImportSelector.selectImports方法获取装配类
        • spring-autoconfigure-metadata.properties文件和spring.factories文件作用
        • spring.factories下的全集自动装配类为啥要过滤,怎么过滤?

前言:
  对于经常使用SpringBoot的程序员来说,SpringBoot大大简化了开发的工作量,节省了大量的配置工作,在SpringBoot启动的时候就自动将相关的配置给封装好了,也就是自动装配功能

自动装配做了什么?

  通常搭建一个基于spring 的 web 应用,我们需要做以下工作:

1、pom 文件中引入相关jar包,包括 spring、springmvc、redis、mybaits、log4j、mysql-connector-java 等等相关jar …

2、配置web.xml,Listener配置、Filter配置、Servlet配置、log4j配置、error配置 …

3、配置数据库连接、配置spring事务

4、配置视图解析器

5、开启注解、自动扫描功能

6、配置完成后部署tomcat、启动调试

搭个初始项目不一会就一个小时甚至半天过去了。而用 springboot 后,一切都变得很简便快速。

1、如下所示引入依赖

在这里插入图片描述

  mybatis-spring-boot-starter jar包不仅帮我们整合了 mybatis 使用的基础 jar包,而且多了一些 mybatis-antoconfigure jar包,这些jar包主要是一些配置类,比如:dataSource的配置,transactionManager的配置…,而不需要像spring那样我们自己去xml文件配置 dataSource 的 bean对象了,但对于dataSourece 一些配置的修改,则需要在application.yml配置,如下:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mytest?characterEncoding=utf-8&useSSL=false
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
    ......

  那配置类什么时候被加载,yml文件什么时候被加载,yml配置对配置类进行修改,再到根据配置类实例化所需的对象是怎么个过程,接下来我们进行源码分析

自动装配源码分析:

  首先以debug方式运行我们的启动类:

public class MyApplicationContext {

    public static void main(String[] args) {
        SpringApplication.run(MyApplicationContext.class, args);
    }
}

我们直接进入到SpringApplication.run()方法:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[]{ConfigurableApplicationContext.class}, context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            // 重点是这个方法,前面都是做一些初始化操作,这个方法是和spring的整合
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        } catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

  一直debug到 AbstractApplicationContext.refresh()方法,该方法如下:

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                // 用于自动装配、处理 @ComponentScan 注解,生成BeanDefinition也加入到BeanFactory
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            } catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            } finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

  从AbstractApplicationContext.invokeBeanFactoryPostProcessors(beanFactory)方法debug进去,如下:

SpringBoot 自动装配_第1张图片
  BeanFactoryPostProcessor:工厂钩子,允许自定义修改应用上下文的bean definitions 属性;说通俗一些就是可以管理我们的bean工厂内所有的beandefinition(未实例化)数据,可以随心所欲的修改属性。

  我们从PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());方法debug进去:

public static void invokeBeanFactoryPostProcessors(
         ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

		......
		/**
		
		*/
        // 重点是这个类,执行BeanDefinitionRegistryPostProcessors
        // 这里是 ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessors
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);   
    	......
    }

  BeanDefinitionRegistryPostProcessor:它是个接口继承了 BeanFactoryPostProcessor ,允许在常规 BeanFactoryPostProcessor 检测开始之前,注册更多的bean定义进来,

SpringBoot 自动装配_第2张图片
  ConfigurationClassPostProcessor:它实现了 BeanDefinitionRegistryPostProcessor 接口,用于处理自动装配类,它会解析会加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。

  进入ConfigurationClassPostProcessor.postProcessBeanDefinitionRegis方法:

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);
    }

  进入ConfigurationClassPostProcessor.processConfigBeanDefinitions方法如下:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    
     ......
     // 这个方法来对主类上的注解进行解析
     parser.parse(candidates);
     ......
}

  进入ConfigurationClassParser.pase方法如下:

SpringBoot 自动装配_第3张图片

  debug进入ConfigurationClassParser.processConfigurationClass方法:

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        // 判断是否有@Conditional注解
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(),    ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }

        // 这里existingClass == null 直接跳过
        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        if (existingClass != null) {
            if (configClass.isImported()) {
                if (existingClass.isImported()) {
                    existingClass.mergeImportedBy(configClass);
                }
                // Otherwise ignore new imported config class; existing non-imported class overrides it.
                return;
            }
            else {
                // Explicit bean definition found, probably replacing an import.
                // Let's remove the old one and go with the new one.
                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }
        }

        // Recursively process the configuration class and its superclass hierarchy.
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            /**
			通过读取源类的 注解、成员、方法来处理构建一个完成的配置
			类。这个方法可以被多次调用,用来处理源类,
			这里我们只需要关系我的们 启动类(它也是个源类),
			还有什么类可以当作源类,被@Componet修饰的类也会当作源类进行解析。
			*/
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);

    }
处理@Import的注解,获取AutoConfigurationImportSelector类

  debug进入ConfigurationClassParser.doProcessConfigurationClass这个方法:

    @Nullable
    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {

        // Recursively process any member (nested) classes first
        // 这个方法直接跳过
        processMemberClasses(configClass, sourceClass);

        //  Process any @PropertySource annotations
        //  没有@PropertySource注解,也跳过
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            }
            else {
                logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }

        // Process any @ComponentScan annotations
        // 处理 @ComponentScan 注解,也不是本文重点,我们跳过
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                // The config class is annotated with @ComponentScan -> perform the scan immediately
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    	// 这里会把每个扫描出来的类当成 Source类进行解析
                        parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }

        // Process any @Import annotations
        // 重点来了,处理启动类上 @Import的注解
        processImports(configClass, sourceClass, getImports(sourceClass), true);
          
       	......

    }   

  ConfigurationClassParser.getImport(sourceClass)方法解析如下,有兴趣的朋友可以自己去看里面怎么解析的(递归)

SpringBoot 自动装配_第4张图片

  我们验证下 启动类是否有3个@Import注解对应这三个类:

  1. @MapperScan注解点进去,这个注解跟本文自动装配无关:

SpringBoot 自动装配_第5张图片

2、由@SpringBootApplication注解→@EnableAutoConfiguration

SpringBoot 自动装配_第6张图片

3、由@SpringBootApplication注解→@EnableAutoConfiguration→@AutoConfigurationPackage

SpringBoot 自动装配_第7张图片

  这里加载了3个@Import上的类完全对应上了,我们接下来看,这些类是干什么用的,跟自动装配有啥关系?

  debug进入ConfigurationClassParser.processImports(configClass, sourceClass, getImports(sourceClass), true)方法一探究竟

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,  Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

        //这里不为空 跳过
        if (importCandidates.isEmpty()) {
            return;
        }

        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    // 直接到这里,遍历3个@import对应的实体类
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // AutoConfigurationImportSelector 这个类进入这里
                        
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        ......业务逻辑
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // AutoConfigurationPackages.Registrar这个类进入这里
                        
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                         ......业务逻辑
                    }
                    else {
                        // 其他类进入这里
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        ......业务逻辑
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                                configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }

1、我们按顺序 先看 AutoConfigurationPackages.Registrar的处理逻辑:

		// 获取AutoConfigurationPackages.Registrar的class对象
        Class<?> candidateClass = candidate.loadClass();
        // 实例化AutoConfigurationPackages.Registrar对象 里面就是通过反射生成
        ImportBeanDefinitionRegistrar registrar =
                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
        // 这里会判断 AutoConfigurationPackages.Registrar instanceof Aware,这里不是,跳过
        ParserStrategyUtils.invokeAwareMethods(
                registrar, this.environment, this.resourceLoader, this.registry);
        // 把AutoConfigurationPackages.Registrar添加到 ConfigurationClass的importBeanDefinitionRegistrars(Map)
        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());

2、接下来我们看 AutoConfigurationImportSelector这个类的处理逻辑:这里也并未调用,只是做了封装处理

		// 获取AutoConfigurationImportSelector的class对象
        Class<?> candidateClass = candidate.loadClass();
        // 一样的逻辑,实例化AutoConfigurationImportSelector对象
        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
        // 这里判断AutoConfigurationImportSelector instanceof Aware 为true,然后为这里判断AutoConfigurationImportSelector设置一些属性:类加载器、BeanFactory、environment、resourceLoader
        ParserStrategyUtils.invokeAwareMethods(
                selector, this.environment, this.resourceLoader, this.registry);
        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
        // 进入这里,new一个  ConfigurationClassParser.DeferredImportSelectorHolder对象添加到deferredImportSelectors(List)
            this.deferredImportSelectors.add(
                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
        } else {
            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
            processImports(configClass, currentSourceClass, importSourceClasses, false);
        }

  解析完,我们debug返回到ConfigurationClassParser.parse(Set configCandidates)方法:

SpringBoot 自动装配_第8张图片

  我们debug跟进去:

SpringBoot 自动装配_第9张图片

  ConfigurationClassParser.getImports方法如下:

SpringBoot 自动装配_第10张图片

  进入AutoConfigurationImportSelector.process方法:

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
      // 跟进这个方法 我们看做了什么
            String[] imports = deferredImportSelector.selectImports(annotationMetadata);
    
            String[] var4 = imports;
            int var5 = imports.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String importClassName = var4[var6];
                this.entries.put(importClassName, annotationMetadata);
            }

        }
调用AutoConfigurationImportSelector.selectImports方法获取装配类

  debug进入AutoConfigurationImportSelector.selectImports方法,重点逻辑都在这里面,这是自动装配的又一关键所在:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            // 1.加载自动装配元数据,哪些数据? 我们跟进去看下
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            // 2.以EnableAutoConfiguration为key在spring.factories文件中 查询出所有的实现类是自动装配类的全集,封装成List集合
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            // 去重 跳过
            configurations = this.removeDuplicates(configurations);
            // 生成exclusions  也就是pom.xml中 需要排查的
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            // 对configurations配置 做排除检测
            this.checkExcludedClasses(configurations, exclusions);
            // configurations配置 移除 排除项
            configurations.removeAll(exclusions);
            // 3、这里对配置类做过滤,不需要用的配置类我们 不进行后面实例化
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }
spring-autoconfigure-metadata.properties文件和spring.factories文件作用
  1. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader)方法解析

SpringBoot 自动装配_第11张图片

  这里会加载META-INF/spring-autoconfigure-metadata.properties下的数据。META-INF/spring-autoconfigure-metadata.properties的用处是啥? 起码看里面数据结构我们不难发现,是key-value映射形式,

  • key:是springboot整合第三方jar包的配置类(例子:es、redis、mybatis…等配置类)

每个配置类可能有4种组合方式,如下:

  1. 配置类全限定类名.AutoConfigureBefore:value代表 在加载key之前先要加载value这些配置类
  2. 配置类全限定类名.AutoConfigureAfter:value代表 在加载key之后要加载value这些配置类
  3. 配置类全限定类名.ConditionalOnClass:value代码 在加载key时,需要的第三方jar包的类。比如:es配置类,需要org.elasticsearch.client.Client这个类,这个类属于es jar包自己的。

整个文件下有2个这样的文件

SpringBoot 自动装配_第12张图片

mybatis的总共8个

SpringBoot 自动装配_第13张图片

springboot 561个 加起来 569个

SpringBoot 自动装配_第14张图片

  验证:刚好569个 配置类映射关系

  1. List configurations = this.getCandidateConfigurations(annotationMetadata, attributes)方法解析,以EnableAutoConfiguration为key在spring.factories文件中 查询出所有的实现类是自动装配类的全集
    SpringBoot 自动装配_第15张图片

  configurations总共返回srpingboot的110 + mybatis的2个 = 112个

spring.factories下的全集自动装配类为啥要过滤,怎么过滤?
  1. configurations = this.filter(configurations, autoConfigurationMetadata)方法解析;

  这里有个问题,spring.factories下的全集自动装配类,为啥要过滤?

  如果springboot单单加载spring.factories文件下全部配置类,那么会在启动的时候报很多类找不到的错误,因为我们并没有引入全部整合的第三方jar包。

  那么配置类是根据什么来过滤的呢?

  上文我们介绍到 META-INF/spring-autoconfigure-metadata.properties 文件下有种 key-配置类全限定类名.ConditionalOnClass 的数据格式,而value正是配置类需要的第三方jar包类,如果没导入这个jar包,而去加载这个类,那么肯定会报类找不到异常,而springboot正是基于这种方式判断的。

SpringBoot 自动装配_第16张图片

  拿到了配置类 需要依赖的第三方jar包的全限定类名,你怎么去判断是否有这些类,也就是是否导入了这些依赖呢?其实很简单:

SpringBoot 自动装配_第17张图片

  灵魂方法,通过class.forName(全限定类名),如果抛出异常,则说明没有导入这个类的jar包,也说明这个配置类不需要加载。

  至此,自动装配需要的配置类BeanDefinition也被加入到BeanFactory,等待被实例化。

你可能感兴趣的:(java,中间件,java,spring,boot)