spring boot零配置启动原理-星辰平台

发表于 2022/04/26 08:55:26 2022/04/26
【摘要】 在创建传统springmvc项目时,需要复杂的配置文件,例如:web.xml,加载配置spring容器,配置拦截application.xml,配置扫描包,扫描业务类springmvc.xml,扫描controller,视图解析器等……而spring boot为我们提供了一种极简的项目搭建方式,看一下spring boot项目的启动类:@springbootapplicationpublic...

在创建传统springmvc项目时,需要复杂的配置文件,例如:

  • web.xml,加载配置spring容器,配置拦截
  • application.xml,配置扫描包,扫描业务类
  • springmvc.xml,扫描controller,视图解析器等
  • ……

而spring boot为我们提供了一种极简的项目搭建方式,看一下spring boot项目的启动类:

@springbootapplication
public class application {
    public static void main(string[] args) {
        springapplication.run(application.class,args);
    }
}

简单的一行代码,即可启动一个spring boot程序,那么在实际运行中是如何做到零配置启动的呢?下面从源码角度进行分析。

@springbootapplication

首先看一下@springbootapplication这个组合注解,除去元注解外,它还引入了其他三个重要的注解:

@springbootconfiguration
@enableautoconfiguration
@componentscan

@springbootconfiguration

@target(elementtype.type)
@retention(retentionpolicy.runtime)
@documented
@configuration
public @interface springbootconfiguration {
}

从源码可以看到,其实@springbootconfiguration并没有额外功能,它只是spring中@configuration的派生注解,用于标注配置类,完成bean的配置与管理。

@componentscan

spring中的注解,用于包的扫描,并把声明了特定注解的类交给spring的ioc容器。

@enableautoconfiguration

spring boot有中一个非常重要的理念就是约定大于配置。而自动配置这一机制的核心实现就是靠@enableautoconfiguration注解完成的。

可以看出,在@enableautoconfiguration注解中,使用@import导入了autoconfigurationimportselector这个类,实现了importselector接口的selectimports()方法。spring中会把selectimports()方法返回的string数组中的类的全限定名实例化为bean,并交给spring容器管理。

查看其中的getautoconfigurationentry方法:

在执行完getcandidateconfigurations后,把众多类的全限定名存储到了一个list中。

springfactoriesloader这个类非常重要,属于spring框架的一种扩展方案,提供一种了配置查找的功能支持。其主要功能就是读取配置文件meta-inf/spring.factories,决定要加载哪些类。

当然,并不是所有spring.factories中的类都会被加载到spring容器中,很多情况下需要按照需求所需的情况引入,依赖条件注解@conditional进行判断。例如servletwebserverfactoryautoconfiguration

只有在classpath下存在servletrequest这一类时,才将servletwebserverfactoryautoconfiguration作为配置类导入spring容器中。

springapplication

springapplication提供了一个简单的方式以启动spring boot程序,查看springapplication.run方法调用。在此创建了一个springapplication的实例,并调用了它的run方法:

看一下创建实例的过程源码:

主要完成了这几件事情:

  • 设置资源加载器,用于将资源加载到加载器中
  • 判断当前项目类型是什么? 提供了noneservletreactive 三种类型备选
  • 使用springfactoriesloader查找并加载所有可用的applicationcontextinitializer
  • 使用springfactoriesloader查找并加载所有可用的监听器applicationlistener
  • 推断并设置main方法的定义

springapplication完成初始化后,调用run方法,下面对run方法中核心代码进行分析:

按照图中标注序号进行分析:

1、spring监听器的使用,要获取这些监听器的对象,就要知道其全路径。通过springfactoriesloader查找spring.factories获得,之后再调用它们的started()方法

2、 创建并配置当前spring boot应用将要使用的environment,根据监听器和默认应用参数来准备所需要的环境

3、打印banner

4、创建spring应用上下文。根据之前推断的项目类型,决定该为当前springboot应用创建什么类型的applicationcontext并创建完成

5、准备应用上下文,首先将之前准备好的environment设置给创建好的applicationcontext使用。然后遍历调用所有applicationcontextinitializerinitialize方法来对已经创建好的applicationcontext进行进一步的处理。最后,遍历调用所有springapplicationrunlistenercontextprepared()方法

6、这里最终调用了spring中abstractapplicationcontextrefresh方法,可以说这个refresh方法是spring中最重要的方法之一,完成了bean工厂创建,后置管理器注册,bean实例化等最重要的工作。这一步工作完成后,spring的ioc容器就完成了

7、如果有bean实现了commandlinerunner接口并重写了run方法,则遍历执行commandlinerunner中的方法

手写 starter

starter是spring boot的核心思想之一,在使用spring boot来搭建项目时,往往只需要引入官方提供的starter,就可以直接使用,而不用再进行复杂的配置工作。

一方面,是前面说过的通过动态spi扩展可以直接从starter的meta-inf/spring.factories中决定什么类将被实例化为bean交给spring容器管理。另一方面,starter的父pom中往往已经包含了需要导入的依赖,以mybatis-spring-boot-starter这一starter为例,点开后可以看见它已经将依赖的坐标全部为我们导入了。

总的来说,使用starter可以完成以下功能:

  • 启用功能,注意不是实现功能
  • 依赖管理,starter帮我们引入需要的所有依赖

讲完了关于starter的原理,下面讲讲如何构造一个自己的starter。官方为我们提供了一个命名规范,建议第三方starter命名应当遵循thirdpart-spring-boot-starter这一格式,那我们就来手写一个my-spring-boot-starter,通过这个过程来学习如何完成属性的配置。

1、创建一个maven的普通project,在pom中添加parent节点

<parent>
    <groupid>org.springframework.bootgroupid>
    <artifactid>spring-boot-starter-parentartifactid>
    <version>2.2.6.releaseversion>
    <relativepath/>
parent>

2、引入自动装配的依赖

<dependencies>
    <dependency>
        <groupid>org.springframework.bootgroupid>
        <artifactid>spring-boot-autoconfigureartifactid>
    dependency>
dependencies>

3、实现自己的功能需求

public class sayhiimpl implements isayhi {
    @autowired
    myproperties properties;
    @override
    public void welcome() {
        string name=properties.getname();
        system.out.println(name" hello spring boot");
    }
}

如果希望能够在其他项目中使用的时候,通过yml或property文件对这个属性进行赋值,就要写一个对属性进行赋值操作的类,并使用@configurationproperties注解

@configurationproperties("spring.sayhi")
public class myproperties {
    private string name="";
    public string getname() {
        return name;
    }
    public void setname(string name) {
        this.name = name;
    }
}

4、如果希望上面开发的功能在springboot启动的时候就加入项目进行管理,就需要有一个代表当前starer自动装配的类

@configuration
@conditionalonclass
//使配置文件生效
@enableconfigurationproperties(myproperties.class)
public class myautoconfiguration {
    @bean
    //条件注解,仅当ioc容器中不存在指定类型的bean时,才会创建bean
    @conditionalonmissingbean
    public isayhi sayhi(){
        return new sayhiimpl();
    }
}

5、在resources创建meta-inf,创建spring.factories文件,在里面写入:

org.springframework.boot.autoconfigure.enableautoconfiguration=com.test.myautoconfiguration

6、使用maven打包

mvn clean install

测试工程

1、新建一个测试工程,在pom文件中引入上面打包的坐标

<dependency>
    <groupid>com.testgroupid>
    <artifactid>my-spring-boot-starterartifactid>
    <version>1.0-snapshotversion>
dependency>

2、使用yml进行属性的配置

spring:
  sayhi:
    name: hydra

3、运行测试

@springbootapplication
public class testapplication implements commandlinerunner {
    @autowired
    private isayhi sayhi;
    public static void main(string[] args) {
        springapplication application=new springapplication(testapplication.class);
        application.run(args);
    }
    @override
    public void run(string... args) throws exception {
        sayhi.welcome();
    }
}

结果:

hydra hello spring boot

如果在之前为name设置了默认值,那么在不在yml中对name进行配置的话就会打印默认值。这也就是为什么springboot在启动tomcat时会自动为我们设置为8080端口的原因,从这再一次体现了“约定大于配置”这一理念。

最后

觉得对您有所帮助,小伙伴们可以点个赞啊,非常感谢~
公众号『码农参上』,一个热爱分享的公众号,有趣、深入、直接,与你聊聊技术。欢迎来加我好友 drhydra9,围观朋友圈,做个点赞之交。

【星辰平台的版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请发送邮件至:;如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。