GVKun编程网logo

SpringBoot 2.X 课程学习 | 第一篇:初识 SpringBoot(springboot2.0教程)

16

针对SpringBoot2.X课程学习|第一篇:初识SpringBoot和springboot2.0教程这两个问题,本篇文章进行了详细的解答,同时本文还将给你拓展SpringBoot2.X课程学习|第

针对SpringBoot 2.X 课程学习 | 第一篇:初识 SpringBootspringboot2.0教程这两个问题,本篇文章进行了详细的解答,同时本文还将给你拓展SpringBoot 2.X 课程学习 | 第六篇:挖掘配置文件的秘密、SpringBoot 2.X课程学习 | 第四篇:初识springboot配置文件、springboot 学习笔记:2. 搭建你的第一个 springboot 应用、SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?等相关知识,希望可以帮助到你。

本文目录一览:

SpringBoot 2.X 课程学习 | 第一篇:初识 SpringBoot(springboot2.0教程)

SpringBoot 2.X 课程学习 | 第一篇:初识 SpringBoot(springboot2.0教程)

一、 SpringBoot 是什么?

          我们可以查看一下官网介绍。

          Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can run. We take an opinionated view of the Spring platform and third-party libraries, so that you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration.

          这句话大体翻译就是以 spring 最少的配置就能轻松地构建独立的、生产级的、基于 Spring 的应用程序。

          官网也说明了其目标是:

           1、为所有的 Spring 开发提供一个更快、更广泛的入门体验。

           2、提供一系列大型项目通用的非功能特性(如嵌入式服务器、安全性、度量、运行状况检查和外部化配置)。

           3、绝对没有代码生成,也不需要 XML 配置。

           总而言之,SpringBoot 是 spring 开源组织下的子项目,是 spring 组件一站式的解决方案,主要是简化了使用 spring 框架的难度,简省了繁琐的配置,并提供一些强大的功能(各种启动器,自动配置,应用监控等),使开发者能快速上手。

二、springboot 优缺点有哪些?

 1)、优点:

  1. 快速构建独立运行的 spring 项目以及与主流框架集成;
  2. 使用嵌入式 Servlet 容器,项目无需打成 war 包;
  3. starters 自动依赖与版本控制;
  4. 大量的自动配置,简化开发,也可修改默认值;
  5. 无需配置 xml,无代码生成,开箱即用;
  6. 应用监控等。

 2)、缺点

    入门容易,但是精通难,因为 springboot 是基于 spring 框架的再封装,如果不了解 spring 框架底层实现,那么对 springboot 的封装机制就不是很了解,如果要了解 springboot 原理的话,就需要先了解 spring 框架的底层原理。

三、 springboot 版本都有哪些?

   大伙们可自行通过 spring 官网查看一下当前 springboot 版本

   

   GA:General Availability, 正式发布的版本,官方推荐使用此版本。在国外都是用 GA 来说明 release 版本的。

   PRE: 预览版,内部测试版. 主要是给开发人员和测试人员测试和找 BUG 用的,不建议使用;

   SNAPSHOT: 快照版,可以稳定使用,且仍在继续改进版本。

  四、搭建 springboot 项目环境需求都有哪些?

  官网给出了搭建 springboot 项目需求说明:

  •  Spring Boot 2.1.5.RELEASE requires Java 8 and is compatible up to Java 11 (included) (最低版本是 jdk1.8,向上兼容 jdk1.11). 
  •  Explicit build support is provided for the following build tools(生成工具提供支持):  

    

五、springboot 内置容器有哪些?

    springboot 支持以下嵌入式 servlet 容器:

    

SpringBoot 2.X 课程学习 | 第六篇:挖掘配置文件的秘密

SpringBoot 2.X 课程学习 | 第六篇:挖掘配置文件的秘密

 

一、两种配置文件获取值的方式

        因为普遍属性比较简单,可能复杂属性有些小伙伴们不知道怎么获取。因此我将 application.yml 和 application.properties 两种配置文件中对象复杂属性定义的内容列举出来:

        application.properties 文件:

#自定义list集合
person.lists=a,b,c
#自定义map集合
person.maps.k1=va2
person.maps.k2=v2
#自定义person中的dog对象属性
person.dog.name=毒瓦斯
person.dog.age=8

       application.yml 文件:

#自定义map集合
#缩进方式
person:
  maps:
    k1: v2
    k2: v3

#行内方式
person: 
  maps:{k1: v2,k2: v3}


#自定义数组
#缩进方式
person
  lists:
    - 镰刀湾
    - 瓦斯的

#行内方式
person:
  lists:[镰刀湾,瓦斯的]

#自定义对象属性
person:
  dog:
    name: awdwa
    age: 11

     1、方式一  通过 @ConfigurationProperties 注解     

            @ConfigurationProperties 注解作用是告诉 springboot 将本类中的所有属性与配置文件中的属性进行绑定,它需要提供 prefix 属性,可通过统一前缀批量将类中属性和配置文件中属性进行绑定,使用该方式的时候需要引入配置文件处理器依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

           并且使用添加 @Component、@Configuration 或者 @EnableConfigurationProperties 注解,将类作为容器中的组件,才能使用容器中的功能。 

       示例代码如下:

package com.example.demo.entity;

import org.springframework.stereotype.Component;

@Component
@Data
public class Dog {

    private String name;

    private int age;

}

          1.1、使用 @Component 注解

package com.example.demo.entity;


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@Component
@ConfigurationProperties(prefix = "person")
@Data
public class Person {

    private Map<String,Object> maps;

    private List<Object> lists;

    private Dog dog;
}

      1.2、使用 @configuration 注解:

package com.example.demo.entity;


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@configuration
@ConfigurationProperties(prefix = "person")
@Data
public class Person {

    private Map<String,Object> maps;

    private List<Object> lists;

    private Dog dog;
}

      1.3、使用 @EnableConfigurationProperties 注解

package com.example.demo.entity;


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@ConfigurationProperties(prefix = "person")
@Data
public class Person {

    private Map<String,Object> maps;

    private List<Object> lists;

    private Dog dog;
}
@SpringBootApplication
@EnableConfigurationProperties(Person.class)
public class DemoApplication {

    @Autowired
    private Person person;

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    @ResponseBody
    public String hello() {
        System.out.println("person" + person);
        return "hello world";
    }

    public static void main(String[] args) {
        //SpringApplication.run(TestProperty1.class, args);
        new SpringApplicationBuilder(DemoApplication.class).web(SERVLET).run(args);

    }
}

2、方式二 通过 @Value 注解

          @Value 注解类似在原始 spring 项目在配置文件中配置 bean 标签,在 bean 标签中配置了 properties 标签,并提供 value 属性值,例如:

<bean class="xxx">
  <properties name="xxx" value=""/>
</bean>

@Value注解相当于这个properties标签中的value属性值。

         使用 @Value 注解不能通过前缀批量将类中所有属性与配置文件中特定属性绑定,它支持使用 ${key}(从环境变量、配置文件中获取值)、#{SpEl} 等。

         比如:

    @Value("${person.last-name}")
    private String name;

    @Value("#{11*2}")  /**为spring表达式
    private int age;

    @Value("true")
    private boolean flag;

      将配置文件中的属性绑定到类中的属性时,对于对象属性,如果键包含除小写字母数字字符或 - 以外的任何字符,则需要使用括号表示法,以便保留原始值。如果键未被 [] 包围,则将删除任何非字母数字或 - 的字符。比如 map 属性:

#yml文件写法
#自定义map集合
person:
  maps:
    "[/key1]": value1
    /key3: value3
    k2: v2


#properties文件写法
#自定义map集合
person.maps.[/key1]=va2
person.maps./key3=v2

那么使用注解获取到的map集合内容为maps={/key1=va2, key3=v2}

二、@ConfigurationProperties 和 @Value 注解不同

  @ConfigurationProperties  @Value
功能 批量注入配置文件中的属性 只能一个一个的注入
松散绑定(Relaxed Binding) 支持 不支持
SrEl 表达式 不支持 支持
JSR303 数据检验 支持 不支持
复杂类型封装 支持 不支持
绑定类属性是否提供 set、get 方法

2.1、松散绑定

         比如类中定义了一个属性,属性名为 firstName,如果使用 @ConfigurationProperties,那么配置文件中,该属性可以写成如下形式:

person.firstName:使用标准方式
person.first-name:大写用-,建议在.properties.yml文件中使用。
person.first_name:大写用_,它是用于.properties.yml文件的可选格式。
PERSON.FIRST_NAME  大写格式,建议在使用系统环境变量时使用。

        但是如果使用 @Value 注解,那么只能写成 @Value ("${person.firstName}")。

        每个属性源的宽松绑定规则:

属性源 Simple List
Properties Files Camel case(驼峰式), kebab case(短横线), or underscore notation(下划线符号) 使用 [] 或逗号分隔值的标准列表语法
YAML Files Camel case(驼峰式), kebab case(短横线), or underscore notation(下划线符号) 标准 yaml 列表语法或逗号分隔值

Environment Variables

(环境变量)

以下划线作为分隔符的大写格式。“-” 不应在属性名中使用 由下划线包围的数字值,例如 my_Acme_1_Other=my.Acme [1].Other

System properties

(系统属性)

Camel case(驼峰式), kebab case(短横线), or underscore notation(下划线符号) 使用 [] 或逗号分隔值的标准列表语法

2.2、JSR303 数据校验

        @ConfigurationProperties 注解支持 JSR303 数据校验,意味着进行数据校验的属性必须符合规则才给予通过,否则报错。比如给类中的某个属性添加 @Email 注解,则规定,该属性值必须符合邮箱格式才进行通过。查看代码:

@Validated
public class Person {

    @Value("${person.last-name}")
    @Email
    private String name;

}

2.3、关于绑定类中的属性是否需要提供 set、get 方法

   这个情况只适应于使用 @ConfigurationProperties 注解。由于绑定是通过标准 javaBean 属性,因此绝大情况下属性都需要提供 setter 和 getter 方法,除以下情况可以省略 setter 方法:

  • 集合或者数组 可以通过索引(通常使用 yaml)或使用单个逗号分隔值(属性)访问。对于数组属性,setter 方法是强制的。我们建议始终为此类类型添加 setter 方法。如果初始化集合,请确保它不是不可变的。
  • 初始化了嵌套的 POJO 属性,例如:person 类中有一个属性:private Dog dog=new Dog (); 则不需要 setter 方法,但是是 private Dog dog; 则需要提供 setter 方法。如果希望绑定器使用其默认构造函数动态创建实例,则需要一个 setter 方法。
  • 最后,只考虑标准的 JavaBean 属性,不支持对静态属性的绑定。

三、配置文件占位符

      springboot 配置文件中允许使用随机数或者在配置文件中引用前面配置过的属性来作为占位符。

      比如使用随机数的占位符:

${random.int}
${random.long}
${random.value}
${random.uuid}
${random.int(10)}
${random.int[1024,65523]}

     比如在文件中引用前面配置过的属性作为占位符:

app.name=MyApp
app.decription=${app.name} is a SpringBoot Application

   如果配置文件上下文没有该属性时,我们随便输入一个属性名,那么它将会把这个属性名打印出来,比如:

app.name=Myapp
app.decription=${MyProject} is a SpringBoot Application


那么使用注解获取app.decription这个属性值时,打印的结果为:MyProject is a SpringBoot Application

     我们还可以为这个配置文件上下文都没有的属性值赋值。比如如下操作:

app.name=Myapp
app.decription=${MyProject:customProject} is a SpringBoot Application


那么使用注解获取app.decription这个属性值时,打印的结果为:customProject is a SpringBoot Application

四、配置文件加载顺序

 

SpringBoot 2.X课程学习 | 第四篇:初识springboot配置文件

SpringBoot 2.X课程学习 | 第四篇:初识springboot配置文件

一、前言

      由于springboot自动配置的特性,它会依照我们搭建项目时给予的组件之间依赖性为我们自动配置好了组件能正常启动相关的配置,我们直接启动即可,因为设置的是默认值,当一些业务情况需要更改配置默认值时,springboot也提供了解决方案,那就是提供配置文件让我们修改默认值,springboot会将配置文件中配置的属性值设置为组件相应的配置值。springboot文档也做了说明:

     Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use properties files, YAML files, environment variables, and command-line arguments to externalize configuration. 

      这句话翻译为:springboot允许将配置外部化,可以使用属性文件、yaml文件、环境变量和命令行参数等方式来外部化配置修改默认值。

二、springboot提供配置文件的种类

     springboot提供两种文件格式的全局配置文件,并且文件名是固定的,均以“application”为文件名,这是springboot设计理念:约定优于配置,而两种文件格式分别是:

     1、application.properties

        这种文件格式的配置文件,在使用快速向导创建springboot应用的时候,默认就在resources目录下创建了,因此它是默认的全局配置文件,这种配置文件语法是“key=value”的形式;比如:

debug=true

spring.profiles.active=dev
spring.application.name: xxx
server.port=8080

spring.mvc.view.prefix=/templates/
spring.mvc.view.suffix=.ftl

logging.file=qingyun-api
logging.path=./logs/${spring.application.name}
logging.config=classpath:logback.xml
logging.level.xxx=DEBUG

    在配置文件中也可以自定义属性。

     2、application.yml

        这种配置文件是以YAML(YAML Ain’t Markup Language)语言的文件,文件语法使用空白,缩进,分行组织数据,yml文件采取树状结构,更加简洁易读,以数据为中心,比json、xml等更适合做配置文件。比如:

//具体写法如下
person:
  lastName: 张三
  age: 24
  boss: false
  birth: 1994/5/1

#  maps: {k1: v1,k2: v2}

  maps:
    k1: v1
    k2: v2

#  lists: [l1,l2,l3]

  lists:
    - l1
    - l2
    - l3
  dog:
    name: 蛋黄
    age: 2

三、两者配置文件的区别

     1.内容格式比较:

        properties文件语法是“key=value”的形式,对于复杂属性数据,是以“.”号连接层级关系的。结构上没有分层效果;而yml文件采用树状结构,结构上有明显的分层效果,语法是以"key:(空格)value"的形式,以空间的缩进来控制层级关系,只要是左对齐的一列数据,都是属性同一层级的。

     2.执行顺序:

       工程中同时存在application.properties文件和 application.yml文件,yml文件会先加载,而后加载的properties文件会覆盖yml文件。所以建议工程中,只使用其中一种类型的文件即可。

springboot 学习笔记:2. 搭建你的第一个 springboot 应用

springboot 学习笔记:2. 搭建你的第一个 springboot 应用

 springboot

1. 开发环境

(推荐):jdk1.8+Maven(3.2+)+Intellij IDEA+windows10;

说明:

jdk:springboot 官方说的很明确,到目前版本的 springboot (1.5.9),官方指定要求 jdk1.8 以上;

依赖包管理:可以通过拷贝 jar 文件的方式管理依赖,但官方也推荐使用 Apache Maven 3.2 或更高版本等构件工具;

开发工具:个人推荐使用 IDEA,功能很强大,使用流畅度和方便性较好;


2. 开始我们的第一个 springboot 项目

本节目标:构造第一个 springboot 入门 web 项目,通过三种方式运行启动;并通过浏览器得到服务器反馈结果;

步骤:

2.1. 验证本机的 jdk 版本和 mvn 版本

确保在 jdk1.8 和 maven3.2+;

2.2. 创建项目 (使用 springboot 官方推荐的创建方式 ——spring initializr):

进入 https://start.spring.io/,选择要使用的 springboot 版本号,这里使用 1.5.9,填写好项目组信息,在 Search for dependencies 中搜索 web 并选中;

选择生成项目后自动下载 chapter01.zip;解压 chapter01.zip 后,使用 IDEA 打开 chapter01 项目文件夹;

此外,还可以在 IDEA 中 File-new-Project,选择 Spring Initializr,在 IDEA 中创建 springboot 项目;

(注意:此处如果你的项目报错,请确保该项目的 maven 配置正确;IDEA 的话打开,正确指定好使用的 maven 是 3.2 以上版本即可:

)

项目正常打开后的目录结构如下:

Chapter01Application.java  内部包含 main 函数,是 springboot 项目的启动类;

Chapter01ApplicationTests.java  测试类

pom.xml  依赖管理文件

application.properties  配置文件,初次生成的时候是空的,以后可以在里面填写配置项;

 

有的同学到这里有些懵,以往 java web 项目不是有个 WebRoot 文件夹吗,这里我明明配置的就是 web 项目,为什么会是这么个目录结构呢?

这里其实就是 springboot 提倡的习惯,以后开发 springboot,要习惯使用模板,摒弃 jsp 的前端方案 (如果你执意要使用 jsp 的话也是可以的,我们后文会介绍);现在我们项目组开发,基本全是 freemarker,而且基本上全部都是请求 rest 接口,这样前后端完全分离;避免了在 jsp 中写 java 代码的陋习 (记得以前做过一个 H5 支付页面,用 jsp 写的,后来项目升级,前端用 vue.js,结果后台需要重新写 n 多接口);以后项目想更换前端方案也不会是坑;所以要接受并学习使用模板;要彻底执行前后端分离的思想;

以前使用 jsp 时,你会把 jsp 放在 WEB-INF 下面;以后使用模板,你需要把模板全放到例如 resources-templates 下面去;

sprinboot 到底使用什么模板,因人而异;我个人喜欢 freemarker;

以 freemarker 为例,模板.ftl 文件会被放到 resources.templates 中,其他静态资源文件会被放到 resources-static 中,这块日后再婊;

2.3 项目详解

2.3.1 详解 pom.xml

我们把目光先集中在 maven 的 pom.xml 中,看看官方生成的这个依赖结构:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zjt</groupId>
    <artifactId>chapter01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>chapter01</name>
    <description>Demo project for Spring Boot</description>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <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>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

 

 是不是看上去很简单,其实里面大有玄机;

首先,注意打包文件格式是 jar 文件,这是因为,sprinboot 使用 mvn 插件可以打包成可执行的内嵌 web 容器的 jar,这对于发布微服务是很方便的;如果想打成 war 包和其他项目一起集成在 tomcat 中,也是可以的,这个日后再表;

构建项目:

 方法 1:父依赖 (spring initializr 默认构建方式):

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

此处,我们发现 springboot 官方默认使用父依赖的方式来构件 springboot 项目。ctrl + 左键点击跟踪 spring-boot-starter-parent,会发现其实她继承了 spring-boot-dependencies;包括日后我们常用的配置文件的过滤等

总结起来,可以说 spring-boot-starter-parent,有如下特性:

默认编译级别为Java 1.8
源码编码为UTF-8
一个依赖管理节点,允许你省略普通依赖的 <version>  标签,继承自 spring-boot-dependencies  POM。
合适的资源过滤
合适的插件配置(exec插件,surefireGit commit IDshade)
针对 application.propertiesapplication.yml  的资源过滤

 只要指定了父依赖的 version,那么其相关的其他自动依赖均无需再指定版本号,springboot 已经自动管理好了最佳的依赖配置,如图:

这也是 springboot 的方便之处;

除非你手动覆盖自己的项目中的属性,来达到修改某个依赖的版本号的目的;

方法 2:使用 import 来引入 spring-boot-dependencies

如果不想使用父依赖的方式,可以直接通过使用一个 scope=import  的依赖来构建:

<dependencyManagement>
        <dependencies>
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.0.0.BUILD-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Starter POMS :

下面我们看一下 pom.xml 中的依赖模块,发现有一些很有特色的 spring-boot-starter-*,官方学名叫 starter poms;

springboot 使用各种 starter poms 来实现组件的热插拔;每一个 starter pom 就是一个可以包含到应用中的一个方便的依赖关系描述符集合;可以获取所有 Spring 及相关技术的一站式服务,而不需要翻阅示例代码,拷贝粘贴大量的依赖描述符。

例如,如果你想使用 Spring 和 JPA 进行数据库访问,只需要在你的项目中包含 spring-boot-starter-data-jpa 依赖,然后你就可以开始了。

下面的应用程序 starters 是 Spring Boot 在 org.springframework.boot  组下提供的,我们可以方便地查找你的项目需要的其他组件的 starter,直接添加即可自动加载依赖:

Table 13.1. Spring Boot application starters

Name Description Pom

spring-boot-starter

Core starter, including auto-configuration support, logging and YAML

核心 Spring Boot starter,包括自动配置支持,日志和 YAML

Pom

spring-boot-starter-activemq

Starter for JMS messaging using Apache ActiveMQ

Pom

spring-boot-starter-amqp

Starter for using Spring AMQP and Rabbit MQ

对 "高级消息队列协议" 的支持,通过 spring-rabbit  实现

Pom

spring-boot-starter-aop

Starter for aspect-oriented programming with Spring AOP and AspectJ

对面向切面编程的支持,包括 spring-aop  和 AspectJ

Pom

spring-boot-starter-artemis

Starter for JMS messaging using Apache Artemis

Pom

spring-boot-starter-batch

Starter for using Spring Batch

对 Spring Batch 的支持,包括 HSQLDB 数据库

Pom

spring-boot-starter-cache

Starter for using Spring Framework’s caching support

Pom

spring-boot-starter-cloud-connectors

Starter for using Spring Cloud Connectors which simplifies connecting to services in cloud platforms like Cloud Foundry and Heroku

对 Spring Cloud Connectors 的支持,简化在云平台下(例如,Cloud Foundry 和 Heroku)服务的连接

Pom

spring-boot-starter-data-cassandra

Starter for using Cassandra distributed database and Spring Data Cassandra

Pom

spring-boot-starter-data-cassandra-reactive

Starter for using Cassandra distributed database and Spring Data Cassandra Reactive

Pom

spring-boot-starter-data-couchbase

Starter for using Couchbase document-oriented database and Spring Data Couchbase

Pom

spring-boot-starter-data-couchbase-reactive

Starter for using Couchbase document-oriented database and Spring Data Couchbase Reactive

Pom

spring-boot-starter-data-elasticsearch

Starter for using Elasticsearch search and analytics engine and Spring Data Elasticsearch

对 Elasticsearch 搜索和分析引擎的支持,包括 spring-data-elasticsearch

Pom

spring-boot-starter-data-jpa

Starter for using Spring Data JPA with Hibernate

对 "Java 持久化 API" 的支持,包括 spring-data-jpa  , spring-orm  和 Hibernate

Pom

spring-boot-starter-data-ldap

Starter for using Spring Data LDAP

Pom

spring-boot-starter-data-mongodb

Starter for using MongoDB document-oriented database and Spring Data MongoDB

对 MongoDB NOSQL 数据库的支持,包括 spring-data-mongodb

Pom

spring-boot-starter-data-mongodb-reactive

Starter for using MongoDB document-oriented database and Spring Data MongoDB Reactive

Pom

spring-boot-starter-data-neo4j

Starter for using Neo4j graph database and Spring Data Neo4j

Pom

spring-boot-starter-data-redis

Starter for using Redis key-value data store with Spring Data Redis and the Lettuce client

Pom

spring-boot-starter-data-redis-reactive

Starter for using Redis key-value data store with Spring Data Redis reactive and the Lettuce client

Pom

spring-boot-starter-data-rest

Starter for exposing Spring Data repositories over REST using Spring Data REST

对通过 REST 暴露 Spring Data 仓库的支持,通过 spring-data-rest-webmvc  实现

Pom

spring-boot-starter-data-solr

Starter for using the Apache Solr search platform with Spring Data Solr

对 Apache Solr 搜索平台的支持,包括 spring-data-solr

Pom

spring-boot-starter-freemarker

Starter for building MVC web applications using FreeMarker views

对 FreeMarker 模板引擎的支持

Pom

spring-boot-starter-groovy-templates

Starter for building MVC web applications using Groovy Templates views

对 Groovy 模板引擎的支持

Pom

spring-boot-starter-hateoas

Starter for building hypermedia-based RESTful web application with Spring MVC and Spring HATEOAS

对基于 HATEOAS 的 RESTful 服务的支持,通过 spring-hateoas  实现

Pom

spring-boot-starter-integration

Starter for using Spring Integration

对普通 spring-integration  模块的支持

Pom

spring-boot-starter-jdbc

Starter for using JDBC with the Tomcat JDBC connection pool

对 JDBC 数据库的支持

Pom

spring-boot-starter-jersey

Starter for building RESTful web applications using JAX-RS and Jersey. An alternative to spring-boot-starter-web

对 Jersey RESTful Web 服务框架的支持

Pom

spring-boot-starter-jooq

Starter for using jOOQ to access SQL databases. An alternative to spring-boot-starter-data-jpa or spring-boot-starter-jdbc

Pom

spring-boot-starter-json

Starter for reading and writing json

Pom

spring-boot-starter-jta-atomikos

Starter for JTA transactions using Atomikos

对 JTA 分布式事务的支持,通过 Atomikos 实现

Pom

spring-boot-starter-jta-bitronix

Starter for JTA transactions using Bitronix

对 JTA 分布式事务的支持,通过 Bitronix 实现

Pom

spring-boot-starter-jta-narayana

Spring Boot Narayana JTA Starter

Pom

spring-boot-starter-mail

Starter for using Java Mail and Spring Framework’s email sending support

对 javax.mail  的支持

Pom

spring-boot-starter-mustache

Starter for building web applications using Mustache views

对 Mustache 模板引擎的支持

Pom

spring-boot-starter-quartz

Spring Boot Quartz Starter

Pom

spring-boot-starter-security

Starter for using Spring Security

对 spring-security  的支持

Pom

spring-boot-starter-test

Starter for testing Spring Boot applications with libraries including JUnit, Hamcrest and Mockito

Pom

spring-boot-starter-thymeleaf

Starter for building MVC web applications using Thymeleaf views

对 Thymeleaf 模板引擎的支持,包括和 Spring 的集成

Pom

spring-boot-starter-validation

Starter for using Java Bean Validation with Hibernate Validator

Pom

spring-boot-starter-web

Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container

对全栈 web 开发的支持,包括 Tomcat 和 spring-webmvc

Pom

spring-boot-starter-web-services

Starter for using Spring Web Services

Pom

spring-boot-starter-webflux

Starter for building WebFlux applications using Spring Framework’s Reactive Web support

Pom

spring-boot-starter-websocket

Starter for building WebSocket applications using Spring Framework’s WebSocket support

对 WebSocket 开发的支持

Pom

除了应用程序的 starters,下面的 starters 可以用于添加生产准备的特性:

Table 13.2. Spring Boot production starters

Name Description Pom

spring-boot-starter-actuator

Starter for using Spring Boot’s Actuator which provides production ready features to help you monitor and manage your application

添加生产准备特性,比如指标和监控

Pom

最后,Spring Boot 包含一些可用于排除或交换具体技术方面的 starters。

Table 13.3. Spring Boot technical starters

Name Description Pom

spring-boot-starter-jetty

Starter for using Jetty as the embedded servlet container. An alternative to spring-boot-starter-tomcat

导入 Jetty HTTP 引擎(作为 Tomcat 的替代)

Pom

spring-boot-starter-log4j2

Starter for using Log4j2 for logging. An alternative to spring-boot-starter-logging

对 Log4J 日志系统的支持

Pom

spring-boot-starter-logging

Starter for logging using Logback. Default logging starter

导入 Spring Boot 的默认日志系统(Logback)

Pom

spring-boot-starter-reactor-netty

Starter for using Reactor Netty as the embedded reactive HTTP server.

Pom

spring-boot-starter-tomcat

Starter for using Tomcat as the embedded servlet container. Default servlet container starter used by spring-boot-starter-web

导入 Spring Boot 的默认 HTTP 引擎(Tomcat)

Pom

spring-boot-starter-undertow

Starter for using Undertow as the embedded servlet container. An alternative to spring-boot-starter-tomcat

导入 Undertow HTTP 引擎(作为 Tomcat 的替代)

Pom

 使用 maven 插件用来打包 springboot 项目

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
</build>

 

 

 2.3.2 代码详解:

项目启动类 (Chapter01Application.java):

package com.zjt.chapter01;

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

@SpringBootApplication
public class Chapter01Application {

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

 

 上述 Chapter01Application.java 为 springboot 的 main 启动类;

这里我们开始学习到了 springboot 启动类中的最重要的一个注解:@SpringBootApplication

注意:

1. 项目启动类,根据官方建议,一定要位于非 default package 下;也就是说这个启动类一定要含有 package 的声明;建议使用反转域名;如 com.zjt.learn;

2. 官方建议,main 应用类要放在其他类上面的根包(root package)中;

3.@SpringBootApplication 该注解在基于上述 1 和 2 两点下,才可以使用 (习惯优于配置);

由于该注解相当于:

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

_______________华丽的小分割__________________

@SpringBootConfiguration:说明当前类是一个配置类,就像 xml 配置文件,而现在是用 java 配置文件,效果是一样的,它会被 @ComponentScan 扫描到。

@EnableAutoConfiguration(开启自动配置,springboot 的灵魂所在)注解通常都放到 main 所在类的上面,当 main 所在类位于 root package 的时候,这样 @EnableAutoConfiguration 可以从逐层的往下搜索各个加注解的类,例如,你正在编写一个 JPA 程序(如果你的 pom 里进行了配置的话),spring 会自动去搜索加了 @Entity 注解的类,并进行调用;

 @ComponentScan:用注解配置实现自动扫描,默认会扫描当前包和所有子包,和 xml 配置自动扫描效果一样;

___________________________________________

所以我们的 springboot 很贴心的为我们准备了 Springboot 提供了统一的注解 @SpringBootApplication 来替代以上三个注解,简化程序的配置;

2.3.3. 最后我们自己添加 controller 包,添加一个 TestController.java,接受 rest 请求:

package com.zjt.chapter01.controller;

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

@RestController
public class TestController {

    @RequestMapping("/hello")
    public String Hello(){
        return "hello world";
    }
}

 2.4. 启动项目

 2.4.1 使用 main 启动类启动:

 

看到这样就证明已经顺利启动了;

 

 

 

 2.4.2 使用 mvn spring-boot:run” 在命令行启动;

2.4.3 使用 mvn package 打成可执行 jar;使用 java -jar 启动;

mvn clean ;mvn build; mvn package;

打包后,命令行启动:

 

 


 

OK,今天的入门我们就一起做到这里;后续会同大家共同探讨很多 springboot 的实用教程和案例;文章中有什么问题或者想法欢迎老铁们吐槽和我沟通;我会及时纠正错误,改进教程的品质;

做程序需要静心,扎扎实实做技术;不忘初心,脚踏实地,就酱。

 

 

 

SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?

SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?

上一篇我们讲了SpringBoot中Tomcat的启动过程,本篇我们接着讲在SpringBoot中如何向Tomcat中添加Servlet、Filter、Listener

自定义Servlet、Filter、Listener

Spring容器中声明ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean

@Bean
public ServletRegistrationBean customServlet() {
    return new ServletRegistrationBean(new CustomServlet(), "/custom");
}

private static class CustomServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("receive by custom servlet");
    }
}

先自定义一个Servlet,重写service实现自己的业务逻辑,然后通过@Bean注解往Spring容器中注入一个ServletRegistrationBean类型的bean实例,并且实例化一个自定义的Servlet作为参数,这样就将自定义的Servlet加入Tomcat中了。

@ServletComponentScan注解和@WebServlet、@WebFilter以及@WebListener注解配合使用

@ServletComponentScan注解启用ImportServletComponentScanRegistrar类,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器所解析。ServletComponentScanRegistrar内部会解析@ServletComponentScan注解,然后会在Spring容器中注册ServletComponentRegisteringPostProcessor,是个BeanFactoryPostProcessor,会去解析扫描出来的类是不是有@WebServlet、@WebListener、@WebFilter这3种注解,有的话把这3种类型的类转换成ServletRegistrationBean、FilterRegistrationBean或者ServletListenerRegistrationBean,然后让Spring容器去解析:

@SpringBootApplication
@ServletComponentScan
public class EmbeddedServletApplication {
 ... 
}

@WebServlet(urlPatterns = "/simple")
public class SimpleServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("receive by SimpleServlet");
    }
}

在Spring容器中声明Servlet、Filter或者Listener

@Bean(name = "dispatcherServlet")
public DispatcherServlet myDispatcherServlet() {
    return new DispatcherServlet();
}

我们发现往Tomcat中添加Servlet、Filter或者Listener还是挺容易的,大家还记得以前SpringMVC是怎么配置DispatcherServlet的吗?在web.xml中:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

和我们SpringBoot中配置Servlet相比是不是复杂很多,虽然SpringBoot中自定义Servlet很简单,但是其底层却不简单,下面我们来分析一下其原理

ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean

我们来看看这几个特殊的类:

ServletRegistrationBean

public class ServletRegistrationBean extends RegistrationBean {
    //存放目标Servlet实例
    private Servlet servlet;
    //存放Servlet的urlMapping
    private Set<String> urlMappings;
    private boolean alwaysMapUrl;
    private int loadOnStartup;
    private MultipartConfigElement multipartConfig;


    public ServletRegistrationBean(Servlet servlet, String... urlMappings) {
        this(servlet, true, urlMappings);
    }

    public ServletRegistrationBean(Servlet servlet, boolean alwaysMapUrl, String... urlMappings) {
        this.urlMappings = new LinkedHashSet();
        this.alwaysMapUrl = true;
        this.loadOnStartup = -1;
        Assert.notNull(servlet, "Servlet must not be null");
        Assert.notNull(urlMappings, "UrlMappings must not be null");
        this.servlet = servlet;
        this.alwaysMapUrl = alwaysMapUrl;
        this.urlMappings.addAll(Arrays.asList(urlMappings));
    }
    
    public void onStartup(ServletContext servletContext) throws ServletException {
        Assert.notNull(this.servlet, "Servlet must not be null");
        String name = this.getServletName();
        if (!this.isEnabled()) {
            logger.info("Servlet " + name + " was not registered (disabled)");
        } else {
            logger.info("Mapping servlet: ''" + name + "'' to " + this.urlMappings);
            Dynamic added = servletContext.addServlet(name, this.servlet);
            if (added == null) {
                logger.info("Servlet " + name + " was not registered (possibly already registered?)");
            } else {
                this.configure(added);
            }
        }
    }
    
    //
}

在我们例子中我们通过return new ServletRegistrationBean(new CustomServlet(), "/custom");就知道,ServletRegistrationBean里会存放目标Servlet实例和urlMapping,并且继承RegistrationBean这个类

FilterRegistrationBean

public class FilterRegistrationBean extends AbstractFilterRegistrationBean {
    //存放目标Filter对象
    private Filter filter;

    public FilterRegistrationBean() {
        super(new ServletRegistrationBean[0]);
    }

    public FilterRegistrationBean(Filter filter, ServletRegistrationBean... servletRegistrationBeans) {
        super(servletRegistrationBeans);
        Assert.notNull(filter, "Filter must not be null");
        this.filter = filter;
    }

    public Filter getFilter() {
        return this.filter;
    }

    public void setFilter(Filter filter) {
        Assert.notNull(filter, "Filter must not be null");
        this.filter = filter;
    }
}

abstract class AbstractFilterRegistrationBean extends RegistrationBean {
    private static final EnumSet<DispatcherType> ASYNC_DISPATCHER_TYPES;
    private static final EnumSet<DispatcherType> NON_ASYNC_DISPATCHER_TYPES;
    private static final String[] DEFAULT_URL_MAPPINGS;
    private Set<ServletRegistrationBean> servletRegistrationBeans = new LinkedHashSet();
    private Set<String> servletNames = new LinkedHashSet();
    private Set<String> urlPatterns = new LinkedHashSet();
    //重写onStartup方法
    public void onStartup(ServletContext servletContext) throws ServletException {
        Filter filter = this.getFilter();
        Assert.notNull(filter, "Filter must not be null");
        String name = this.getOrDeduceName(filter);
        if (!this.isEnabled()) {
            this.logger.info("Filter " + name + " was not registered (disabled)");
        } else {
            Dynamic added = servletContext.addFilter(name, filter);
            if (added == null) {
                this.logger.info("Filter " + name + " was not registered (possibly already registered?)");
            } else {
                this.configure(added);
            }
        }
    }
    //略...
}

我们看到FilterRegistrationBean 中也保存了目标Filter对象,并且继承了RegistrationBean

ServletListenerRegistrationBean

public class ServletListenerRegistrationBean<T extends EventListener> extends RegistrationBean {
    //存放了目标listener
    private T listener;

    public ServletListenerRegistrationBean() {
    }

    public ServletListenerRegistrationBean(T listener) {
        Assert.notNull(listener, "Listener must not be null");
        Assert.isTrue(isSupportedType(listener), "Listener is not of a supported type");
        this.listener = listener;
    }

    public void setListener(T listener) {
        Assert.notNull(listener, "Listener must not be null");
        Assert.isTrue(isSupportedType(listener), "Listener is not of a supported type");
        this.listener = listener;
    }

    public void onStartup(ServletContext servletContext) throws ServletException {
        if (!this.isEnabled()) {
            logger.info("Listener " + this.listener + " was not registered (disabled)");
        } else {
            try {
                servletContext.addListener(this.listener);
            } catch (RuntimeException var3) {
                throw new IllegalStateException("Failed to add listener ''" + this.listener + "'' to servlet context", var3);
            }
        }
    }
    //略...
}

ServletListenerRegistrationBean也是一样,那我们来看看RegistrationBean这个类

public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
    ...
}
public interface ServletContextInitializer {
    void onStartup(ServletContext var1) throws ServletException;
}

我们发现RegistrationBean 实现了ServletContextInitializer这个接口,并且有一个onStartup方法,ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean都实现了onStartup方法。

ServletContextInitializer是 Servlet 容器初始化的时候,提供的初始化接口。所以,Servlet 容器初始化会获取并触发所有的FilterRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean实例中onStartup方法

那到底是何时触发这些类的onStartup方法呢?

当Tomcat容器启动时,会执行callInitializers,然后获取所有的ServletContextInitializer,循环执行onStartup方法触发回调方法。那FilterRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean实例是何时加入到Initializers集合的呢?这要回顾一下我们上一篇文章Tomcat的启动过程

Servlet容器的启动

大家可以看看我上一篇文章,我这里简单的复制一下代码

EmbeddedWebApplicationContext

 1 @Override
 2 protected void onRefresh() {
 3     super.onRefresh();
 4     try {
 5         //核心方法:会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器
 6         createEmbeddedServletContainer();
 7     }
 8     catch (Throwable ex) {
 9         throw new ApplicationContextException("Unable to start embedded container", ex);
10     }
11 }
12 
13 private void createEmbeddedServletContainer() {
14     EmbeddedServletContainer localContainer = this.embeddedServletContainer;
15     ServletContext localServletContext = getServletContext();
16     if (localContainer == null && localServletContext == null) {
17         //先获取嵌入式Servlet容器工厂
18         EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
19         //根据容器工厂来获取对应的嵌入式Servlet容器
20         this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
21     }
22     else if (localServletContext != null) {
23         try {
24             getSelfInitializer().onStartup(localServletContext);
25         }
26         catch (ServletException ex) {
27             throw new ApplicationContextException("Cannot initialize servlet context",ex);
28         }
29     }
30     initPropertySources();
31 }

关键代码在第20行,先通过getSelfInitializer()获取到所有的Initializer,传入Servlet容器中,那核心就在getSelfInitializer()方法:

1 private ServletContextInitializer getSelfInitializer() {
2     //只是创建了一个ServletContextInitializer实例返回
3     //所以Servlet容器启动的时候,会调用这个对象的onStartup方法
4     return new ServletContextInitializer() {
5         public void onStartup(ServletContext servletContext) throws ServletException {
6             EmbeddedWebApplicationContext.this.selfInitialize(servletContext);
7         }
8     };
9 }

我们看到只是创建了一个ServletContextInitializer实例返回,所以Servlet容器启动的时候,会调用这个对象的onStartup方法,那我们来分析其onStartup中的逻辑,也就是selfInitialize方法,并将Servlet上下文对象传进去了

selfInitialize

 1 private void selfInitialize(ServletContext servletContext) throws ServletException {
 2     prepareWebApplicationContext(servletContext);
 3     ConfigurableListableBeanFactory beanFactory = getBeanFactory();
 4     ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(beanFactory);
 5     WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,getServletContext());
 6     existingScopes.restore();
 7     WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,getServletContext());
 8     //这里便是获取所有的 ServletContextInitializer 实现类,会获取所有的注册组件
 9     for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
10         //执行所有ServletContextInitializer的onStartup方法
11         beans.onStartup(servletContext);
12     }
13 }

关键代码在第9和第11行,先获取所有的ServletContextInitializer 实现类,然后遍历执行所有ServletContextInitializer的onStartup方法

获取所有的ServletContextInitializer

我们来看看getServletContextInitializerBeans方法

protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());
}

ServletContextInitializerBeans对象是对ServletContextInitializer的一种包装:

 1 public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {
 2     private final MultiValueMap<Class<?>, ServletContextInitializer> initializers = new LinkedMultiValueMap();
 3     //存放所有的ServletContextInitializer
 4     private List<ServletContextInitializer> sortedList;
 5 
 6     public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
 7         //执行addServletContextInitializerBeans
 8         this.addServletContextInitializerBeans(beanFactory);
 9         //执行addAdaptableBeans
10         this.addAdaptableBeans(beanFactory);
11         List<ServletContextInitializer> sortedInitializers = new ArrayList();
12         Iterator var3 = this.initializers.entrySet().iterator();
13 
14         while(var3.hasNext()) {
15             Entry<?, List<ServletContextInitializer>> entry = (Entry)var3.next();
16             AnnotationAwareOrderComparator.sort((List)entry.getValue());
17             sortedInitializers.addAll((Collection)entry.getValue());
18         }
19         this.sortedList = Collections.unmodifiableList(sortedInitializers);
20     }
21 
22     private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
23         Iterator var2 = this.getOrderedBeansOfType(beanFactory, ServletContextInitializer.class).iterator();
24 
25         while(var2.hasNext()) {
26             Entry<String, ServletContextInitializer> initializerBean = (Entry)var2.next();
27             this.addServletContextInitializerBean((String)initializerBean.getKey(), (ServletContextInitializer)initializerBean.getValue(), beanFactory);
28         }
29 
30     }
31 
32     private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
33         if (initializer instanceof ServletRegistrationBean) {
34             Servlet source = ((ServletRegistrationBean)initializer).getServlet();
35             this.addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
36         } else if (initializer instanceof FilterRegistrationBean) {
37             Filter source = ((FilterRegistrationBean)initializer).getFilter();
38             this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
39         } else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
40             String source = ((DelegatingFilterProxyRegistrationBean)initializer).getTargetBeanName();
41             this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
42         } else if (initializer instanceof ServletListenerRegistrationBean) {
43             EventListener source = ((ServletListenerRegistrationBean)initializer).getListener();
44             this.addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
45         } else {
46             this.addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer);
47         }
48 
49     }
50 
51     private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory, Object source) {
52         this.initializers.add(type, initializer);
53         if (source != null) {
54             this.seen.add(source);
55         }
56 
57         if (logger.isDebugEnabled()) {
58             String resourceDescription = this.getResourceDescription(beanName, beanFactory);
59             int order = this.getOrder(initializer);
60             logger.debug("Added existing " + type.getSimpleName() + " initializer bean ''" + beanName + "''; order=" + order + ", resource=" + resourceDescription);
61         }
62 
63     }
64 
65     private void addAdaptableBeans(ListableBeanFactory beanFactory) {
66         MultipartConfigElement multipartConfig = this.getMultipartConfig(beanFactory);
67         this.addAsRegistrationBean(beanFactory, Servlet.class, new ServletContextInitializerBeans.ServletRegistrationBeanAdapter(multipartConfig));
68         this.addAsRegistrationBean(beanFactory, Filter.class, new ServletContextInitializerBeans.FilterRegistrationBeanAdapter(null));
69         Iterator var3 = ServletListenerRegistrationBean.getSupportedTypes().iterator();
70 
71         while(var3.hasNext()) {
72             Class<?> listenerType = (Class)var3.next();
73             this.addAsRegistrationBean(beanFactory, EventListener.class, listenerType, new ServletContextInitializerBeans.ServletListenerRegistrationBeanAdapter(null));
74         }
75 
76     }
77     
78     public Iterator<ServletContextInitializer> iterator() {
79         //返回所有的ServletContextInitializer
80         return this.sortedList.iterator();
81     }
82 
83     //略...
84 }

我们看到ServletContextInitializerBeans 中有一个存放所有ServletContextInitializer的集合sortedList,就是在其构造方法中获取所有的ServletContextInitializer,并放入sortedList集合中,那我们来看看其构造方法的逻辑,看到第8行先调用

addServletContextInitializerBeans方法:  

1 private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
2     //从Spring容器中获取所有ServletContextInitializer.class 类型的Bean
3     for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)) {
4         //添加到具体的集合中
5         addServletContextInitializerBean(initializerBean.getKey(),initializerBean.getValue(), beanFactory);
6     }
7 }

我们看到先从Spring容器中获取所有ServletContextInitializer.class 类型的Bean,这里我们自定义的ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean就被获取到了,然后调用addServletContextInitializerBean方法:

 1 private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
 2     //判断ServletRegistrationBean类型
 3     if (initializer instanceof ServletRegistrationBean) {
 4         Servlet source = ((ServletRegistrationBean)initializer).getServlet();
 5         //将ServletRegistrationBean加入到集合中
 6         this.addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
 7     //判断FilterRegistrationBean类型
 8     } else if (initializer instanceof FilterRegistrationBean) {
 9         Filter source = ((FilterRegistrationBean)initializer).getFilter();
10         //将ServletRegistrationBean加入到集合中
11         this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
12     } else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
13         String source = ((DelegatingFilterProxyRegistrationBean)initializer).getTargetBeanName();
14         this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
15     } else if (initializer instanceof ServletListenerRegistrationBean) {
16         EventListener source = ((ServletListenerRegistrationBean)initializer).getListener();
17         this.addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
18     } else {
19         this.addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer);
20     }
21 
22 }
23 
24 private void addServletContextInitializerBean(Class<?> type, String beanName, 
25                             ServletContextInitializer initializer, ListableBeanFactory beanFactory, Object source) {
26     //加入到initializers中
27     this.initializers.add(type, initializer);
28 }

很明显,判断从Spring容器中获取的ServletContextInitializer类型,如ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean,并加入到initializers集合中去,我们再来看构造器中的另外一个方法addAdaptableBeans(beanFactory):

 1 private void addAdaptableBeans(ListableBeanFactory beanFactory) {
 2     //从beanFactory获取所有Servlet.class和Filter.class类型的Bean,并封装成RegistrationBean对象,加入到集合中
 3     this.addAsRegistrationBean(beanFactory, Servlet.class, new ServletContextInitializerBeans.ServletRegistrationBeanAdapter(multipartConfig));
 4     this.addAsRegistrationBean(beanFactory, Filter.class, new ServletContextInitializerBeans.FilterRegistrationBeanAdapter(null));
 5 }
 6 
 7 private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type, Class<B> beanType, ServletContextInitializerBeans.RegistrationBeanAdapter<T> adapter) {
 8     //从Spring容器中获取所有的Servlet.class和Filter.class类型的Bean
 9     List<Entry<String, B>> beans = this.getOrderedBeansOfType(beanFactory, beanType, this.seen);
10     Iterator var6 = beans.iterator();
11 
12     while(var6.hasNext()) {
13         Entry<String, B> bean = (Entry)var6.next();
14         if (this.seen.add(bean.getValue())) {
15             int order = this.getOrder(bean.getValue());
16             String beanName = (String)bean.getKey();
17             //创建Servlet.class和Filter.class包装成RegistrationBean对象
18             RegistrationBean registration = adapter.createRegistrationBean(beanName, bean.getValue(), beans.size());
19             registration.setName(beanName);
20             registration.setOrder(order);
21             this.initializers.add(type, registration);
22             if (logger.isDebugEnabled()) {
23                 logger.debug("Created " + type.getSimpleName() + " initializer for bean ''" + beanName + "''; order=" + order + ", resource=" + this.getResourceDescription(beanName, beanFactory));
24             }
25         }
26     }
27 
28 }

我们看到先从beanFactory获取所有Servlet.class和Filter.class类型的Bean,然后通过ServletRegistrationBeanAdapter和FilterRegistrationBeanAdapter两个适配器将Servlet.class和Filter.class封装成RegistrationBean

private static class ServletRegistrationBeanAdapter implements ServletContextInitializerBeans.RegistrationBeanAdapter<Servlet> {
    private final MultipartConfigElement multipartConfig;

    ServletRegistrationBeanAdapter(MultipartConfigElement multipartConfig) {
        this.multipartConfig = multipartConfig;
    }

    public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) {
        String url = totalNumberOfSourceBeans == 1 ? "/" : "/" + name + "/";
        if (name.equals("dispatcherServlet")) {
            url = "/";
        }
        //还是将Servlet.class实例封装成ServletRegistrationBean对象
        //这和我们自己创建ServletRegistrationBean对象是一模一样的
        ServletRegistrationBean bean = new ServletRegistrationBean(source, new String[]{url});
        bean.setMultipartConfig(this.multipartConfig);
        return bean;
    }
}

private static class FilterRegistrationBeanAdapter implements ServletContextInitializerBeans.RegistrationBeanAdapter<Filter> {
    private FilterRegistrationBeanAdapter() {
    }

    public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) {
        //Filter.class实例封装成FilterRegistrationBean对象
        return new FilterRegistrationBean(source, new ServletRegistrationBean[0]);
    }
}

代码中注释很清楚了还是将Servlet.class实例封装成ServletRegistrationBean对象,将Filter.class实例封装成FilterRegistrationBean对象,这和我们自己定义ServletRegistrationBean对象是一模一样的,现在所有的ServletRegistrationBean、FilterRegistrationBean

Servlet.class、Filter.class都添加到List<ServletContextInitializer> sortedList这个集合中去了,接着就是遍历这个集合,执行其onStartup方法了

ServletContextInitializer的onStartup方法

ServletRegistrationBean

public class ServletRegistrationBean extends RegistrationBean {
    private static final Log logger = LogFactory.getLog(ServletRegistrationBean.class);
    private static final String[] DEFAULT_MAPPINGS = new String[]{"/*"};
    private Servlet servlet;
    
    public void onStartup(ServletContext servletContext) throws ServletException {
        Assert.notNull(this.servlet, "Servlet must not be null");
        String name = this.getServletName();
        //调用ServletContext的addServlet
        Dynamic added = servletContext.addServlet(name, this.servlet);
    }
    
    //略...
}

private javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String servletClass, Servlet servlet, Map<String, String> initParams) throws IllegalStateException {
    if (servletName != null && !servletName.equals("")) {
        if (!this.context.getState().equals(LifecycleState.STARTING_PREP)) {
            throw new IllegalStateException(sm.getString("applicationContext.addServlet.ise", new Object[]{this.getContextPath()}));
        } else {
            Wrapper wrapper = (Wrapper)this.context.findChild(servletName);
            if (wrapper == null) {
                wrapper = this.context.createWrapper();
                wrapper.setName(servletName);
                this.context.addChild(wrapper);
            } else if (wrapper.getName() != null && wrapper.getServletClass() != null) {
                if (!wrapper.isOverridable()) {
                    return null;
                }

                wrapper.setOverridable(false);
            }

            if (servlet == null) {
                wrapper.setServletClass(servletClass);
            } else {
                wrapper.setServletClass(servlet.getClass().getName());
                wrapper.setServlet(servlet);
            }

            if (initParams != null) {
                Iterator i$ = initParams.entrySet().iterator();

                while(i$.hasNext()) {
                    Entry<String, String> initParam = (Entry)i$.next();
                    wrapper.addInitParameter((String)initParam.getKey(), (String)initParam.getValue());
                }
            }

            return this.context.dynamicServletAdded(wrapper);
        }
    } else {
        throw new IllegalArgumentException(sm.getString("applicationContext.invalidServletName", new Object[]{servletName}));
    }
}

看到没,ServletRegistrationBean 中的 onStartup先获取Servlet的name,然后调用ServletContext的addServlet将Servlet加入到Tomcat中,这样我们就能发请求给这个Servlet了。

AbstractFilterRegistrationBean

public void onStartup(ServletContext servletContext) throws ServletException {
    Filter filter = this.getFilter();
    Assert.notNull(filter, "Filter must not be null");
    String name = this.getOrDeduceName(filter);
    //调用ServletContext的addFilter
    Dynamic added = servletContext.addFilter(name, filter);
}

AbstractFilterRegistrationBean也是同样的原理,先获取目标Filter,然后调用ServletContext的addFilter将Filter加入到Tomcat中,这样Filter就能拦截我们请求了。

DispatcherServletAutoConfiguration

最熟悉的莫过于,在Spring Boot在自动配置SpringMVC的时候,会自动注册SpringMVC前端控制器: DispatcherServlet,该控制器主要在 DispatcherServletAutoConfiguration自动配置类中进行注册的。DispatcherServlet是SpringMVC中的核心分发器。DispatcherServletAutoConfiguration也在spring.factories中配置了

DispatcherServletConfiguration

 1 @Configuration
 2 @ConditionalOnWebApplication
 3 // 先看下ClassPath下是否有DispatcherServlet.class字节码
 4 // 我们引入了spring-boot-starter-web,同时引入了tomcat和SpringMvc,肯定会存在DispatcherServlet.class字节码
 5 @ConditionalOnClass({DispatcherServlet.class})
 6 // 这个配置类的执行要在EmbeddedServletContainerAutoConfiguration配置类生效之后执行
 7 // 毕竟要等Tomcat启动后才能往其中注入DispatcherServlet
 8 @AutoConfigureAfter({EmbeddedServletContainerAutoConfiguration.class})
 9 protected static class DispatcherServletConfiguration {
10   public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
11   public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
12   @Autowired
13   private ServerProperties server;
14 
15   @Autowired
16   private WebMvcProperties webMvcProperties;
17 
18   @Autowired(required = false)
19   private MultipartConfigElement multipartConfig;
20 
21   // Spring容器注册DispatcherServlet
22   @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
23   public DispatcherServlet dispatcherServlet() {
24     // 直接构造DispatcherServlet,并设置WebMvcProperties中的一些配置
25     DispatcherServlet dispatcherServlet = new DispatcherServlet();
26     dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
27     dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
28     dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
29     return dispatcherServlet;
30   }
31 
32   @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
33   public ServletRegistrationBean dispatcherServletRegistration() {
34     // 直接使用DispatcherServlet和server配置中的servletPath路径构造ServletRegistrationBean
35     // ServletRegistrationBean实现了ServletContextInitializer接口,在onStartup方法中对应的Servlet注册到Servlet容器中
36     // 所以这里DispatcherServlet会被注册到Servlet容器中,对应的urlMapping为server.servletPath配置
37     ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), this.server.getServletMapping());
38     registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
39     if (this.multipartConfig != null) {
40       registration.setMultipartConfig(this.multipartConfig);
41     }
42     return registration;
43   }
44 
45   @Bean // 构造文件上传相关的bean
46   @ConditionalOnBean(MultipartResolver.class)
47   @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
48   public MultipartResolver multipartResolver(MultipartResolver resolver) {
49     return resolver;
50   }
51 
52 }

先看下ClassPath下是否有DispatcherServlet.class字节码, 我们引入了spring-boot-starter-web,同时引入了tomcat和SpringMvc,肯定会存在DispatcherServlet.class字节码,如果没有导入spring-boot-starter-web,则这个配置类将不会生效

然后往Spring容器中注册DispatcherServlet实例,接着又加入ServletRegistrationBean实例,并把DispatcherServlet实例作为参数,上面我们已经学过了ServletRegistrationBean的逻辑,在Tomcat启动的时候,会获取所有的ServletRegistrationBean,并执行其中的onstartup方法,将DispatcherServlet注册到Servlet容器中,这样就类似原来的web.xml中配置的dispatcherServlet。

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

所以只要导入了spring-boot-starter-web这个starter,SpringBoot就有了Tomcat容器,并且往Tomcat容器中注册了DispatcherServlet对象,这样就能接收到我们的请求了

 
 

 

原文出处:https://www.cnblogs.com/java-chen-hao/p/11842611.html

今天关于SpringBoot 2.X 课程学习 | 第一篇:初识 SpringBootspringboot2.0教程的分享就到这里,希望大家有所收获,若想了解更多关于SpringBoot 2.X 课程学习 | 第六篇:挖掘配置文件的秘密、SpringBoot 2.X课程学习 | 第四篇:初识springboot配置文件、springboot 学习笔记:2. 搭建你的第一个 springboot 应用、SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?等相关知识,可以在本站进行查询。

本文标签: