专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

SpringBoot源码解析-Bean的加载与自动化配置

springboot作为一个基于spring开发的框架,自然也继承了spring的容器属性。容器中的bean自然成为了springboot各种功能的基础。本节就来分析一下springboot如何将各种bean加载进容器中。


开始分析之前首先我们先概览一下springboot框架究竟加载了多少bean。在main函数中添加如下代码,运行。

public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);
        Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
    }

不出意外的话,控制台会打印出上百个bean的名字。虽然我们仅仅只写了两个类而已!那么这些类的加载有何规则呢?相比于spring的xml配置文件,springboot的自动化配置又是如何实现的?这些都将在本节揭晓。

public ConfigurableApplicationContext run(String... args) {
                ...
                //创建ApplicationContext
            context = createApplicationContext();
            ...
            //做一些初始化配置
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            ...
    }

首先我们进入SpringApplication的run方法中,在run方法中我们看到和ApplicationContext有关的代码一共有4行,第一行创建了ApplicationContext,第二行做了一些初始化配置,第三行调用了refresh方法,读过spring源码的话应该知道这个方法包含了ApplicationContext初始化最重要也最大部分的逻辑,所以这行待会会重点分析,最后一行是一个空方法,留着子类覆写。

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                ...
                }
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

首先进入create方法,在SpringApplication初始化的时候,我们已经知道了这是一个网络服务,所以这边创建的类是DEFAULT_SERVLET_WEB_CONTEXT_CLASS类,(org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext) 在这边直接调用了无参构造函数。先进入构造函数看一下做了那些事情。

    public AnnotationConfigServletWebServerApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

初始化了reader和scanner组件,reader是用来注册bean的,scanner是用来扫描bean的。这两个组件初始化的逻辑都不复杂,读者可以自行理解。但是重点关注一个地方。在reader的构造函数中:

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this(registry, getOrCreateEnvironment(registry));
    }

        public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        ...
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
        registerAnnotationConfigProcessors(registry, null);
    }

    public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
            BeanDefinitionRegistry registry, @Nullable Object source) {

        ...
        if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        ...
    }

一个ConfigurationClassPostProcessor的bean被注入到了容器中,这个地方留意一下,后面这个bean很重要。

创建完成了之后,我们看一下prepareContext方法

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        ...
        load(context, sources.toArray(new Object[0]));
        ...
    }

prepareContext方法中,调用了一些监听器,和初始化接口,但是最重要的是load这个方法。load这个方法,将我们main方法的这个类传入了容器中。这个类上面有一个非常重要的注解SpringBootApplication。

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);
        Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
    }

}

下面就进入到了最重要的refresh方法,如果读过《spring源码深度解析》这本书的话,这个地方的逻辑应该感到很亲切,没读过的话强烈建议读一下,不管spring怎么发展,基础还是那些的。

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            ...
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

            ...
    }

所以refresh方法中的逻辑我也不多介绍了,直接进入主题。invokeBeanFactoryPostProcessors方法。

    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
        ...
    }

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

            ...
            List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
            String[] postProcessorNames =
                    beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();
            ...
    }

在invokeBeanFactoryPostProcessors方法中,从容器中获取了BeanDefinitionRegistryPostProcessor类型的类,然后执行了这些类的postProcessBeanDefinitionRegistry方法。还记得上面我让你们重点关注的ConfigurationClassPostProcessor么,他就是实现了BeanDefinitionRegistryPostProcessor,所以这个地方会调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法。那么我们进入方法瞧瞧。

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        ...
        processConfigBeanDefinitions(registry);
    }

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

        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            ...
            //判断@Configuration注解
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
        ...
        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 {
        //解析带有@Configuration注解的类
            parser.parse(candidates);
        ...
    }

processConfigBeanDefinitions方法主要有两个逻辑,首先判断类上是否带有@Configuration注解,然后解析该类。其实在这儿,主要解析的就是@SpringBootApplication注解。因为点开@SpringBootApplication注解的源码

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@Configuration
public @interface SpringBootConfiguration {

}

@SpringBootApplication注解上面有@SpringBootConfiguration注解,而后者又包含了@Configuration注解,所以这个地方,解析的就是带有@SpringBootApplication注解的类。进入parse方法。

    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
        ...

        this.deferredImportSelectorHandler.process();
    }

主要有两个逻辑,我们一个一个来分析。首先再次进入parse方法

    protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(metadata, beanName));
    }

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        ...
        SourceClass sourceClass = asSourceClass(configClass);
        do {
        //进入这个方法
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);
    }

在doProcessConfigurationClass中,我们看到了熟悉的Component,PropertySources,ComponentScan,ImportResource,以及Import注解,上述几个注解的功能大家应该都很熟悉了,我就不多介绍了,这些注解在这儿就完成了他们的使命,经过这个方法后,我们自己写的类就会全部进入springboot容器中了。

下面开始分析this.deferredImportSelectorHandler.process();

public void process() {
            List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
            this.deferredImportSelectors = null;
            try {
                if (deferredImports != null) {
                ...
        }

进入方法后发现如果deferredImportSelectors为空的话,就什么都做不了。但是调用debug后发现这个地方是有值的,那么他是什么时候被放进来的呢。我们回头看刚刚的doProcessConfigurationClass方法。

    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        ...
        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);
        ...
    }

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

        ...
                        if (selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectorHandler.handle(
                                    configClass, (DeferredImportSelector) selector);
                        ...
        }
    }

        public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
            ...
                this.deferredImportSelectors.add(holder);
            }
        }

在processImports发现了添加的痕迹。但是添加有个前提条件是要import导入的类selector instanceof DeferredImportSelector,这个条件是怎么实现的呢?答案就在@SpringBootApplication注解中。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
        BeanFactoryAware, EnvironmentAware, Ordered {

所以到这儿我们就知道了deferredImportSelectors里面有一个元素,就是这边的AutoConfigurationImportSelector。

所以到这儿,我们就可以接着分析process方法了

        public void process() {
            List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
            this.deferredImportSelectors = null;
            try {
                if (deferredImports != null) {
                    DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                    deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                    //注册
                    deferredImports.forEach(handler::register);
                    //解析
                    handler.processGroupImports();
                }
            }
            finally {
                this.deferredImportSelectors = new ArrayList<>();
            }
        }

一个注册方法,一个解析方法,注册方法逻辑比较简单,我们直接进入解析方法。

        public void processGroupImports() {
            for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
            //这个地方看一下getImports方法
                grouping.getImports().forEach(entry -> {
                    ...
                    //这个方法标记一下,processImport待会回来
                        processImports(configurationClass, asSourceClass(configurationClass),
                                asSourceClasses(entry.getImportClassName()), false);
                    ...
            }
        }

        public Iterable<Group.Entry> getImports() {
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
            //重点看process方法
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            return this.group.selectImports();
        }

        public void process(AnnotationMetadata annotationMetadata,
                DeferredImportSelector deferredImportSelector) {
            ...
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                            annotationMetadata);
            ...
        }

    protected AutoConfigurationEntry getAutoConfigurationEntry(
            AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        ...
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        ...
    }

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        ...
        return configurations;
    }

SpringFactoriesLoader.loadFactoryNames这个方法熟悉么,一直在用,所以话不多说,先看看getSpringFactoriesLoaderFactoryClass返回了一个什么类。返回的是EnableAutoConfiguration.class; 所以进入配置文件查看。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
...
...
...

你应该会看到这么长长的一串配置,这里就是springboot自动化配置的中心了。我就以aop来展示一下springboot是如何简化spring的配置的。

首先经过我们刚刚的一串逻辑org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,这个类会被加载进容器中,那么这个类,和aop又有啥关系呢。

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
        AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
    public static class JdkDynamicAutoProxyConfiguration {

    }

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
    public static class CglibAutoProxyConfiguration {

    }

}

查看该类的源码,发现该类加载时有两个判断条件,容器中需要有EnableAspectJAutoProxy.class, Aspect.class, Advice.class,AnnotatedElement.class这几个注解,或者有spring.aop相关的配置。(关于Conditional条件的机制后面再详细解读,这个地方大概了解一下即可)

如果我们在启动时的类上添加了EnableAspectJAutoProxy注解的话,该注解会加载AspectJAutoProxyRegistrar类,这个类又会向容器注入AnnotationAwareAspectJAutoProxyCreator类,而后者正是aop的核心类。只要这个类进入容器,容器就带有了aop功能(aop如何实现的看我推荐的那本书,书上很详细)。

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

那么如果我没有显示的添加EnableAspectJAutoProxy注解会怎样呢?如果没有显示添加的话,只要满足其他条件,AopAutoConfiguration类依然会被加载进容器,而他进入容器后,里面得到两个静态类也会被扫描进容器,而这两个类都是带有EnableAspectJAutoProxy注解的,所以aop功能依然可以实现。

所以当我们获得了自动化配置的这些支持后,就该回到刚刚标记的processImport方法了。

        public void processGroupImports() {
            for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
                grouping.getImports().forEach(entry -> {
                    ConfigurationClass configurationClass = this.configurationClasses.get(
                            entry.getMetadata());
                    try {
                    //刚刚标记的方法
                        processImports(configurationClass, asSourceClass(configurationClass),
                                asSourceClasses(entry.getImportClassName()), false);
                    }
                    catch (BeanDefinitionStoreException ex) {
                        throw ex;
                    }
                    catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                "Failed to process import candidates for configuration class [" +
                                        configurationClass.getMetadata().getClassName() + "]", ex);
                    }
                });
            }
        }

这个方法会把我们获得的自动化配置相关支持全部导入容器,这样在经过spring那一套加载逻辑之后,我们的springboot项目就可以获得各种我们配置的功能了。


返回目录

文章永久链接:https://tech.souyunku.com/19193

未经允许不得转载:搜云库技术团队 » SpringBoot源码解析-Bean的加载与自动化配置

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们