SpringBoot学习笔记
1、简介
1.1、什么是Spring
Spring是一个开源框架,2003年兴起的一个轻量级的Java开发框架。
Spring是为了解决企业级应用开发的复杂性而创建的,简化开发
1.2、Spring是如何简化Java开发的
为了降低java开发的复杂性,Spring采用了以下4种关键策略:
- 基于POJO的轻量级和最小入侵式编程,所有的东西都是Bean
- 通过控制反转(IOC),依赖注入(DI)和面向切面编程(AOP)实现松耦合
- 基于切面(AOP)和惯例进行声明式编程;
- 通过切面和模板减少样式代码,Redis Template
1.3、什么是Spring Boot
Spring Boot就是一个JavaWeb的开发框架,简化开发,约定大于配置。
Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多的设置,多数Spring Boot应用只需要很少的Spring配置。
Spring Boot 同时集成了大量常用的第三方库配置(Redis,MongoDB,Jpa等)。
Spring Boot 其实并不是新的框架,它默认配置了很多框架的使用方式,就像Maven整合了所有的jar包,Spring Boot只是整合了所有的框架。
Spring Boot的优点:
- 使所有Spring开发者更快的入门
- 开箱即用,提供了各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
2、Hello World
// 相当于@Controller + @ResponseBody
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "Hello World";
}
}
3、原理初探
3.1、pom.xml
3.1.1、父依赖
pom.xml主要是依赖一个父项目,主要负责管理项目的资源过滤和插件。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
点进去,发现还有一个父依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.0</version>
</parent>
这里是管理Spring Boot应用里面所有依赖版本的地方。
以后导入依赖默认是不需要写版本的,除非要导入的包不在被管理的依赖中,这时候就需要手动配置版本,
3.1.2、启动器spring-boot-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring-boot-starter-xxx:就是spring-boot的场景启动器。
spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件。
3.2、主启动类
3.2.1、默认的主启动类
// @SpringBootApplication 来标注一个主程序类
// 说明这是一个Spring Boot应用
@SpringBootApplication
public class HelloSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(HelloSpringBootApplication.class, args);
}
}
3.2.2、@SpringBootApplication
作用:标注在某个类上,说明这个类是SpringBoot的主配置类,SpringBoot 就应该运行这个类的main方法来启动SpringBoot应用。
进入这个注解,会发现上面还有很多其他注解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
- 四个元注解:@Retention @Target @Document @Inherited;
java中元注解有四个: @Retention @Target @Document @Inherited;
@Retention:注解的保留位置
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target:注解的作用目标
@Target(ElementType.TYPE) //接口、类、枚举
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
@Document:// 说明该注解将被包含在javadoc中
@Inherited:// 说明子类可以继承父类中的该注解
-
@ComponentScan
这个注解在Spring中很重要,它对应XML配置中的元素。
作用:自动扫描并加载符合条件的组件或者bean,将这个bean定义加载到IOC容器中。
-
@SpringBootConfiguration
作用:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类。
进去这个注解看
@Configuration public @interface SpringBootConfiguration{} // 点进@Configuration @Component public @interface Configuration {}
这里的@Configuration,说明这是一个配置类,配置类就是对应Spring的xml配置文件;
里面的@Component说明,启动类本身也是Spring中的一个组件,负责启动应用。
-
**@EnableAutoConfiguration:开启自动配置功能 **
点进这个注解:
- @AutoConfigurationPackage:自动配置包
@Import({Registrar.class})public @interface AutoConfigurationPackage {}
-
@import:Spring底层注解,给容器中导入一个组件。
Registrar.class作用:将主启动类的所在包及包下面所有子包里面的所有组件(@Component、@Service、@Controller、@Repository)扫描导入到Spring容器。
-
@Import({AutoConfigurationImportSelector.class}):给容器导入组件
AutoConfigurationImportSelector.class:自动配置导入选择器,那么它会导入哪些组件的选择器呢?通过源码来看一下。
- 进入这个类,发现有getCandidateConfigurations方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations;}
- 这个方法又调用了SpringFactoriesLoader类的静态方法loadFactoryNames,进入这个方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoader == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());}
- 继续查看loadSpringFactories方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { Map<String, List<String>> result = (Map)cache.get(classLoader); if (result != null) { return result; } else { HashMap result = new HashMap(); try { Enumeration urls = classLoader.getResources("META-INF/spring.factories"); 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-boot-autoconfigure的jar包下
4、yaml
4.1、配置文件
Spring Boot 使用一个全局的配置文件,配置文件名称是固定的
- application.properties
- 语法结构:key=value
- application.yaml
- 语法结构:key: 空格 value
配置文件的作用:修改SpringBoot自动配置的默认值。因为Spring Boot在底层都已经自动配置好了。
4.2、yaml概述
YAML 是 "YAML Ain't a Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)
这种语言以数据为中心,而不是以标记语言为重点
yaml 和 xml 对比:
-
xml配置:
<server> <port>8081</port></server>
-
yaml配置
server: port: 8081
4.3、yaml基础语法
- 属性和值的大小写都是敏感的;
- 使用缩进表示层级关系,只要是左边对齐的一列数据都是同一层级的;
- 冒号后的空格不能省略;
#
表示注释。
yaml 支持三种数据结构:
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes)/ 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence)/列表(list)
- 纯量(scalars):单个的、不可再分的值(整型、时间、日期、null、字符串、浮点型、布尔型)
1、对象
对象的一组键值对,使用冒号结构表示(冒号后有空格)
port: 8001
另一种写法,将所有的键值对写成一个行内对象。
user: {name: gmt,age: 12}
2、数组
一组连词线开头的行,构成一个数组
- name- age- id
数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。
gmt: - age - id - phone
数组也可以采用行内表示法
gmt: [ name,age,phone ]
3、复合结构
对象和数组可以结合使用,形成复合结构
gmt: - age: 12 - id: 9527 - phone: 10086
4、纯量
纯量是最基本的、不可再分的值。
-
布尔值:
true
、false
-
null:用
~
表示 -
整数:例如
12
-
浮点数:例如
12.12
-
时间:采用
ISO8601
格式。例如iso8601: 2001-12-14t21:59:43.10-05:00
-
日期:采用复合
ISO8601
格式的年月日表示。例如:date: 1976-07-31
-
字符串:是最常见也是最复杂的一种数据类型
-
字符串默认不使用引号表示:
str: 这是一行字符串
-
如果字符串中包含空格或者特殊字符,需要放在引号中
str: '内容: 字符串'
单引号和双引号都可以使用,双引号不会对特殊字符转义:
str1: '内容\n字符串'str2: "内容\n字符串"
转为JavaScript如下:
{ str1: '内容\\n字符串', str2: '内容\n字符串' }
-
字符串可以写成多行,从第二行开始必须有一个单空格缩进。换行符会被转为空格。
str: 这是一段 多行 字符串
转为JavaScript如下:
{ str: '这是一段 多行 字符串' }
-
多行字符串可以使用
|
保留换行符,也可以是使用>
折叠换行this: | Foo Barthat: > Foo Bar
转为JavaScript如下:
{ this: 'Foo\nBar\n', that: 'Foo Bar\n' }
-
+
表示保留字符串末尾的换行,-
表示删除字符串末尾的换行s1: | Foo s2: |+ Foo s3: |- Foo
转为JavaScript如下:
{ s1: 'Foo\n', s2: 'Foo\n\n\n', s3: 'Foo' }
-
字符串之中可以插入HTML标记
message: | <p style="color: red"> 段落 </p>
转为JavaScript如下:
{ message: '\n<p style="color: red">\n 段落\n</p>\n' }
-
yaml 允许使用两个感叹号,强制转换数据类型
e: !!str 123
转化为JavaScript如下:
{ e: '123' }
5、引用
锚点&
和别名*
,可以用来引用。
defaults: &defaults adapter: postgres host: localhost development: database: myapp_development <<: *defaults test: database: myapp_test <<: *defaults
等同于如下代码
defaults: adapter: postgres host: localhost development: database: myapp_development adapter: postgres host: localhost test: database: myapp_test adapter: postgres host: localhost
& 用来建立锚点,<<表示合并到当前数据,*用来引用锚点
4.4、注入配置文件
yaml文件更强大的地方在于,它可以给我们的实体类直接注入匹配值。
1、注入配置文件
-
在SpringBoot项目中的resources目录下,新建一个application.yaml
-
编写一个实体类 Dog
package com.gmt.hellospringboot.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Component // 注册Bean@Data@NoArgsConstructor@AllArgsConstructorpublic class Dog { private String name; private Integer age;}
-
我们原来是如何给bean注入属性值的。@Value
@Component@Data@NoArgsConstructor@AllArgsConstructorpublic class Dog { @Value("大黄") private String name; @Value("12") private Integer age;}
-
在SpringBoot的测试类下注入Dog输出一下
@SpringBootTestclass HelloSpringBootApplicationTests { @Autowired Dog dog; @Test void contextLoads() { System.out.println(dog); }}
输出:
Dog(name=大黄, age=12)
-
编写Person类
@Component@Data@AllArgsConstructor@NoArgsConstructorpublic class Person { private String name; private Integer age; private Dog dog; private List<String> hobby; private Map<String,Object> maps; private Date birth;}
-
使用yaml配置的方式注入,编写application.yaml
名称要和Person类的变量名一致,不然会输出null
person: name: gmt age: 12 dog: name: 大黄 age: 11 hobby: - 唱 - 跳 - rap - 篮球 maps: {k1: v1, k2: v2} birth: 2000/01/01
-
将值注入到Person类中
@Component@Data@AllArgsConstructor@NoArgsConstructor@ConfigurationProperties(prefix = "person")/*@ConfigurationProperties作用:将配置文件中配置的每一个属性的值,映射到这个组件中;告诉SpringBoot将奔雷中的所有属性和配置文件中相关的配置进行绑定参数prefix = "person" :将配置文件中的person下的所有属性一一对应*/public class Person { private String name; private Integer age; private Dog dog; private List<String> hobby; private Map<String,Object> maps; private Date birth;}
-
IDEA 提示,springboot配置注解处理器没有找到,导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional></dependency>
-
测试
@SpringBootTestclass HelloSpringBootApplicationTests { @Autowired Person person; @Test void contextLoads() { System.out.println(person); }}
输出:
Person(name=gmt, age=12, dog=Dog(name=大黄, age=11), hobby=[唱, 跳, rap, 篮球], maps={k1=v1, k2=v2}, birth=Sat Jan 01 00:00:00 CST 2000)
2、加载指定配置文件
- @PropertySource:加载指定的配置文件
- @ConfigurationProperties:默认从全局配置文件中获取值
- 在resources目录下新建一个person.properties文件
name=五六七
- 将值注入到Person类中
@Component@Data@AllArgsConstructor@NoArgsConstructor// 加载指定的配置文件@PropertySource("classpath:person.properties")public class Person { @Value("${name}") private String name; private Integer age; private Dog dog; private List<String> hobby; private Map<String,Object> maps; private Date birth;}
通过@PropertySource("classpath:person.properties")加载指定的配置文件
通过@Value("${name}")和EL表达式将值注入到类中
- 测试
@SpringBootTestclass HelloSpringBootApplicationTests { @Autowired Person person; @Test void contextLoads() { System.out.println(person); }}
输出:
Person(name=五六七, age=null, dog=null, hobby=null, maps=null, birth=null)
4.5、功能对比图
@ConfigurationProperties | @Value | |
---|---|---|
功能 | 批量注入到配置文件中的属性 | 一个个指定 |
松散绑定(松散语法) | 支持 | 不支持 |
SpEL(Spring表达式语言) | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
@ConfigurationProperties
只需要写一次即可,@Value
则需要每个字段都添加。- 松散绑定:比如yaml中写的是last-name,这个和lastName是一样的。
- JSR303校验:这个就是我们可以在字段上增加一层过滤器校验,可以保证数据的合法性
- 复杂类型封装:yaml可以封装对象,使用@Value就不支持
5、JSR303校验
JSR303是Java EE6的一项子规范,在spring-boot中可以用@Validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。这里写个注解让name属性只能支持email格式。
- 首先在pom.xml中导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
- 在Person中增加注解
@Component@Data@AllArgsConstructor@NoArgsConstructor@ConfigurationProperties(prefix = "person")@Validated // 数据校验public class Person { @Email(message = "邮箱格式错误") private String name; private Integer age; private Dog dog; private List<String> hobby; private Map<String,Object> maps; private Date birth;}
若name属性不符合Email格式,则会抛出如下错误
Property: person.nameValue: gmtOrigin: class path resource [application.yaml] - 2:9Reason: 邮箱格式错误
Bean Validation中内置的constraint
6、多环境切换
我们在编写主配置文件的时候,文件名可以使application-{profile}.properties/yaml,用来指定多个环境版本
1、多配置文件
application-test.properties代表测试环境配置
application-dev.properties代表开发环境配置
application.properties代表默认开发环境配置
但是Spring Boot并不会直接启动这些配置文件,它默认使用application.properties主配置文件:
我们需要通过一个配置来选择需要激活的环境:
编写application.properties配置文件:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试#这时候启动程序,服务器端口就会变成8082spring.profiles.active=devserver.port=8080
application-test.properties配置文件:
server.port=8081
application-dev.properties配置文件:
server.port=8082
2、yaml的多文档块
server: port: 8080# 选择要激活哪个环境块spring: profiles: active: dev---server: port: 8081spring: config: activate: on-profile: dev---server: port: 8082spring: config: activate: on-profile: test
注意:如果yaml和properties同时配置了端口,并且没有激活其他环境,默认会使用properties配置文件
3、配置文件加载位置
Spring Boot启动会扫描一下位置的application.properties或者application.yaml文件作为Spring Boot的默认主配置文件:
optional:classpath:/
optional:classpath:/config/
optional:file:./
optional:file:./config/
optional:file:./config/*/
注意:优先级是从低到高的
file是指项目根路径,和src目录同级
classpath是指java或者resources目录下