SpringBoot学习

我的springboot版本:2.7.2

1、SpringBoot概述

1.1、回顾什么是Spring

Spring是一个开源框架,2003 年兴起的一个轻量级的 Java 开发框架,作者:Rod Johnson。

Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。

1.2、Spring是如何简化Java开发的

为了降低Java开发的复杂性,Spring采用了以下4种关键策略:

  1. 基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
  2. 通过IOC,依赖注入(DI)和面向接口实现松耦合;
  3. 基于切面(AOP)和惯例进行声明式编程;
  4. 通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

1.3、什么是SpringBoot

SpringBoot 就是一个 javaweb 的开发框架,和 SpringMVC 类似,对比其他 javaweb 框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。

所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景衍生一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。

这就是Java企业级应用->J2EE->spring->springboot 的过程。

随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;

Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。

Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门;
  • 开箱即用,提供各种默认配置来简化项目配置;
  • 内嵌式容器简化Web项目;
  • 没有冗余代码生成和XML配置的要求;

1.4、微服务架构

什么是微服务?

微服务是一种架构风格,他要求我们在开发一个应用的时候,这个应用必须建成一系列小服务组合,可以通过http方式进行通信。

单体应用架构:

所谓单体应用架构(all in one)是指,我们将一个应用中的所有应用服务都封装在一个应用中。

无论是ERP、CRM或是其他什么系统,你都把数据库访问,web访问,等等各个功能放到一个war包内。

  • 这样做的好处是,易于开发和测试;也十分方便部署了;当需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。
  • 单体应用架构的缺点时,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用war包。特别是对于一个大型应用,我们不可能把所有内容都放在一个应用里面,我们如何维护、如何分工合作都是问题。

微服务架构:

all in one 的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。

所谓微服务架构,就是打破之前 all in one 的架构方式,把每个功能元素独立出来,把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些可以整合多个功能元素,所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。

这样做的好处是:

  1. 节省了调用资源;
  2. 每个功能元素的服务都是一个可替换的,可独立升级的软件代码;
  3. 高内聚(在划分模块时,要把功能关系紧密的放到一个模块中);低耦合(模块之间的联系越少越好,接口越简单越好);

springboot3全栈开发pdf java spring boot开发_spring boot

如何构建微服务:

一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元就是一个功能元素, 它们各自完成自己的功能,然后通过http相互请求调用。比如一个电商系统,查缓存、连数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务共同构建了一个庞大的系统。如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。

但是这种庞大的系统架构给部署和运维带来很大的难度。于是,spring为我们带来了构建大型分布式微服务的全套、全程产品:

  • 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用;
  • 大型分布式网络服务的调用,这部分springcloud来完成,实现分布式;
  • 在分布式中间,进行流式数据计算、批处理,我们有spring cloud data flow;
  • spring为我们想清楚了整个开始构建应用到大型分布式应用全流程方案;

springboot3全栈开发pdf java spring boot开发_spring boot_02

2、第一个SpringBoot程序

2.1、准备工作

  • Jdk 1.8
  • Maven 3.6.1
  • Springboot:最新版
  • 开发工具:IDEA

2.2、创建基础项目说明

**项目创建方式一:**使用Spring Initializr 的 Web 页面创建项目。

Spring官方提供了非常方便的工具让我们快速构建应用:Spring Initializr:https://start.spring.io/

  1. 打开 https://start.spring.io/;
  2. 填写项目信息;
  3. 点击”Generate Project“按钮生成项目,下载此项目;
  4. 解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕;
  5. 如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。

**项目创建方式二:**使用 IDEA 直接创建项目。

  1. 创建一个新项目;
  2. 选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现;
  3. 填写项目信息;

springboot3全栈开发pdf java spring boot开发_学习_03

  1. 选择初始化的组件(初学勾选 Web 即可);

springboot3全栈开发pdf java spring boot开发_微服务_04

  1. 填写项目路径;
  2. 等待项目构建成功;

2.3、项目结构分析

项目结构分析:

通过上面步骤完成了基础项目的创建,就会自动生成以下文件:

  1. 程序的主启动类 HelloworldApplication;
  2. 一个配置文件 application.properties;
  3. 一个 测试类 HelloworldApplicationTests;
  4. 一个 pom.xml;

  • 主启动类 HelloworldApplication:
package com.kuang.helloworld;

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

//本身就是spring的一个组件。

//程序的主入口
@SpringBootApplication
public class HelloworldApplication {

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

}
  • 配置文件 application.properties : (可以更改端口号)
# springboot 的核心配置文件
server.port=8081
  • 测试类 HelloworldApplicationTests:
package com.kuang.helloworld;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

//单元测试
@SpringBootTest
class HelloworldApplicationTests {

    @Test
    void contextLoads() {
    }

}
  • 打开pom.xml,看看Spring Boot项目的依赖:
<?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.7.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.kuang</groupId>
    <artifactId>helloworld</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>helloworld</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <!--spring-boot-starter所有的springboot依赖都是使用这个开头的-->

        <!--web依赖:tomcat,dispatcherSerlvert,xml。。。-->
        <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>
        <!--打jar包插件-->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.4、编写一个http接口

  1. 在主程序的同级目录下,新建一个controller包。(一定要在同级目录下,否则识别不到)
  2. 在包中新建一个 HelloController 类:
package com.kuang.helloworld.controller;

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

//自动装配,原理。
@RestController
public class HelloController {

    //接口:http://localhost:8080/hello
    @RequestMapping("/hello")
    public String hello(){
        //调用业务,接受前端的参数!
        return "hello,springboot!";
    }

}
  1. 编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台也输出了 Tomcat 访问的端口号!

springboot3全栈开发pdf java spring boot开发_spring_05

简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单。所以我们常用它来建立我们的微服务项目!

2.5、打包项目

将项目打成jar包,点击 maven 的 package。

如果打包成功,则会在target目录下生成一个 jar 包。

springboot3全栈开发pdf java spring boot开发_学习_06

打成了jar包后,就可以在任何地方运行了!

2.6、彩蛋

如何更改启动时显示的字符拼成的字母SpringBoot呢?也就是 banner 图案;

只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。

图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可!

springboot3全栈开发pdf java spring boot开发_spring_07

3、运行原理初探

3.1、pom.xml

父依赖

它主要依赖一个父项目,主要是管理项目的资源过滤及插件。

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

点进去,发现还有一个父依赖:

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

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

以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了。

启动器 spring-boot-starter

<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即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 。

3.2、主启动类

默认的主启动类

package com.kuang;

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

//SpringBootApplication:标注这个类是一个SpringBoot的应用。
@SpringBootApplication
public class Springboot02ConfigApplication {

    public static void main(String[] args) {
        //将SpringBoot应用启动。
        SpringApplication.run(Springboot02ConfigApplication.class, args);
    }

}

但是**一个简单的启动类并不简单!**我们来分析一下这些注解都干了什么。

@SpringBootApplication

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

进入这个注解:可以看到上面还有很多其他注解!

springboot3全栈开发pdf java spring boot开发_微服务_08

@ComponentScan

这个注解在Spring中很重要 ,它对应XML配置中的元素。

作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中。

@SpringBootConfiguration

作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类。

我们点击进去查看这个注解:

@Configuration
public @interface SpringBootConfiguration {}

//再点击 @Configuration 进去看看
@Component
public @interface Configuration {}

这里的 @Configuration,说明这是一个配置类 ,配置类就是对应 Spring 的 xml 配置文件;

里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!

然后,我们回到 SpringBootApplication 注解中继续看。

@EnableAutoConfiguration

@EnableAutoConfiguration :开启自动配置功能

以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 。@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

点进去继续查看:

@AutoConfigurationPackage :自动配置包

再点进去查看:

@Import(AutoConfigurationPackages.Registrar.class)

@import :Spring底层注解@import , 给容器中导入一个组件。

Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 。

然后,我们退到上一步,继续看:

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

AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?

我们点击去这个类看源码:

  1. 这个类中有一个这样的方法:
//获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  //这里的getSpringFactoriesLoaderFactoryClass()方法
  //返回的就是我们最开始看的启动自动导入配置文件的注解类:EnableAutoConfiguration
  List<String> configurations = new ArrayList<>(
      SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
  ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
  Assert.notEmpty(configurations,
      "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
          + "are using a custom packaging, make sure that file is correct.");
  return configurations;
}


protected Class<?> getSpringFactoriesLoaderFactoryClass() {
  return EnableAutoConfiguration.class;
}
  1. 这个方法还调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法,点进去查看:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    //这里它又调用了 loadSpringFactories 方法
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
  1. 我们继续点击查看 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);
        }
    }
}
  1. 然后我们搜索 spring.factories 这个文件。

spring.factories

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

springboot3全栈开发pdf java spring boot开发_spring_09

我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration 。

(我在spring.factories没找到,在下面org包中找到的)

springboot3全栈开发pdf java spring boot开发_spring_10

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

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

结论:

springboot所有自动配置都是在启动的时候扫描并加载: spring.factories。所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!

  1. springboot在启动的时候,从类路径下/META-INF/spring. factories获取指定的值;
  2. 将这些自动配置的类导入容器,自动配置就会生效,帮我进行自动配置!
  3. 以前我们需要自动配置的东西,现在springboot帮我们做了!
  4. 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar这个包下;
  5. 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
  6. 容器中也会存在非常多的 xxxAutoConfiguration 的文件(@Bean),就是这些类给容器中导入了这个场景需要
    的所有组件,并自动配置,@Configuration, JavaConfig!
  7. 有了自动配置类,免去了我们手动编写配置文件的工作!

3.3、SpringApplication

最初以为就是运行了一个main方法,没想到却开启了一个服务!

SpringApplication.run分析:

分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行。

SpringApplication

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

  1. 推断应用的类型是普通的项目还是Web项目。
  2. 查找并加载所有可用初始化器 , 设置到initializers属性中。
  3. 找出所有的应用程序监听器,设置到listeners属性中。
  4. 推断并设置main方法的定义类,找到运行的主类。

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // ......
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

run方法流程分析:

springboot3全栈开发pdf java spring boot开发_微服务_11

4、yaml配置注入

4.1、yaml概述

YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。

这种语言以数据作为中心,而不是以标记语言为重点!

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml:

传统xml配置:

<server>
    <port>8081<port>
</server>

yaml配置:

server:
  prot: 8080

4.2、yaml基础语法

properties语法:

# properties 只能保存键值对

name = kuangshen
student.name = kuangshen
student.age = kuangshen

yaml语法:

# 对空格的要求十分高!
# 注入到我们的配置类中!

# k-v
name: kuangshen
# 对象
student:
  name: kuangshen
  age: 3
# 行内写法
student2: {name: kuangshen,age: 3}
# 数组
pets:
  - cat
  - dog
  - pig
pets2: [cat,dog,pig]

说明:语法要求严格!

  1. 空格不能省略;
  2. 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的;
  3. 属性和值的大小写都是十分敏感的。

4.3、yaml注入配置文件

yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!

  1. 在项目中的 resources 目录下新建一个文件 application.yaml 配置文件。
  2. 编写一个 Dog 实体类。并用 @Value 给bean注入属性值。
package com.kuang.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component   //注册bean到容器中
public class Dog {

    @Value("旺财")
    private String dogName;
    @Value("3")
    private Integer age;

    public Dog() {
    }

    public Dog(String dogName, Integer age) {
        this.dogName = dogName;
        this.age = age;
    }

    public String getDogName() {
        return dogName;
    }

    public void setDogName(String dogName) {
        this.dogName = dogName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "dogName='" + dogName + '\'' +
                ", age=" + age +
                '}';
    }

}
  1. 在SpringBoot的测试类下查看注入的输出。
package com.kuang;

import com.kuang.pojo.Dog;
import com.kuang.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot02ConfigApplicationTests {
  
    @Autowired   //将狗的类自动注入进来。
    private Dog dog;

    @Test
    void contextLoads() {
        System.out.println(dog);
    }
  
}
  1. 查看结果,@Value注入成功!这是我们原来的办法。
  2. 我们再编写一个复杂一点的实体类:Person 类。
package com.kuang.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Email;
import java.util.Date;
import java.util.List;
import java.util.Map;

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

    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

    public Person() {
    }

    public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
        this.name = name;
        this.age = age;
        this.happy = happy;
        this.birth = birth;
        this.maps = maps;
        this.lists = lists;
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Boolean getHappy() {
        return happy;
    }

    public void setHappy(Boolean happy) {
        this.happy = happy;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public List<Object> getLists() {
        return lists;
    }

    public void setLists(List<Object> lists) {
        this.lists = lists;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", happy=" + happy +
                ", birth=" + birth +
                ", maps=" + maps +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }

}
  1. 我们来使用yaml配置的方式进行注入,编写一个yaml配置!
person:
  name: kuangshen
  age: 3
  happy: false
  birth: 2000/01/01
  maps: {k1: v1,k2: v2}
  lists:
   - code
   - girl
   - music
  dog:
    name: 旺财
    age: 1
  1. 我们刚才已经把person这个对象的所有值都写好了,然后通过上面代码中的以下操作来注入到我们的类中!
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应。
*/
@ConfigurationProperties(prefix = "person")
  1. IDEA 提示springboot配置注解处理器没有找到。我们点开查看文档,根据文档的提示,添加一个依赖!
<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
  1. 确认以上配置都OK之后,我们去测试类中测试一下:
package com.kuang;

import com.kuang.pojo.Dog;
import com.kuang.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot02ConfigApplicationTests {

    @Autowired
    private Person person;

    @Test
    void contextLoads() {
        System.out.println(person);
    }

}
  1. 查看输出结果,所有值全部注入成功!

4.4、加载指定的配置文件

**@PropertySource :**加载指定的配置文件;

@configurationProperties:默认从全局配置文件中获取值;

  1. 我们去在resources目录下新建一个 kuang.properties文件。
name=kuangshen
  1. 然后在我们的代码中指定加载 kuang.properties 文件。
@Component
@PropertySource(value = "classpath:kuang.properties")
public class Person {

    @Value("${name}")  //SPEL表达式取出配置文件的值。
    private String name;
    ......
      
}
  1. 再次测试,查看结果!指定配置文件绑定成功!

注意:

properties 配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8:

springboot3全栈开发pdf java spring boot开发_spring_12

4.5、配置文件占位符

配置文件还可以编写占位符生成随机数:

person:
  name: kuangshen${random.uuid}   # 随机的uuid
  age: ${random.int}   # 随机的id
  happy: false
  birth: 1999/12/23
  maps: {k1: v1,k2: v2}
  hello: hello!
  lists:
    - code
    - music
    - girl
  dog:
    # 如果配置文件中有person.hello这个属性,就会输出这个属性;如果没有的话,会输出hello。
    name: ${person.hello:hello}-旺财
    age: 3

4.6、对比小结

@Value 这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦,我们来看个功能对比图:

springboot3全栈开发pdf java spring boot开发_spring_13

  1. @ConfigurationProperties只需要写一次即可,@Value则需要每个字段都添加;
  2. 松散绑定:比如我的yaml中写的 last-name,这个和 lastName 是一样的,后面跟着的字母默认是大写的。这就是松散绑定。
  3. JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性;
  4. 复杂类型封装,yaml中可以封装对象 , 使用@Value就不支持;

结论:

  • 配置 yaml 和配置 properties 都可以获取到值,但强烈推荐 yaml;
  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

5、JSR303数据校验及多环境切换

5.1、JSR303数据校验的使用

Springboot中可以用 @validated 来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。

我们这里来写个注解让我们的name只能支持Email格式。

@Component
@ConfigurationProperties(prefix = "person")
@Validated   //数据校验
public class Person {

    @Email(message = "邮箱格式错误!")   //name必须是邮箱格式,f否则会提示message。
    private String name;
		......
      
}

查看运行结果 :

springboot3全栈开发pdf java spring boot开发_spring_14

使用数据校验,可以保证数据的正确性;

注意:

SpringBoot 2.3.0版本之后就没有引入validation对应的包,程序包javax.validation不存在,需要自己手动导入!

<!--SpringBoot 2.3.0版本之后就没有引入validation对应的包,程序包javax.validation不存在-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

5.2、JSR303数据校验常见参数

@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null       验证对象是否为null。
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串。
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格。
@NotEmpty   检查约束元素是否为NULL或者是EMPTY。
    
Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

日期检查
@Past       验证 Date 和 Calendar 对象是否在当前时间之前  
@Future     验证 Date 和 Calendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则

5.3、多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境。

多配置文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本。

例如:

application-test.properties 代表测试环境配置;

application-dev.properties 代表开发环境配置;

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

我们需要通过一个配置来选择需要激活的环境:

# 比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
# 我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

yaml的多文档块

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !

server:
  port: 8088
#选择要激活那个环境块
spring:
  profiles:
    active: dev

---
server:
  port: 8089
spring:
  profiles: dev  # 配置环境的名称

---
server:
  port: 8090
spring:
  profiles: test  # 配置环境的名称

注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!

配置文件加载位置

外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!

springboot3全栈开发pdf java spring boot开发_微服务_15

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

优先级由高到底,高优先级的配置会覆盖低优先级的配置。

SpringBoot会从这四个位置全部加载主配置文件,互补配置!

6、自动配置原理

6.1、分析自动配置原理

我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理:

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件。
@Configuration 

//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取。
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //。。。。。。。
}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类。
//从配置文件中获取指定的值和bean的属性进行绑定。
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

我们去配置文件里面试试前缀,看提示!

springboot3全栈开发pdf java spring boot开发_学习_16

精髓:

  1. SpringBoot启动会加载大量的自动配置类;
  2. 我们看我们需要的功能有没有在 SpringBoot 默认写好的自动配置类当中;
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了);
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件;

xxxxProperties:封装配置文件中相关属性;

6.2、了解@Conditional

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效。

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置里面的所有内容才生效。

springboot3全栈开发pdf java spring boot开发_spring_17

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

我们怎么知道哪些自动配置类生效?

**我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;**在配置文件中加上以下:

#开启springboot的调试类
debug=true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)

【演示:查看输出的日志】

7、SpringBoot Web开发

7.1、静态资源处理

我们的项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?

如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的。但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!

第一种静态资源映射规则:

在SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面。我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法。

其中有一个方法:addResourceHandlers(添加资源处理):

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
  if (!this.resourceProperties.isAddMappings()) {
    // 已禁用默认资源处理
    logger.debug("Default resource handling disabled");
    return;
  }
  // webjars 配置
  addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
  addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
    if (this.servletContext != null) {
      ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
      registration.addResourceLocations(resource);
    }
  });
}

读一下源代码:比如所有的 /webjars/**, 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源。

什么是webjars 呢?

Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。

使用SpringBoot需要使用Webjars,我们可以去搜索一下:网站:https://www.webjars.org

要使用jQuery,我们只要引入jQuery对应版本的pom依赖即可:

<!--webjars-->
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

导入完毕后,查看webjars目录结构:

springboot3全栈开发pdf java spring boot开发_springboot3全栈开发pdf_18

只要是静态资源,SpringBoot就会去对应的路径寻找资源。

要访问Jquery.js文件,我们这里就访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

springboot3全栈开发pdf java spring boot开发_微服务_19

第二种静态资源映射规则:

那我们项目中要是使用自己的静态资源该怎么导入呢?我们去上面所述代码中的看下一行代码。

我们进入 getStaticLocations() 方法进一步分析:

//在上面的代码中进入 getStaticLocations 方法。
public String[] getStaticLocations() {
  return this.staticLocations;
}

//点击 staticLocations 找到对应的值。
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

//再点击 CLASSPATH_RESOURCE_LOCATIONS 找到对应的路径。
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { 
  "classpath:/META-INF/resources/",
	"classpath:/resources/", 
  "classpath:/static/", 
  "classpath:/public/" 
};

这里可以设置和我们静态资源有关的参数,这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

所以得出结论,以下四个目录存放的静态资源可以被我们识别:

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件。

比如:我们在对应的位置新建一个 1.js ,并访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件:

springboot3全栈开发pdf java spring boot开发_微服务_20

通过测试,我们发现这几个位置的优先级为:

resources > static > public

自定义静态资源路径:

我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置:

这里研究了老半天才看明白😂

# 指定了访问项目中的静态资源的url地址,需要以/static开头.   http://localhost:8080/static/1.js
spring.mvc.static-path-pattern = /static/**
# 指定静态资源的存放位置.   http://localhost:8080/1.js
spring.web.resources.static-locations=classpath:/kuang/

# 这两个配合使用,意思是输入 http://localhost:8080/static/1.js 访问了 kuang/1.js

一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!

7.2、首页处理

静态资源文件夹说完后,我们继续向下看源码!可以看到一个欢迎页的映射 welcomePageHandlerMapping ,就是我们的首页!

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
  WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
      new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
      this.mvcProperties.getStaticPathPattern());
  welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
  welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
  return welcomePageHandlerMapping;
}

点击 getWelcomePage() 进去看看:

private Resource getWelcomePage() {
  for (String location : this.resourceProperties.getStaticLocations()) {
    Resource indexHtml = getIndexHtml(location);
    if (indexHtml != null) {
      return indexHtml;
    }
  }
  ServletContext servletContext = getServletContext();
  if (servletContext != null) {
    return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION));
  }
  return null;
}

// 欢迎页就是一个 location 下的 index.html 而已。
private Resource getIndexHtml(String location) {
  return getIndexHtml(this.resourceLoader.getResource(location));
}

欢迎页:静态资源文件夹下的所有 index.html 页面都被 /** 映射。

比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html。

新建一个 index.html ,在我们上面的3个目录中任意一个,然后访问测试 http://localhost:8080/ 就可以到我们的首页了!

8、Thymeleaf模板引擎

8.1、模板引擎

前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。

jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的

那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?

SpringBoot推荐你可以来使用模板引擎:

模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢?我们来看一下这张图:

springboot3全栈开发pdf java spring boot开发_学习_21

模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。

8.2、引入Thymeleaf

怎么引入呢,对于springboot来说,什么事情都是一个start的事情,我们去在项目中引入一下。

给大家三个网址:

Thymeleaf 官网:https://www.thymeleaf.org/

Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf

Spring官方文档:找到我们对应的版本:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

找到对应的pom依赖包,并start启动!

<!--thymeleaf,我们都是基于3.x开发的-->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<!--启动thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Maven会自动下载jar包,我们可以去看下下载的东西:

springboot3全栈开发pdf java spring boot开发_spring boot_22

8.3、Thymeleaf分析

前面我们已经引入了Thymeleaf,那这个要怎么使用呢?

我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,再按照那个规则,我们进行使用。

我们去找一下Thymeleaf的自动配置类:ThymeleafProperties:

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
  
	private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
	public static final String DEFAULT_PREFIX = "classpath:/templates/";
	public static final String DEFAULT_SUFFIX = ".html";
	private boolean checkTemplate = true;
	private boolean checkTemplateLocation = true;
	private String prefix = DEFAULT_PREFIX;
	private String suffix = DEFAULT_SUFFIX;
	private String mode = "HTML";
	private Charset encoding = DEFAULT_ENCODING;
  ......
    
}

我们可以在其中看到默认的前缀和后缀!

我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。

使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!

8.4、Thymeleaf 语法学习

要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下。

Thymeleaf 官网:https://www.thymeleaf.org/ ,简单看一下官网!我们去下载 Thymeleaf 的官方文档!

测试代码:

  1. 编写一个 HelloController:
package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Arrays;

//在templates目录下的所有资源,只能通过controller来跳转!
//这个需要模版引擎的支持!
@Controller
public class IndexController {

    @RequestMapping("/test")
    public  String test(Model model){
        model.addAttribute("msg","<h1>hello,springboot!</h1>");
        model.addAttribute("users", Arrays.asList("kuang","shen"));
        return "test";
    }

}
  1. 我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。
xmlns:th="http://www.thymeleaf.org
  1. 编写一个测试页面 test.html 放在 templates 目录下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<!--首先,需要在html文件中导入命名空间的约束。-->
<!--所有的html元素都可以被thymeleaf替换接管: th:元素名-->
<div th:text="${msg}"></div>
<!--不转义-->
<div th:utext="${msg}"></div>

<hr>

<!--遍历数据,两种取值的方式:-->
<h3 th:each="user:${users}" th:text="${user}"></h3>
<h3 th:each="user:${users}">[[${user}]]</h3>

</body>
</html>
  1. 启动项目测试,查看结果!

springboot3全栈开发pdf java spring boot开发_spring_23

结论:只要需要使用 thymeleaf ,只需要导入对应的依赖,在html文件中导入命名空间的约束就可以了!我们将html放在我们的templates目录下即可。


OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!

  1. 我们可以使用任意的 th:attr 来替换html中原生属性的值!

springboot3全栈开发pdf java spring boot开发_springboot3全栈开发pdf_24

  1. 我们能写哪些表达式呢?
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
    1)、获取对象的属性、调用方法
    2)、使用内置的基本对象:#18
         #ctx : the context object.
         #vars: the context variables.
         #locale : the context locale.
         #request : (only in Web Contexts) the HttpServletRequest object.
         #response : (only in Web Contexts) the HttpServletResponse object.
         #session : (only in Web Contexts) the HttpSession object.
         #servletContext : (only in Web Contexts) the ServletContext object.

    3)、内置的一些工具对象:
      #execInfo : information about the template being processed.
      #uris : methods for escaping parts of URLs/URIs
      #conversions : methods for executing the configured conversion service (if any).
      #dates : methods for java.util.Date objects: formatting, component extraction, etc.
      #calendars : analogous to #dates , but for java.util.Calendar objects.
      #numbers : methods for formatting numeric objects.
      #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
      #objects : methods for objects in general.
      #bools : methods for boolean evaluation.
      #arrays : methods for arrays.
      #lists : methods for lists.
      #sets : methods for sets.
      #maps : methods for maps.
      #aggregates : methods for creating aggregates on arrays or collections.
==================================================================================

  Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
  Message Expressions: #{...}:获取国际化内容
  Link URL Expressions: @{...}:定义URL;
  Fragment Expressions: ~{...}:片段引用表达式

Literals(字面量)
      Text literals: 'one text' , 'Another one!' ,…
      Number literals: 0 , 34 , 3.0 , 12.3 ,…
      Boolean literals: true , false
      Null literal: null
      Literal tokens: one , sometext , main ,…
      
Text operations:(文本操作)
    String concatenation: +
    Literal substitutions: |The name is ${name}|
    
Arithmetic operations:(数学运算)
    Binary operators: + , - , * , / , %
    Minus sign (unary operator): -
    
Boolean operations:(布尔运算)
    Binary operators: and , or
    Boolean negation (unary operator): ! , not
    
Comparisons and equality:(比较运算)
    Comparators: > , < , >= , <= ( gt , lt , ge , le )
    Equality operators: == , != ( eq , ne )
    
Conditional operators:条件运算(三元运算符)
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
    
Special tokens:
    No-Operation: _

9、MVC自动配置原理

9.1、官网阅读

在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!

Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars 
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。
如果希望提供 RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration 
(interceptors, formatters, view controllers, and other features), you can add your own 
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide 
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or 
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

我们来仔细对照,看一下它怎么实现的,它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?

9.2、ContentNegotiatingViewResolver 内容协商视图解析器

自动配置了 ViewResolver ,就是我们之前学习的 SpringMVC 的视图解析器。

即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。

我们去看看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法:

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
  ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
  resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
  // ContentNegotiatingViewResolver uses all the other view resolvers to locate
  // a view so it should have a high precedence
  // ContentNegotiatingViewResolver 使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级.
  resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
  return resolver;
}

我们可以点进这类看看!找到对应的解析视图的代码:

@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {
        // 获取候选的视图对象.
        List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
        // 选择一个最适合的视图对象,然后把这个对象返回.
        View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
 		// ......
}

我们继续点进去看,他是怎么获得候选的视图的呢?

getCandidateViews 中看到他是把所有的视图解析器拿来,进行while循环,挨个解析!

Iterator var5 = this.viewResolvers.iterator();

所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的

我们再去研究下他的组合逻辑,看到有个属性 viewResolvers,看看它是在哪里进行赋值的:

protected void initServletContext(ServletContext servletContext) {
    // 这里它是从 beanFactory 工具中获取容器中的所有视图解析器.
    // ViewRescolver.class 把所有的视图解析器来组合的.
    Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
    ViewResolver viewResolver;
    if (this.viewResolvers == null) {
        this.viewResolvers = new ArrayList(matchingBeans.size());
      // ......
 }

既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?

我们可以自己给容器中去添加一个视图解析器,这个类就会帮我们自动的将它组合进来,我们去实现一下。


测试代码:

  1. 我们去写一个视图解析器来试试:
package com.kuang.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

//如果,你想diy一些定制化的功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配!
//扩展 springmvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //ViewResolver 实现了视图解析器接口的类,我们就可以把它看作视图解析器。
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    //自定义了一个自己的视图解析器MyViewResolver。
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }

}
  1. 怎么看我们自己写的视图解析器有没有起作用呢?

我们给 DispatcherServlet 中的 doDispatch 方法加个断点进行调试一下,因为所有的请求都会走到这个方法中:

springboot3全栈开发pdf java spring boot开发_spring boot_25

  1. 我们启动我们的项目,然后随便访问一个页面,看一下Debug信息:

找到this:

springboot3全栈开发pdf java spring boot开发_学习_26

找到视图解析器,我们看到我们自己定义的就在这里了:

springboot3全栈开发pdf java spring boot开发_spring boot_27

所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了!

9.3、转换器和格式化器

在 WebMvcAutoConfiguration 中找到格式化转换器:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
  Format format = this.mvcProperties.getFormat();
  WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
      .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
  addFormatters(conversionService);
  return conversionService;
}

点击去:

private final Format format = new Format();

public Format getFormat() {
  return this.format;
}

可以看到在我们的Properties文件中,我们可以进行自动配置它!

如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:

# 自定义的配置日期格式化!
spring.mvc.date-format=

其余的就不一一举例了!

9.4、修改SpringBoot的默认配置

SpringBoot 在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的。如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

扩展使用SpringMVC:

官方文档如下:

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

我们要做的就是编写一个 @Configuration 注解类,并且类型要为 WebMvcConfigurer,还不能标注@EnableWebMvc注解。

我们去自己写一个,我们新建一个包叫config,写一个类MyMvcConfig2:

package com.kuang.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//如果我们要扩展springmvc,官方建议我们这样去做!
@Configuration
//@EnableWebMvc   //这玩意就是导入了一个类:DelegatingWebMvcConfiguration:从容器中获取所有的WebMvcConfigurer;
public class MyMvcConfig2 implements WebMvcConfigurer {

    //视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/kuang").setViewName("test");
    }

}

我们去浏览器访问一下,查看结果!

确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保留SpringBoot所有的自动配置,也能用我们扩展的配置!

我们可以去分析一下原理:

  1. WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter。
  2. 这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)。
  3. 我们点进 EnableWebMvcConfiguration 这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration。

这个父类中有这样一段代码:

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

  // 从容器中获取所有的webmvcConfigurer.
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
  // ......
}
  1. 我们可以在这个类中去寻找一个我们刚才设置的 viewController 当做参考,发现它调用了一个addViewControllers() :
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
  this.configurers.addViewControllers(registry);
}
  1. 我们点 addViewControllers() 进去看一下:
@Override
public void addViewControllers(ViewControllerRegistry registry) {
  for (WebMvcConfigurer delegate : this.delegates) {
    // 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的.
    delegate.addViewControllers(registry);
  }
}

所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用。

9.5、全面接管SpringMVC

官方文档:

If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.

全面接管即:SpringBoot 对 SpringMVC 的自动配置不需要了,所有都是我们自己去配置!

只需在我们的配置类中要加一个 @EnableWebMvc

我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下,查看结果!

不加注解之前,访问首页,测试结果有效!

给配置类加上注解:@EnableWebMvc,我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子。

当然,我们开发中,不推荐使用全面接管SpringMVC。

思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:

  1. 这里发现它是导入了一个类,我们可以继续进去看:
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
  1. 它继承了一个父类 WebMvcConfigurationSupport:
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
  1. 我们来回顾一下 WebMvcAutoConfiguration 自动配置类 :
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效.
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebMvcAutoConfiguration {
  
}

总结一句话:@EnableWebMvc 将 WebMvcConfigurationSupport 组件导入进来了.

而导入的 WebMvcConfigurationSupport 只是 SpringMVC 最基本的功能!