美文网首页
SpringBoot自动配置

SpringBoot自动配置

作者: 布拉德老瓜 | 来源:发表于2021-09-02 16:39 被阅读0次

Spring作为一个应用开发容器,很好地解决了对象关系管理的问题。但是在使用的时候,当需要引入第三方组件时,需要为组件做大量的配置工作,同时还需要手动去解决组建的版本依赖问题,才能让组件正常工作起来。为了让开发变得更简单一些,SpringBoot引入了自动配置,开箱即用的想法。

组件依赖的问题由starter来解决,在starter内部,定义了该组件所需的依赖关系和被依赖组件的版本,预先将依赖项进行打包,通过引入一个starter,就可以将约定好的依赖文件完成导入,从而解决了依赖引入的困扰。

当依赖项被引入之后,有些依赖是不能直接工作的,需要为其进行配置,创建对应的配置类,注入属性,然后才能开始使用。SpringBoot采用约定大于配置的思想,为应用提供了大量的默认配置,在使用组件时,只需要对其进行少量配置即可使用。

所谓自动配置实际上是指,被依赖的组件,自己提供了大量的默认配置信息,在容器启动过程中,将自动配置信息封装为java对象,注入到组件内部。那么springboot是如何读取到这些配置信息,并将其封装到对应的配置类中的呢?这就是本文的重点:SpringBoot自动配置的过程与原理。

1.启动过程简述

一个典型的SpringBoot项目启动都需要如下的主类,然后通过执行执行main方法启动。

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

静态run方法实际上是经历了两步: 创建SpringApplication对象, 执行该对象的run方法。

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }

1.1 创建SpringApplication对象

构造方法逻辑比较简单: 将主类primarySource注入对象的field, 判断应用类型(如reactive, servlet...), 从"META_INF/spring.factories"中加载所有的初始化器和监听器。

    public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

1.2 application.run(args)

run方法的核心作用是:创建IOC容器,并完成单例对象的创建工作。

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        // 通知应用开始启动
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            // 创建应用上下文
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            // 准备容器
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新容器 (spring中的refresh还有印象吗)
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

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

既然核心工作是创建IOC容器并完成对象的创建工作,那么关键的步骤就是从createApplication到refreshContext.

            context = this.createApplicationContext();
            //exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
1.2.1 创建容器

根据应用类型,创建对应的AnnotationConfig...ApplicationContext, 典型的如servlet类型的应用容器:


创建ApplicationContext
1.2.2 prepareContext

创建了应用容器(上下文)之后,为其设置环境变量,为之前设置进来的初始化器 调用初始化方法。手动注册几个单例Bean。然后最关键的是,需要加载并注册主启动类(因为它上面有核心注解@EnableSpringApplication,后续要解析该注解,然后根据该注解去进行包扫描、jar包下自动配置类的读取等工作)

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        this.applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }

        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }

        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        this.load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

完成prepareContext之后, 容器的beanDefinitionMap内就注册了启动类的信息。


image.png
1.2.3 refreshContext

刷新容器,过程和Spring大同小异。在这里重点关注SpringBoot是如何做到自动配置的,即他如何从被引入的jar包中获取自动配置类,并创建相应的对象实例。

    private void refreshContext(ConfigurableApplicationContext context) {
        this.refresh((ApplicationContext)context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
            }
        }

    }
    protected void refresh(ConfigurableApplicationContext applicationContext) {
        applicationContext.refresh();
    }

AbstractApplicationContext::refresh()

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

从prepareRefresh到prepareBeanFactory可以去Spring中了解是如何准备IOC容器beanFactory的。
自动配置的重点在于BeanFctory的postProcess过程。这里以AnnotationConfigServletWebServerApplicationContext为例说明。 首先是调用postProcessBeanFactory()方法,注册一个BeanPostProcessor:xxxAwareProcessor。然后通过BeanFcatoryPostProcessor对beanFactory对象进行后处理。

  • 注册beanPostProcessor(简单跳过)
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        super.postProcessBeanFactory(beanFactory);
        if (this.basePackages != null && this.basePackages.length > 0) {
            this.scanner.scan(this.basePackages);
        }

        if (!this.annotatedClasses.isEmpty()) {
            this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
        }
    }
  • invokeBeanFactoryPostProcessors
    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors());
        if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean("loadTimeWeaver")) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }

    }

Delegate.invokeBeanFactoryPostProcessors(beanFactory, processors)方法会遍历processors,对于处理bean定义信息的postProcessor,去处理beanDefinition的注册。在registryProcessors中,有一个比较关键的ConfigurationClassPostProcessor, 通过delegate.invokeBeanDefinitionRegistryPostProcessors()来最终调用它的processConfigBeanDefinitions方法,来完成自动配置Bean的注册。

invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
image.png

processConfigBeanDefinitions的逻辑是:从当前容器中获取已经注册的beanName,创建parser,然后依次对其进行递归的parse。被解析的重点对象自然是主类。


image.png
image.png

主类是一个配置类,快进到doProcessConfigurationClass方法。该方法中主要做了以下三件事: 1.解析注解标签,如果是一个@Component类型的注解,那么它是一个Component,需要processMemberClasses;2.进行componentScan, 将主类所在包及其子包下被@Component系列注解标注的类注册到容器; 3.处理@Import等标签

    protected final ConfigurationClassParser.SourceClass doProcessConfigurationClass(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate<String> filter) throws IOException {
        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            this.processMemberClasses(configClass, sourceClass, filter);
        }

        Iterator var4 = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, PropertySource.class).iterator();

        AnnotationAttributes importResource;
        while(var4.hasNext()) {
            importResource = (AnnotationAttributes)var4.next();
            if (this.environment instanceof ConfigurableEnvironment) {
                this.processPropertySource(importResource);
            } else {
                this.logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }

        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            Iterator var14 = componentScans.iterator();

            while(var14.hasNext()) {
                AnnotationAttributes componentScan = (AnnotationAttributes)var14.next();
                Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                Iterator var8 = scannedBeanDefinitions.iterator();

                while(var8.hasNext()) {
                    BeanDefinitionHolder holder = (BeanDefinitionHolder)var8.next();
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }

                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        this.parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }

        this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);
        importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        if (importResource != null) {
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            String[] var20 = resources;
            int var22 = resources.length;

            for(int var23 = 0; var23 < var22; ++var23) {
                String resource = var20[var23];
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }

        Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(sourceClass);
        Iterator var18 = beanMethods.iterator();

        while(var18.hasNext()) {
            MethodMetadata methodMetadata = (MethodMetadata)var18.next();
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        this.processInterfaces(configClass, sourceClass);
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                return sourceClass.getSuperClass();
            }
        }

        return null;
    }

我们重点关注processImports, 通过主类上@SpringBootApplication->@EnableAutoConfiguration->@Import({AutoConfigurationImportSelector.class}),引入了AutoConfigurationImportSelector。


获取ImportSelector,完成selectImports

此时,就可以获取到这个ImportSelector,执行器selectImports方法。
后面的事情大家都知道了,selector通过SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, AppClassLoader)去加载META_INF/spring.factories文件,读取所有自动配置类的类名,完成过滤后,将类信息注册到容器。

所有的BeanFactoryPostProcessor执行结束之后,postProcessBeanFactory也就结束了。后面的过程就跟Spring没啥区别了。通过finishBeanFactoryInitialization去getBean,创建单例对象,属性注入,完成初始化过程,后处理Bean的过程中完成代理对象的创建就不细说了。

相关文章

网友评论

      本文标题:SpringBoot自动配置

      本文链接:https://www.haomeiwen.com/subject/lkzqwltx.html