Spring Boot——Hello World运行原理

📢📢📢📣📣📣

哈喽!大家好,我是【一心同学】,一位上进心十足的【Java领域博主】!😜😜😜

✨【一心同学】的写作风格:喜欢用【通俗易懂】的文笔去讲解每一个知识点,而不喜欢用【高大上】的官方陈述。

✨【一心同学】博客的领域是【面向后端技术】的学习,未来会持续更新更多的【后端技术】以及【学习心得】。

✨如果有对【后端技术】感兴趣的【小可爱】,欢迎关注一心同学】💞💞💞

❤️❤️❤️感谢各位大可爱小可爱!❤️❤️❤️ 


目录

一、pom.xml

1.1父依赖

1.2启动器

二、主程序类@SpringBootApplication分析

2.1介绍

2.2@SpringBootConfiguration

2.2.1@Configuration

2.3@EnableAutoConfiguration

2.3.1@AutoConfigurationPackage

2.3.2@Import({AutoConfigurationImportSelector.class})

spring.factories

2.4@ComponentScan

三、SpringApplication

3.1 SpringApplication职责

3.2 run方法执行流程分析

结语


一、pom.xml

1.1父依赖

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.0</version>
    </parent>

我们点击进去探以下究竟。

发现里面还有依赖一个父项目

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

 我们继续把代码一层一层剥掉,继续点击进去往里面探究

果真!发现了一个宝藏!

 
    <properties>
        <activemq.version>5.16.3</activemq.version>
        <antlr2.version>2.7.7</antlr2.version>
        <appengine-sdk.version>1.9.92</appengine-sdk.version>
        <artemis.version>2.19.0</artemis.version>
        <aspectj.version>1.9.7</aspectj.version>
        <assertj.version>3.21.0</assertj.version>
        <atomikos.version>4.0.6</atomikos.version>
        <awaitility.version>4.1.1</awaitility.version>
        <build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
        .......
    </properties>

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

问题:为什么用SpringBoot有的依赖需要写版本号,有的不需要写?

答案:
因为我们项目的pom里引入了parent节点,所以将最终的spring-boot-dependencies-2.6.0.pom也继承过来,而spring-boot-dependencies-2.6.0.pom里面管理了大部分的jar包的版本,所以我们依赖jar包的时候无需写版本号;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

我们回过头去看我们注入的另一个依赖

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>

是否发现在这个依赖中就不需要我们写版本号,因为在父节点中已经管理了这个jar包的版本,我们去spring-boot-dependencies-2.6.0.pom查看是否有管理这个jar包

果然,这个jar包有被SpringBoot所管理,所以不需要我们写配置文件。

问题:为什么启动main函数就能访问url?

答案:因为SpringBoot内嵌了一个tomcat。

1.2启动器

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>

分析:

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

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

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 

二、主程序类@SpringBootApplication分析

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

2.1介绍

@SpringBootApplication: 标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;

我们来分析@SpringBootApplication这个注解到底做了什么?

点击进入@SpringBootApplication查看:

@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 {

//......
}

看到这么多注解,不要慌,其实这里面只有三个关键的注解:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

我们来对这三个注解一一进行讲解,看下这三个注解到底做了什么?

2.2@SpringBootConfiguration

介绍:SpringBoot配置类。标注在某个类上,表示这是一个SpringBoot的配置类
我们进入这个注解进行查看: 

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

2.2.1@Configuration

@Configuration:说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件,也是一个组件(因为内部标注了@Component注解);

为了验证其也是一个组件,我们继续进入@Configuration的源码进行查看,看下是否有@Component注解。

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

果然看到了@Component ,这就说明该配置类也是容器中的一个组件,负责启动应用!

2.3@EnableAutoConfiguration

介绍开启自动配置功能,那么以前我们需要配置的东西,Spring Boot帮我们自动配置,再也不需要写一堆繁琐的配置文件。

我们点击进去查看代码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

发现里面有两个重要的注解:

  • @AutoConfigurationPackage
  • @Import({AutoConfigurationImportSelector.class})

2.3.1@AutoConfigurationPackage

源码分析:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
}

分析

@Import({Registrar.class}):给Spring容器中导入了Registrar组件

作用:将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器。

2.3.2@Import({AutoConfigurationImportSelector.class})

作用:快速给容器中导入一些组件。

AutoConfigurationImportSelector:

介绍导入哪些组件的选择器。

作用将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

使用场景会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件;

我们去查看一下它会导入哪些组件的选择器,进入AutoConfigurationImportSelector源码进行分析一下。

(1)该类有这样一个方法:

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

(2)这个方法又调用了  SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类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();
//这里又调用了loadSpringFactories方法
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
        }

(3)我们继续点击查看 loadSpringFactories 方法

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {

//获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
        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");

//将读取到的资源遍历,封装成为一个Properties
        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);
        }
        }
        }

(4)发现一个多次出现的文件:spring.factories,全局搜索它

spring.factories

我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!

有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;

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

2.4@ComponentScan

Spring的注解,相当于配置文件的

<context:component-scan base-package="xxx">
 <context:exclude-filter type="annotation" expression="xxxx"/>
</context:component-scan>

以上就是对@SpringBootApplication注解的运行原理进行讲解,我们用一张图来概括以上的知识点,将其串接起来,方便我们理解。


三、SpringApplication

我们再来看一下这个启动类:

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

分析该方法主要分两部分:

(1)SpringApplication的实例化

(2)run方法的执行;

3.1 SpringApplication职责

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

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

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

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

3.2 run方法执行流程分析



结语

以上就是【一心同学】对SpringBoot的核心组件进行的基本解析,综合来看,大部分都是Spring框架背后的一些概念和实践方式,SpringBoot只是在这些概念和实践上对特定的场景事先进行了固化和升华,而也恰恰是这些固化让我们开发基于Sping框架的应用更加方便高效。

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一心同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值