Spring Boot(一):快速入门及启动原理

一、Spring Boot 简介

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置(约定优于配置),从而使开发人员不再需要定义样板化的配置。

个人认为,springboot 并非全新的框架,而是对spring框架的再封装,通过 starter 的方式轻松集成第三方的框架,集成一个框架只需要引入一个 starter,去掉了 XML 的配置,全部用注解代替,然后在属性文件中配置一些值,整个集成的过程就结束了。总的来说,使用springboot框架可以非常方便、快捷的进行spring项目的开发,提高我们开发的效率。

二、springBoot特点

  • 创建独立的Spring应用程序
  • 嵌入的Tomcat,无需部署WAR文件
  • 简化Maven配置
  • 自动配置Spring
  • 提供生产就绪型功能,如指标,健康检查和外部配置
  • 绝对没有代码生成和对XML没有要求配置

三、使用 IDEA 的 Spring Initializr 创建 springboot 项目

使用 IDEA Spring Initializr 创建项目,可以非常方便的创建一个 spring 项目,真正做到 just run

1、开发环境

  • –jdk1.8:Spring Boot 推荐jdk1.7及以上;java version “1.8.0_152”

  • –maven3.x:maven 3.3以上版本;Apache Maven 3.6.1

  • –IntelliJ IDEA:IntelliJ IDEA 2020.1

    使用IDEA Spring Initializr创建项目其实就是集成了springboot官网提供的 Spring Initializr 模板来创建项目,是需要连接网络的,如果没有网络,建议使用maven方式创建项目。

2、点击文件 -> 新建 -> 项目

由于使用了IntelliJ IDEA官方提供的中文插件,所以显示的是中文,我发现换成中文后,自己反而好不习惯,后面已经改回英文了。。。
新建

3、选择 Spring Initializr ,选择JDK

需要注意的是springboot只支持 jdk 1.8及以上版本

在这里插入图片描述

4、填写项目信息

springboot框架内嵌了Tomcat和Jetty容器,直接打成jar包也可直接运行,spring-boot-starter-web默认使用Tomcat容器运行,如需切换为Jetty容器,只需在POM文件中添加Jetty的依赖即可。

在这里插入图片描述

5、选择需要的依赖包

在这里插入图片描述

6、选择存放项目的路径

需要注意的是,这里有可能会生成项目失败,原因是因为连接https://start.spring.io/网站超时,多尝试几次即可。

在这里插入图片描述

7、项目创建成功

看到这个界面,我们的第一个springboot项目就创建成功了(可以直接启动,默认地址为localhost:8080)。是不是很开心。。。

在这里插入图片描述

四、Hello World

上面我们创建了一个springboot的项目,已经可以成功运行了,只是项目里面什么都没有,接下来我们来编写一个hello world程序,并在浏览器中访问。

1、编写HelloController

package com.xiaoming.springboot.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    
    @RequestMapping("/hello")
    public String hello() {
        return "Hello World!";
    }
    
}

2、启动程序

编写好我们的controller类,我们就可以运行,并且从浏览器访问了,是不是so easy。简直不要太简单了。

那么如何启动程序呢,我们需要找到程序的主入口类,在本篇就是SpringBootHelloApplication.class,(需要注意的是,在一个项目中,主入口类有且只能有一个),运行main方法即可。

在这里插入图片描述

3、页面访问

在浏览器中请求 localhost:8080/hello 地址,页面就会给我们显示出 Hello World! ,到这里一个入门程序就已经完成了,各位程序猿,冲冲冲!!!

在这里插入图片描述

4、修改启动端口和根路径

在实际的开发中,程序的端口号是需要根据实际需求改动的,并且一般访问路径上需要带上项目的名称。此时我们需要修改springboot的默认端口和根路径的配置,springboot提供了一个全局的配置文件给我们修改springboot自动配置的默认值。配置文件的名字是固定:(本人更喜欢yml格式的配置文件)

​ •application.properties

​ •application.yml

关于配置文件就不在这里细说了,springboot配置相关的知识点我会再写一篇博客。

如何修改配置文件呢,我们只需要将我们的application.properties文件先改名为application.yml(不改也行,当然不该的格式和这里不同),然后在配置文件中加入如下代码即可:

server:
  port: 8888
  servlet:
    context-path: /xiaoming

修改完成重新启动一下程序,可以看到我们的端口号和根路径都变成了我们配置文件中的值。

在这里插入图片描述

五、揭开 springboot 的神秘面纱

在上面我们创建并运行了一个 springboot 项目,我们什么都没有配置,他凭什么就能够正常运行呢?相信各位小伙伴肯定是比较疑惑的。下面我们就来探究一下 springboot 为什么能够这么便捷的运行起来。

1、POM文件

既然我们的项目是一个maven项目,那么我们就先来看看程序的 POM 文件里面长什么样吧。

1)、parent 父工程

打开 POM 文件,我们可以在文件的头部位置看到一个 parent 项目

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.2.7.RELEASE</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>

这个项目有什么作用呢?

  1. 依赖的版本控制

    我们继续查看他的POM文件,点进去可以看到他又有一个 parent 工程

    <parent>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-dependencies</artifactId>
    	<version>2.2.7.RELEASE</version>
    	<relativePath>../../spring-boot-dependencies</relativePath>
    </parent>
    

    继续点进去,我们发现这个工程里面保存了基本的依赖和版本的定义,如下图
    在这里插入图片描述

    可以说这个项目是Spring Boot的版本仲裁中心,真正管理Spring Boot应用里面的所有依赖版本。所以我们导入依赖默认是不需要写版本(没有在这个项目里面管理的依赖自然还是需要声明版本号),妈妈再也不担心我们在开发大型项目时因为依赖冲突产生的问题了。

  2. 定义了 Java 编译版本为 1.8

    回到 spring-boot-starter-parent 工程,还可以看到如下内容:

    <properties>
       <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
       <!-- 这也是为什么我们的jdk环境为什么需要1.8及以上了 -->
       <java.version>1.8</java.version>
       <resource.delimiter>@</resource.delimiter>
       <maven.compiler.source>${java.version}</maven.compiler.source>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>
    
  3. 设置使用 UTF-8 格式编码。

  4. 执行打包操作的配置。

  5. 自动化的资源过滤。

  6. 自动化的插件配置。

  7. 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。

    <resources>
     <resource>
     <filtering>true</filtering>
     <directory>${basedir}/src/main/resources</directory>
     <includes>
       <include>**/application*.yml</include>
       <include>**/application*.yaml</include>
       <include>**/application*.properties</include>
     </includes>
     </resource>
     <resource>
     <directory>${basedir}/src/main/resources</directory>
     <excludes>
       <exclude>**/application*.yml</exclude>
       <exclude>**/application*.yaml</exclude>
       <exclude>**/application*.properties</exclude>
     </excludes>
     </resource>
    </resources>
    

2)、启动器

接着往下查看项目导入的依赖,可以看到以下内容

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

可以发现其中有两个依赖,一个是我们自己导入的 spring-boot-starter-web 依赖,另一个则是 Spring Initializr 创建项目时自动导入的测试依赖包(细心的小伙伴是不是已经发现了我们导入的依赖都没有标明版本号)。我们主要关注我们自己导入的 spring-boot-starter-web 依赖包。

我们发现两个依赖都有类似的名称 spring-boot-starter-xxx,其中我们称spring-boot-starter为springboot启动器,其实就是一系列依赖描述的组合信息,当我们需要用到那个模块,我们只需要导入那个模块的启动器即可,springboot会自动的帮我们导入该模块需要的一系列组件,并且有 parent 工程定义导入组件的版本,保证我们的程序能够正常运行

点进 spring-boot-starter-web 项目的 POM 文件,不出所料,spring-boot-starter-web 启动器帮我们导入了一系列的依赖包

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.2.7.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.2.7.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <!-- 这里可以看出为什么我们的程序默认使用Tomcat容器运行 -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.2.7.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.2.7.RELEASE</version>
    <scope>compile</scope>
    <exclusions>
      <exclusion>
        <artifactId>tomcat-embed-el</artifactId>
        <groupId>org.apache.tomcat.embed</groupId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.6.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.6.RELEASE</version>
    <scope>compile</scope>
  </dependency>
</dependencies>

2、主入口类

我们开发任何一个Spring Boot项目,项目中都会有且只有一个启动类,如下

package com.xiaoming.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootHelloApplication {

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

}

从上面代码可以看出,我们需要关注的点有两个,一个是@SpringBootApplication注解,另一个就是main方法中的SpringApplication.run()语句。

1)、@SpringBootApplication

@SpringBootApplication注解是Spring Boot的核心注解,它标注在某个类上就说明这个类是Spring Boot应用的主配置类,SpringBoot就应该运行这个类的main方法来启动Spring Boot应用;我们点进这个注解中可以发现@SpringBootApplication注解其实是多个注解的组合。

@Target(ElementType.TYPE) // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented // 表明这个注解应该被javadoc记录
@Inherited // 子类可以继承该注解
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan(excludeFilters = { // 包扫描路径
   @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), 
   @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) 
})
public @interface SpringBootApplication {
   ...
}

虽然定义使用了7个注解进行了原信息标注,但实际上重要的只有三个注解:

  • @SpringBootConfiguration(点开查看发现里面还是应用了@Configuration注解)
  • @EnableAutoConfiguration
  • @ComponentScan

我们可以这样认为:@SpringBootApplication ≈ @Configuration + @EnableAutoConfiguration + @ComponentScan

如果我们不嫌麻烦的话,我们的启动类也可以写成这样子

package com.xiaoming.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class SpringBootHelloApplication {

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

}

可以看到,运行效果和@SpringBootApplication是一样
在这里插入图片描述

2)、@SpringBootConfiguration

@SpringBootConfiguration注解标注在某个类上,表示这是一个Spring Boot的配置类,底层使用的是@Configuration注解,需要注意的是,配置类本身也是IOC容器中的一个组件。

3)、@ComponentScan

@ComponentScan这个注解在Spring中很重要,它的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IOC容器中。

注意:如果你的其他包都在使用了@SpringBootApplication注解的main app所在的包及其下级包,则你什么都不用做,SpringBoot会自动帮你把其他包都扫描了。

如果你有一些bean所在的包,不在main app的包及其下级包,那么你需要手动加上@ComponentScan注解并指定那个bean所在的包。

所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

4)、@EnableAutoConfiguration

@EnableAutoConfiguration 注解告诉Spring Boot开启自动配置功能,这样自动配置才能生效。他的底层是使用@Import注解,将所有符合自动配置条件的bean定义加载到IOC容器。

@EnableAutoConfiguration作为一个复合Annotation,其自身定义关键信息如下:

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

其中主要的注解就是,@AutoConfigurationPackage和 @Import(EnableAutoConfigurationImportSelector.class)

  • @AutoConfigurationPackage 的底层其实也是调用了 @Import(AutoConfigurationPackages.Registrar.class) 注解

    @Import(AutoConfigurationPackages.Registrar.class)的其实就是由AutoConfigurationPackages.Registrar类将主配置类所在包及下面所有子包里面的所有组件扫描并注册到IOC容器中
    在这里插入图片描述

  • @Import(EnableAutoConfigurationImportSelector.class)中的EnableAutoConfigurationImportSelector就是自动配置导入选择器,它来决定那些组件能够被导入进去,它会将所有需要导入的组件以全类名的方式返回,返回的组件就会别添加到IOC容器中。

    // springboot 获取自动配置类的源码
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
    		AnnotationMetadata annotationMetadata) {
    	if (!isEnabled(annotationMetadata)) {
    		return EMPTY_ENTRY;
    	}
    	AnnotationAttributes attributes = getAttributes(annotationMetadata);
    	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    	configurations = removeDuplicates(configurations);
    	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    	checkExcludedClasses(configurations, exclusions);
    	configurations.removeAll(exclusions);
    	configurations = filter(configurations, autoConfigurationMetadata);
    	fireAutoConfigurationImportEvents(configurations, exclusions);
    	return new AutoConfigurationEntry(configurations, exclusions);
    }
    

    进入源码debug查看返回了那些类,如下图
    在这里插入图片描述

    我们发现@Import(EnableAutoConfigurationImportSelector.class)会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件,有了这些自动配置类,就免去了我们手动编写配置注入功能组件的工作;

    在 getAutoConfigurationEntr() 方法中调用了一个非常关键的方法,就是 getCandidateConfigurations(),源代码如下

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
    			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;
    }
    

    该方法中的SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()); 就是我们能够获取到这么多自动配置类的关键。

    SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。

    配合@EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类。我们去到源代码查看,如下图
    在这里插入图片描述
    所以,@EnableAutoConfiguration自动配置就是:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IOC容器配置类,然后汇总为一个并加载到IOC容器中。(详细的自动配置原理,我会放到springboot配置相关的博客中讲解的,边学习springboot边写博客,让自己理解的更加透彻一点,嘻嘻嘻)

    在上面编写的Hello World程序中我们添加了spring-boot-starter-web启动器,程序会自动添加Tomcat和Spring MVC的依赖,然后Spring Boot就会对Tomcat和Spring MVC进行自动配置。

看到这里各位小伙伴心中的疑惑是不是已经解开了呢?明明我们什么都没有配置,程序却能正常运行,其实和我们用spring开发时的配置相比一样没少,只不过是springboot将以前需要我们程序猿配置的东西,自动配置好了,真正做到了 “just run”。这就是 springboot 这么火爆的理由之一吧。

感谢你看到这里

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章