02-SpringBoot基本原理初探

SpringBoot基本原理初探

上文书说道,SpringBoot的结构主要包括4个部分:

  • pom.xml
  • application.properties 配置文件
  • 程序的主启动类
  • 测试类

本节就接着稍微了解一下基本的原理

1. pom.xml

众所周知,maven项目的pom文件,存放的都是些maven依赖,

springboot项目中的依赖文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--父级依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.kevin</groupId>
    <artifactId>springBoot-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springBoot-01</name>
    <description>springBoot-01</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--web场景的starter启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--springboot的单元测试启动-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--项目打包插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  1. 父级依赖:
<!--父级依赖-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.3</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

点开artifactId标签中的:spring-boot-starter-parent进去,发现里面还有一个父级依赖:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.6.3</version>
</parent>

点击spring-boot-dependencies进去,可以发现,该文件中包含了几乎所有要用到的依赖内容,这才是顶级依赖,springboot版本的控制中心:
在这里插入图片描述

一般情况下,我们在创建springboot项目之后,导入依赖不需要写版本号,但是如果在这个顶级依赖中没有所需要的依赖,就需要自行手动配置了。

2. 启动器:spring-boot-starter

在pom文件中可以看到有:spring-boot-starter-web、spring-boot-starter-test;

springboot-boot-starter-xxx:就是spring-boot的场景启动器,譬如:

spring-boot-starter-web,帮我们导入了web模块正常运行所依赖的组件;

在这里插入图片描述

SpringBoot将所有的功能场景都抽取出来(类似于公共方法的向上抽取成抽象方法似的),按类别(web、test)做成一个个的starter ,只需要在项目中引入这些starter,那么该启动器中相关的依赖都会被导入 , 我们要用什么功能就导入什么样的场景启动器。

3. 主启动类

创建的项目,默认的主启动类:SpringBoot01Application

package com.kevin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @SpringBootApplication 这个注解标注SpringBoot01Application这个类是一个主程序类
 * 说明这是一个Spring Boot应用
 */
@SpringBootApplication
public class SpringBoot01Application {
    public static void main(String[] args) {
        // 这不是一般的run方法,该方法直接启动整个服务
        SpringApplication.run(SpringBoot01Application.class, args);
    }

}

注解@SpringBootApplication标注了该类是一个主程序类,该注解是springboot的核心,至关重要,点进去(ctrl+鼠标左键)看看该注解都做了些啥:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {

}

可以看到该注解中还有很多注解:

@Target、@Retention、@Documented、@Inherited为四个为元注解(元注解可理解为元老级自带注解,可以用于自定义注解,之后再学习)

@componentScan注解

这个注解在Spring中很重要,也十分常见,对应XML配置中的元素,从字面意思可以理解为自动扫描:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

着重来看另外两个注解:

3.1 @SpringBootConfiguration

字面理解:SpringBoot的配置类 。该注解标注在某个类上 , 表示这是一个SpringBoot的配置类;点进去进去这个注解查看

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

该注解中又有几个注解,可以着重看一下**@Configuration**这一注解,该注解表示这类是一个配置类,对应spring的配置文件;再深入到这个注解中看:

  • @Configuration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
	// ......
}

这个注解中@Component,表明该类就是一个spring组件

因此通过对@SpringBootConfiguration这个启动类的深入了解,可以发现该启动类本身也是Spring中的一个组件而已,负责启动应用!

3.2 @EnableAutoConfiguration

该注解:开启自动配置功能,在使用springboot之前,我们需要自己进行配置的各种配置文件,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

继续进入该注解内部:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    // ......
}

同样的,@Target、@Retention、@Documented、@Inherited为四个为元注解为4个元注解,着重看一下**@AutoConfigurationPackage和@Import({AutoConfigurationImportSelector.class})**

这两个注解。

  • @AutoConfigurationPackage:自动配置包,进入该注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    // ......
}

@Import({Registrar.class}):Spring底层注解@import , 给容器中导入一个组件,Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;

  • @Import({AutoConfigurationImportSelector.class}) :给容器导入组件

导入的组件为:AutoConfigurationImportSelector :自动配置导入选择器,

该组件会导入哪些组件的选择器呢?点击去这个类看源码:

该类中有一个getCandidateConfigurations方法:获取候选配置:

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;
}
//上面方法中的getSpringFactoriesLoaderFactoryClass()方法 返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}
  • 获取候选配置的方法又调用了 SpringFactoriesLoader 类的静态方法loadFactoryNames(),点进去loadFactoryNames() 方法看看:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

它又调用了 loadSpringFactories 方法,再进去看看:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
                // 从"META-INF/spring.factories"中获取资源并开始遍历获取到的资源
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }
classLoader.getResources("META-INF/spring.factories");

通过类加载器classLoade从"META-INF/spring.factories"中获取资源(getResources),并对获取到的内容开始进行遍历,那么我们就去找找这个spring.factories

  • spring.factories

在这里插入图片描述

在这个里面,找一个比较熟悉的WebMvcAutoConfiguration自动配置类,打开瞧瞧:

在这里插入图片描述

可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean。

所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。

如何识别需要加载的配置文件呢?哪些需要哪些不需要呢?

在这里插入图片描述

3.3 结论

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  3. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
  5. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

4. 主启动类的运行

SpringApplication.run分析

进到SpringApplication类的run方法中:

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

//进入到return的run方法中:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

先根据获取到的资源(primarySources)实例化SpringApplication,再调用run方法:

  • 实例化:

这个类主要做了以下四件事情:

1、推断应用的类型是普通的项目还是Web项目

2、查找并加载所有可用初始化器 , 设置到initializers属性中

3、找出所有的应用程序监听器,设置到listeners属性中

4、推断并设置main方法的定义类,找到运行的主类

SpringApplication的构造方法:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // ......
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}
  • 进入run方法查看:
public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
        }
        listeners.started(context, timeTakenToStartup);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var12) {
        this.handleRunFailure(context, var12, listeners);
        throw new IllegalStateException(var12);
    }
    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);
        return context;
    } catch (Throwable var11) {
        this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var11);
    }
}

ruan方法的执行流程(this picture references from狂神说):

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值