SpringBoot学习
我的springboot版本:2.7.2
1、SpringBoot概述
1.1、回顾什么是Spring
Spring是一个开源框架,2003 年兴起的一个轻量级的 Java 开发框架,作者:Rod Johnson。
Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
1.2、Spring是如何简化Java开发的
为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
- 基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
- 通过IOC,依赖注入(DI)和面向接口实现松耦合;
- 基于切面(AOP)和惯例进行声明式编程;
- 通过切面和模版减少样式代码,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 的架构方式,把每个功能元素独立出来,把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些可以整合多个功能元素,所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。
这样做的好处是:
- 节省了调用资源;
- 每个功能元素的服务都是一个可替换的,可独立升级的软件代码;
- 高内聚(在划分模块时,要把功能关系紧密的放到一个模块中);低耦合(模块之间的联系越少越好,接口越简单越好);
如何构建微服务:
一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元就是一个功能元素, 它们各自完成自己的功能,然后通过http相互请求调用。比如一个电商系统,查缓存、连数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务共同构建了一个庞大的系统。如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。
但是这种庞大的系统架构给部署和运维带来很大的难度。于是,spring为我们带来了构建大型分布式微服务的全套、全程产品:
- 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用;
- 大型分布式网络服务的调用,这部分springcloud来完成,实现分布式;
- 在分布式中间,进行流式数据计算、批处理,我们有spring cloud data flow;
- spring为我们想清楚了整个开始构建应用到大型分布式应用全流程方案;
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/
- 打开 https://start.spring.io/;
- 填写项目信息;
- 点击”Generate Project“按钮生成项目,下载此项目;
- 解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕;
- 如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。
**项目创建方式二:**使用 IDEA 直接创建项目。
- 创建一个新项目;
- 选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现;
- 填写项目信息;
- 选择初始化的组件(初学勾选 Web 即可);
- 填写项目路径;
- 等待项目构建成功;
2.3、项目结构分析
项目结构分析:
通过上面步骤完成了基础项目的创建,就会自动生成以下文件:
- 程序的主启动类 HelloworldApplication;
- 一个配置文件 application.properties;
- 一个 测试类 HelloworldApplicationTests;
- 一个 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接口
- 在主程序的同级目录下,新建一个controller包。(一定要在同级目录下,否则识别不到)
- 在包中新建一个 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!";
}
}
- 编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台也输出了 Tomcat 访问的端口号!
简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单。所以我们常用它来建立我们的微服务项目!
2.5、打包项目
将项目打成jar包,点击 maven 的 package。
如果打包成功,则会在target目录下生成一个 jar 包。
打成了jar包后,就可以在任何地方运行了!
2.6、彩蛋
如何更改启动时显示的字符拼成的字母SpringBoot呢?也就是 banner 图案;
只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。
图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可!
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应用。
进入这个注解:可以看到上面还有很多其他注解!
@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 :自动配置导入选择器,那么它会导入哪些组件的选择器呢?
我们点击去这个类看源码:
- 这个类中有一个这样的方法:
//获得候选的配置
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;
}
- 这个方法还调用了 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());
}
- 我们继续点击查看 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);
}
}
}
- 然后我们搜索 spring.factories 这个文件。
spring.factories
我们根据源头打开 spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration 。
(我在spring.factories没找到,在下面org包中找到的)
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean。
所以,自动配置真正实现是从 classpath 中搜寻所有的 META-INF/spring.factories 配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
结论:
springboot所有自动配置都是在启动的时候扫描并加载: spring.factories。所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
- springboot在启动的时候,从类路径下/META-INF/spring. factories获取指定的值;
- 将这些自动配置的类导入容器,自动配置就会生效,帮我进行自动配置!
- 以前我们需要自动配置的东西,现在springboot帮我们做了!
- 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar这个包下;
- 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
- 容器中也会存在非常多的 xxxAutoConfiguration 的文件(@Bean),就是这些类给容器中导入了这个场景需要
的所有组件,并自动配置,@Configuration, JavaConfig! - 有了自动配置类,免去了我们手动编写配置文件的工作!
3.3、SpringApplication
最初以为就是运行了一个main方法,没想到却开启了一个服务!
SpringApplication.run分析:
分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行。
SpringApplication
这个类主要做了以下四件事情:
- 推断应用的类型是普通的项目还是Web项目。
- 查找并加载所有可用初始化器 , 设置到initializers属性中。
- 找出所有的应用程序监听器,设置到listeners属性中。
- 推断并设置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方法流程分析:
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]
说明:语法要求严格!
- 空格不能省略;
- 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的;
- 属性和值的大小写都是十分敏感的。
4.3、yaml注入配置文件
yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
- 在项目中的 resources 目录下新建一个文件 application.yaml 配置文件。
- 编写一个 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 +
'}';
}
}
- 在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);
}
}
- 查看结果,@Value注入成功!这是我们原来的办法。
- 我们再编写一个复杂一点的实体类: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 +
'}';
}
}
- 我们来使用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
- 我们刚才已经把person这个对象的所有值都写好了,然后通过上面代码中的以下操作来注入到我们的类中!
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应。
*/
@ConfigurationProperties(prefix = "person")
- IDEA 提示springboot配置注解处理器没有找到。我们点开查看文档,根据文档的提示,添加一个依赖!
<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
- 确认以上配置都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);
}
}
- 查看输出结果,所有值全部注入成功!
4.4、加载指定的配置文件
**@PropertySource :**加载指定的配置文件;
@configurationProperties:默认从全局配置文件中获取值;
- 我们去在resources目录下新建一个 kuang.properties文件。
name=kuangshen
- 然后在我们的代码中指定加载 kuang.properties 文件。
@Component
@PropertySource(value = "classpath:kuang.properties")
public class Person {
@Value("${name}") //SPEL表达式取出配置文件的值。
private String name;
......
}
- 再次测试,查看结果!指定配置文件绑定成功!
注意:
properties 配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8:
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 这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦,我们来看个功能对比图:
- @ConfigurationProperties只需要写一次即可,@Value则需要每个字段都添加;
- 松散绑定:比如我的yaml中写的 last-name,这个和 lastName 是一样的,后面跟着的字母默认是大写的。这就是松散绑定。
- JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性;
- 复杂类型封装,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;
......
}
查看运行结果 :
使用数据校验,可以保证数据的正确性;
注意:
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配置文件的!
配置文件加载位置
外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!
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 {
// .....
}
我们去配置文件里面试试前缀,看提示!
精髓:
- SpringBoot启动会加载大量的自动配置类;
- 我们看我们需要的功能有没有在 SpringBoot 默认写好的自动配置类当中;
- 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了);
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件;
xxxxProperties:封装配置文件中相关属性;
6.2、了解@Conditional
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效。
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置里面的所有内容才生效。
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效?
**我们可以通过启用 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目录结构:
只要是静态资源,SpringBoot就会去对应的路径寻找资源。
要访问Jquery.js文件,我们这里就访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js
第二种静态资源映射规则:
那我们项目中要是使用自己的静态资源该怎么导入呢?我们去上面所述代码中的看下一行代码。
我们进入 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 , 他就会去这些文件夹中寻找对应的静态资源文件:
通过测试,我们发现这几个位置的优先级为:
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,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢?我们来看一下这张图:
模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是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包,我们可以去看下下载的东西:
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 的官方文档!
测试代码:
- 编写一个 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";
}
}
- 我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。
xmlns:th="http://www.thymeleaf.org
- 编写一个测试页面 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>
- 启动项目测试,查看结果!
结论:只要需要使用 thymeleaf ,只需要导入对应的依赖,在html文件中导入命名空间的约束就可以了!我们将html放在我们的templates目录下即可。
OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!
- 我们可以使用任意的 th:attr 来替换html中原生属性的值!
- 我们能写哪些表达式呢?
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());
// ......
}
既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?
我们可以自己给容器中去添加一个视图解析器,这个类就会帮我们自动的将它组合进来,我们去实现一下。
测试代码:
- 我们去写一个视图解析器来试试:
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;
}
}
}
- 怎么看我们自己写的视图解析器有没有起作用呢?
我们给 DispatcherServlet 中的 doDispatch 方法加个断点进行调试一下,因为所有的请求都会走到这个方法中:
- 我们启动我们的项目,然后随便访问一个页面,看一下Debug信息:
找到this:
找到视图解析器,我们看到我们自己定义的就在这里了:
所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情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所有的自动配置,也能用我们扩展的配置!
我们可以去分析一下原理:
- WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter。
- 这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)。
- 我们点进 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);
}
}
// ......
}
- 我们可以在这个类中去寻找一个我们刚才设置的 viewController 当做参考,发现它调用了一个addViewControllers() :
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
- 我们点 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。
思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:
- 这里发现它是导入了一个类,我们可以继续进去看:
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
- 它继承了一个父类 WebMvcConfigurationSupport:
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
- 我们来回顾一下 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 最基本的功能!