美文网首页
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