IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Springboot启动流程核心知识点(一):Spring自动装配原理 -> 正文阅读

[Java知识库]Springboot启动流程核心知识点(一):Spring自动装配原理

目录

1 项目工程类信息注册

1.1 @Component形式

1.2 @ComponentScan形式

1.3 @Import形式

1.4?@ImportResource形式

1.5 beanMethods方式

1.6 解析接口中的beanMethods方式

1.7 递归解析当前类的父类

2 Spring自动装配原理

3 小结


对Springboot有所了解的读者,对一些理念想必很熟悉,如Springboot工程会默认加载当前主启动类下的所有类文件到IOC容器,以及Springboot可以通过自动装配的功能,实现对外部功能的无缝引入。

怎么说呢?大的方向是这样,但是如果更近一步,要问一下这些功能具体是如何实现的,只有通过源码才能完整而清晰的了解其每一步的实现意图。

下面我们结合Springboot的启动过程,来直接进入源码,再现Springboot的依托主启动类扫包和自动装配原理。

首先,通过主启动类的main方法进入SpringApplication的run方法:

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

之后再通过如下顺序:

this.refreshContext(context); (SpringApplication) --->>>

this.refresh(context); (SpringApplication) --->>>

refresh();(AbstractApplicationContext) --->>>

this.invokeBeanFactoryPostProcessors(beanFactory);(AbstractApplicationContext)

最终进入类PostProcessorRegistrationDelegate中的BeanFactory后置处理器执行方法invokeBeanFactoryPostProcessors。

在此方法中,第一次执行方法

invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

时,会执行一个特殊的BeanDefinitionRegistry后置处理器——ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法。

在此方法中会通过

this.processConfigBeanDefinitions(registry);

下的

parser.parse(candidates);

真正进入主启动类下项目工程类信息注册,以及Spring自动装配功能。

说了这么多,其实是为了让读者可以通过断点,启动任意一个Springboot项目,自己去断点调试一下,验证笔者所说,毕竟眼见为实,耳听为虚,实践才是最好的老师。

有兴趣的读者,可以直接进入ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法,再进入parser.parse(candidates),就可以直接而快速的了解接下来笔者要讲解的源码。

1 项目工程类信息注册

关于bean信息注册的方法,基本都在ConfigurationClassParser这个类中,processConfigBeanDefinitions实现bean信息注册的入口方法在ConfigurationClassParser的parse方法中:

 public void parse(Set<BeanDefinitionHolder> configCandidates) {
        //主启动类bean信息集合
        Iterator var2 = configCandidates.iterator();
        while(var2.hasNext()) {
            BeanDefinitionHolder holder = (BeanDefinitionHolder)var2.next();
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                //注册项目工程类信息
                    this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());
               .......
        }
        //注册Spring提供默认类信息
        this.deferredImportSelectorHandler.process();
    }

this.parse方法主要就是依据其传入的参数,也就是主启动类的bean定义信息BeanDefinition和BeanDefinitionHolder构造一个新的对象ConfigurationClass,再以ConfigurationClass和DEFAULT_EXCLUSION_FILTER,作为参数进入真正的执行方法processConfigurationClass。

private static final Predicate<String> DEFAULT_EXCLUSION_FILTER = (className) -> {
        return className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype.");
    };

进入processConfigurationClass后,首先会判断当前当前主启动类ConfigurationClass是否需要被拦截,主要是根据@Conditional注解和DEFAULT_EXCLUSION_FILTER的信息来判断:

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
        if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {

在执行完过滤条件后,再以ConfigurationClass和DEFAULT_EXCLUSION_FILTER参数构造一个参数SourceClass,其是ConfigurationClassParser的一个内部类,最终把ConfigurationClass、SourceClass和DEFAULT_EXCLUSION_FILTER三个作为参数,来执行下一步的类信息解析:

do {
     sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
   } while(sourceClass != null);

?有人看到这一步就会很奇怪,configClass和sourceClass,这两参数基本都是一样,只不过另一个又包装了一层,这样构造的意义在哪里呢?其原因主要也是为了框架的需要,这样可以利用递归,把主启动类包下的每一个已经加入IOC容器的类都当作是一个配置类,可以任意向下扩展。

它可以在自身已经被@Component或其他注解形式修饰,在已经加入IOC容器的前提下,通过@ComponentScan注解、@Import注解或者@ImportResource注解,来给IOC容器新增我们需要的指定类。

下面通过doProcessConfigurationClass方法分别介绍:

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

在介绍这个方法之前,我们先说一个前提,只要满足条件的基础上,方法正常执行,会返回一个configClass,被缓存到当前类的一个Map属性configurationClasses中,后面会被构造成bean的定义信息被注册到IOC容器。

在此基础上,我们会对当前类,有可能出现的扩展情况,进行处理,通俗说,就是在当前类配置了一些属性,需要把新的类信息注入IOC容器

1.1 @Component形式

如果当前类带有@Component注解,我们会对其内部类进行解析,看其内部类是否配置了@Component注解,需要被注入IOC容器:

private void processMemberClasses(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate<String> filter) throws IOException {
        //获取内部类
        Collection<ConfigurationClassParser.SourceClass> memberClasses = sourceClass.getMemberClasses();
        //如果存在内部类
        if (!memberClasses.isEmpty()) {
            List<ConfigurationClassParser.SourceClass> candidates = new ArrayList(memberClasses.size());
            Iterator var6 = memberClasses.iterator();
            ConfigurationClassParser.SourceClass candidate;
            while(var6.hasNext()) {
                candidate = (ConfigurationClassParser.SourceClass)var6.next();
                //内部类是否需要被注入IOC容器
                if (ConfigurationClassUtils.isConfigurationCandidate(candidate.getMetadata()) && !candidate.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
                    //满足条件注入
                    candidates.add(candidate);
......
//递归方法,最终会再调用doProcessConfigurationClass
                        this.processConfigurationClass(candidate.asConfigClass(configClass), filter);
.......

实操用法如下:

@SpringBootApplication
public class BookstoreApplication {
    public static void main(String[] args) {
        SpringApplication.run(new Class[]{BookstoreApplication.class, TestApp.class}, args);
    }
    @Component
    class TestClass{
        private final String name = "123";
        TestClass(){
            System.out.println(name);
        }
    }
}

如上,会通过方法processMemberClasses把TestClass信息注入IOC。

1.2 @ComponentScan形式

因为第一次进入doProcessConfigurationClass方法,传入的就是已经加入容器内的主启动类,所以会解析主启动类的注解@ComponentScan,大致和前文介绍的一样,如果@ComponentScan的basePackages和basePackageClasses属性都没有定义的话,就加载主启动类当前路径下各级文件夹中所有的class:

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

下一步需要进行判断,这些加载的class是否满足立即被加载进容器的条件,简单来说就是类上是否包含四个注解:

[org.springframework.context.annotation.Import, org.springframework.stereotype.Component, org.springframework.context.annotation.ImportResource, org.springframework.context.annotation.ComponentScan]

如果含有其中之一, 则使用递归调用的方式,重新调用ConfigurationClassParser类的parser方法:

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

以这样的方式,把加入容器中的每一个类都当作了潜在的配置类,可以灵活的引用没有配置进容器的类。

实操示例如下:

@Configuration
@ComponentScan("test")
public class TestConfiguration {
}

当前类在com包下,被扫描进IOC容器,因为使用了@ComponentScan注解,可以加载外部包test下,标注了@Component的类:

package test;
import org.springframework.stereotype.Component;

@Component
public class TestComponentScan{
    TestComponentScan(){
        System.out.println("test----TestComponentScan if exist in IOC");
    }
}

1.3 @Import形式

this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);

把当前类中注解了@Import的类信息注册到容器中,注意这里有一种特殊情形需要注意,如果式主启动类,由于它通过@EnableAutoConfiguration复合注解,组合了@Import({AutoConfigurationImportSelector.class})注解,相当于要加载一些Spring自带的需要加入容器的类,这就属于特殊情形。

Spring框架使AutoConfigurationImportSelector实现了一个特殊接口DeferredImportSelector,把它归属到特殊的一类Import,等当前工程项目中所有的类注册完成后再继续加载:

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

此时,其他的普通类通过@Import注解依然通过递归调用的方式,可以对@Import其他引入的类进行继续解析:?

this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);

1.4?@ImportResource形式

根据注解信息,找到Spring的配置文件路径,之后加载配置信息,把配置的bean信息注册到IOC容器:

importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);

具体操作示例如下:

@SpringBootApplication
@ImportResource(locations={"classpath:beans.xml"})
public class BookstoreApplication {
    public static void main(String[] args) {
        SpringApplication.run(BookstoreApplication.class, args);

?beans.xml具体信息如下,注册一个不在当前主启动类的bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--将 HelloService 以xml的方式,注入到容器中-->
    <bean id="helloService" class="test.HelloService"></bean>
</beans>

?在项目启动后,HelloService就可以直接从容器中获取:

public class HelloService {
    HelloService(){
        System.out.println("基于@ImportResource注解的bean--HelloService注入");
    }
}

1.5 beanMethods方式

在实际的项目中,还有一种情况用的比较多,比如当前类标注了@Configuration,就代表是一个配置类,之后在方法中可以用@Bean注解的方式来把返回值注入到容器中。

Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(sourceClass);
        Iterator var18 = beanMethods.iterator();
        while(var18.hasNext()) {
            MethodMetadata methodMetadata = (MethodMetadata)var18.next();
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

具体用法如下:

@Configuration
public class TestComponentScan{
    @Bean
    public TestBeanMethod testBeanMethod(){
        return new TestBeanMethod();
    }
}

1.6 解析接口中的beanMethods方式

主要是针对当前类已经实现的接口中,其方法上,是否有需要注入IOC容器中的类:

this.processInterfaces(configClass, sourceClass);

示例如下:

public interface InterfaceService {
     @Bean
     default Object interfaceBean(){
          return new Object();
     }
}
@Component
public class CustomApplicationListener  implements InterfaceService  {

}

?CustomApplicationListener ?会在自身注入容器的同时,把InterfaceService中注入了@Bean方法interfaceBean也加入进容器。

其代码实现为:

private void processInterfaces(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass) throws IOException {
        Iterator var3 = sourceClass.getInterfaces().iterator();
        while(var3.hasNext()) {
            ConfigurationClassParser.SourceClass ifc = (ConfigurationClassParser.SourceClass)var3.next();
            Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(ifc);
            Iterator var6 = beanMethods.iterator();
            while(var6.hasNext()) {
                MethodMetadata methodMetadata = (MethodMetadata)var6.next();
                if (!methodMetadata.isAbstract()) {
                    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
                }
            }
            this.processInterfaces(configClass, ifc);
        }
    }

这些beanMethods的bean定义信息会依附于当前类bean信息而存在,具体表现为存在ConfigurationClass类的Set属性beanMethods中,在需要的时候解析出来。

1.7 递归解析当前类的父类

现在到了最后一步,首先判断这个类是否还有父类,如果没有,直接返回空,如果有,获取父类后,继续判断,如果这个父类不为空、包名开头不是java且不能是已经被解析的父类,否则返回空:

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;

取得返回值后,跳出循环,把当前ConfigurationClass返回,缓存到Map中:

do {
   sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
    } while(sourceClass != null);
this.configurationClasses.put(configClass, configClass);

经过ConfigurationClassParser类中的parse方法调用AnnotatedBeanDefinition类型的parse方法,我们完成了对当前项目工程下所有类,注册到IOC容器中的操作。

2 Spring自动装配原理

下面需要继续介绍,Spring自带的框架类,如何加入容器。在ConfigurationClassParser类中的执行方法为:

this.deferredImportSelectorHandler.process();

首先会获取到前面我们通过主启动类上的@Import注解导入的自动装配类信息,之后新建一个Group用来对这一类信息归类,把@Import中的信息导入到Group,之后对Group中的信息进行处理:

public void process() {
            List<ConfigurationClassParser.DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
            this.deferredImportSelectors = null;
            try {
                if (deferredImports != null) {
                    ......
                    deferredImports.forEach(handler::register);
                    handler.processGroupImports();
                }
            } finally {
                this.deferredImportSelectors = new ArrayList();
            }
        }
public void processGroupImports() {
            Iterator var1 = this.groupings.values().iterator();

            while(var1.hasNext()) {
                ConfigurationClassParser.DeferredImportSelectorGrouping grouping = (ConfigurationClassParser.DeferredImportSelectorGrouping)var1.next();
                Predicate<String> exclusionFilter = grouping.getCandidateFilter();
                grouping.getImports().forEach((entry) -> {
                    ConfigurationClass configurationClass = (ConfigurationClass)this.configurationClasses.get(entry.getMetadata());

最后通过grouping.getImports()方法来调用主启动类导入的类AutoConfigurationImportSelector中的process方法,来进行自动装配:

public Iterable<Entry> getImports() {
            Iterator var1 = this.deferredImports.iterator();

            while(var1.hasNext()) {
                ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
                this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
            }

进入AutoConfigurationImportSelector:

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
                return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
            });
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(this.getAutoConfigurationMetadata(), annotationMetadata);

获取配置实体:?

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

?执行扫描方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

?通过this.getSpringFactoriesLoaderFactoryClass()方法扫描META-INF/spring.factories中key为EnableAutoConfiguration的全类名:

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

Spring框架的示例配置为:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

?以上就是Spring自动装配的全部内容。

到了这一步,我们才讲完了把所有需要注册进容器的类缓存到一个Map集合,下一步就是取到Map集合的值,然后把值信息作为bean的定义信息,真正注册到容器中:

Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());
                configClasses.removeAll(alreadyParsed);
                if (this.reader == null) {
                    this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
                }

                this.reader.loadBeanDefinitions(configClasses);

这样,就完成了ConfigurationClassPostProcessor类中,第二次BeanDefinitionRegistry后置处理器中指定方法的执行:

invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();

执行完成后,清空currentRegistryProcessors,为下一次执行BeanDefinitionRegistry后置处理器方法做准备。

3 小结

想必很多读者对主启动类下包扫描,和Springboot自动装配原理都不陌生,这里只是给大家提供了一个源码级别的验证,让大家能够更清晰而完整的了解Springboot的一些原理。本文也结合了一些具体示例,希望对大家有所帮助。由于笔者水平有限,有不足之处,也望大家多多指教、包涵!

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 01:51:23  更:2022-09-15 01:51:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:29:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码