SpringBoot学习笔记01
1.Hello,World!
1.1 SpringBoot简介
回顾什么是Spring
Spring是一个开源框架,2003年兴起的轻量级Java开发框架,作者:Rod Johnson
Spring 是为了解决企业级应用开发的复杂性而创建的简化开发
Spring是如何简化Java开发的
为了简化java开发的复杂性,Spring采用了以下四种关键策略:
- 基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
- 通过IOC,依赖注入(DI)和面向接口实现松耦合;
- 基于切面(AOP)和惯例进行声明式编程
- 通过切面和模板减少样式代码,Redis Template, xxxTemplate;
什么是SpringBoot
学过Javaweb的同学都知道,开发一个web的应用,从最开始接触Servlet结合Tomcat,跑出一个HelloWorld程序,是要经历特别多的步骤,后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现
言归正传,什么是SpringBoot呢,就是一个JavaWeb的开发框架,和SpringMVC相似,对比其他Javaweb框架的好处,官方说是简化开发,约定大于配置,you can" just run",能迅速的开发web应用,几行代码开发一个http接口
所有的技术开发框架的发展似乎都遵循一条主线规律:从一个复杂应用场景衍生一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点,发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架,之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案
是的,这就是java企业级应用->J2EE->spring->springboot的过程
随着spring的不断发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地域,SpringBoot正式在这样的一个背景下被抽象出来的开发框架,目的让大家更容易的使用Spring,更容易的集成各种常用的中间件,开源软件
SpringBoot基于Spring开发,SpringBoot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速,敏捷地开发新一代基于Spring框架的应用程序,也就是说,它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具,SpringBoot以约定大于配置的核心思想,默默帮我们进行了很多设置,多数SpringBoot应用只需要很少的Spring配置,同时他集成了大量常用的第三方库配置,例如Redis, MongoDB, Jpa,RabbitMQ,Quartz等,SpringBoot应用中这些第三方库几乎可以零配置的开箱即用
简单来说SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,springboot整合了所有的框架
SpringBoot出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,springboot已经当之无愧成为Java领域最热门的技术
SpringBoot的主要优点
- 为所有的Spring开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化web项目
- 没有冗余代码生成和XML配置的要求
1.2 Hello, World
准备工作
将学习如何创建一个SpringBoot应用,并且实现一个简单的HTTP请求处理,通过这个例子对SpringBoot又一个初步的理解,并体验其结构简单,开发快速的特性
环境准备
- Java JDK: 1.8
- Maven: 3.5.0
- SpringBoot: 2.3.4
开发工具:
- IDEA
创建基础项目说明
Spring官方提供了非常方便的工具让我们快速构建应用
Spring initializr: https://start.spring.io/
项目创建方式一:使用Spring Initialier的web页面创建项目
- 打开https://start.spring.io/
- 填写项目信息
- 点击”GENERATE”按钮生成项目下载此项目
- 解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕
- 如果是第一次使用,包比较多,需要耐心等待一切就绪
项目创建方式二:使用idea直接创建项目
- 创建一个新项目
- 选择Spring Initalizr,可以看到默认就是去官网的快速构建工具那里实现
- 填写项目信息
- 选择初始化的组件(初学勾选web即可)
- 填写项目路径
- 等待项目构建成功
项目结构分析:
通过上面的步骤完成了基础项目的构建,就会自动生成以下文件
- 程序的主启动类
- 一个application.properties配置文件
- 一个测试类
- 一个pom.xml
pom.xml分析
打开pom.xml看看SpringBoot项目的依赖
<?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.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jcsune</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
编写一个http接口
- 在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到
- 在包中新建一个HelloController类
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello world";
}
}
- 编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了Tomcat访问的端口号
简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单,我们常用它来建立我们的微服务项目
将项目打包成jar包,点击maven的package
如果遇上以上的错误,可以配置打包时,跳过项目运行测试用例
<!--
在工作中,很多情况下我们打包是不想执行测试用例的
可能是测试用例不完事,或是测试用例会影响数据库数据
跳过测试用例执
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
如果打包成功,则会在target目录下生成一个jar包
打包成jar包后,就可以在任何地方运行了
如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是banner图案
只需一步:到项目下的resources目录下新建一个banner.txt即可,图案可以到:https://www.bootschool.net/ascii这个网站生成,然后拷贝到文件中即可
2.SpringBoot运行原理
上一节中创建的springboot项目,是怎么运行的呢,由于它同时也是一个maven项目,所以可以从pom.xml文件开始探究
pom.xml
父依赖
其实他主要是依赖一个父项目,主要是管理项目的资源过滤及插件
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
点进去发现还有一个父依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
这里才是真正管理springboot应用里面所有依赖版本的地方,springboot的版本控制中心;
以后导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了
启动器spring-boot-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring-boot-starter-xxx: 就是springboot的场景启动器
spring-boot-starter-web: 帮我们导入了web模块正常运行所依赖的组件
springboot将所有的功能场景都抽取出来,做成一个个的starter(启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来,我们用到什么功能就导入什么样的场景启动器即可,未来也可以自己定义starter
主启动类
分析完了pom.xml,来看看这个启动类
默认的主启动类
package com.jcsune;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//说明这是一个springboot应用
@SpringBootApplication
public class Springboot01Application {
public static void main(String[] args) {
SpringApplication.run(Springboot01Application.class, args);
}
}
但是一个简单的启动类并不简单,我们来分析一下这些注解都干了什么
@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}
)}
)
这个注解在spring中很重要,它对应XML配置中的元素
作用:自动扫描并加载符合条件的组件或者bean,将这个bean定义加载到IOC容器中
@SpringBootConfiguration
作用:springboot的配置类,标注在某个类上,表示这是一个springboot的配置类,我们继续进去这个注解查看
// 点进去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}
@Component
public @interface Configuration {}
这里的@Configuration,说明这是一个配置类,配置类就是对应Spring的xml文件
里面的@Component这就说明启动类本身也是Spring中的一个组件而已,负责启动应用!
@EnableAutoConfiguration
@EnableAutoConfiguration:开启自动配置功能
以前我们需要自己配置的东西,而现在springboot可以自动帮我们配置
@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效
点进注解继续查看
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
//继续进入@AutoConfigurationPackage注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@AutoConfigurationpackage:自动配置包
@Import :Spring底层注解@Import,给容器中导入一个组件
Registrar.class作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器;
这个分析完了,退到上一步,继续看
@Import({AutoConfigurationImportSelector.class}):给容器导入组件;
AutoConfigurationImportSelector:自动配置导入选择器,那么他会导入哪些组件的选择器呢?点击去这个类看源码
- 这个类中有一个这样的方法
// 获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//这里的getSpringFactoriesLoaderFactoryClass()方法
//返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
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类的静态方法,我们进入SpringFactoriesLoader类loadFactoryNames()
方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
//这里它又调用了 loadSpringFactories 方法
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
- 我们继续点击查看loadSpringFactories方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//去获取一个资源 "META-INF/spring.factories"
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
//将读取到的资源遍历,封装成为一个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 factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
- 发现一个多次出现的文件:spring factories,全局搜索他
spring.factories
根据源头打开spring.factories,看到了很多自动配置的文件;这就是自动配置根源所在
自动配置真正实现的是从classpath中搜寻所有的META-INF/Spring.factories配置文件,并将其中对应的org.springframework.boot.autoconfigure.包下的配置项,通过反射实例化为对应标注了@Configuration的JavaConfig形式的IOC容器配置类,然后将这些都汇总成为一个实例并加载到IOC容器中
结论
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
- 将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作
- 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中
- 它会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件
- 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作
SpringApplication
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
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();
}
3.yaml配置注入
3.1 yaml语法学习
配置文件
SpringBoot使用一个全局的配置文件,配置文件名称是固定的
- application.properties
- 语法结构:key=value
- application.yml
- 语法结构:key: 空格 value
配置文件的作用:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了
比如我们可以在配置文件中修改Tomcat默认启动的端口号
server.port=8081
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
yaml基础语法
说明:语法要求严格
- 空格不能省略
- 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的
- 属性和值得大小写都是十分敏感的
字面值: 普通的值[数字,布尔值,字符串]
字面量直接写在后面就可以,字符串默认不用加上双引号或者单引号;
k: v
注意:
- “ “双引号,不会转义字符串里面的特殊字符,特殊字符会作为本身想表示的意思
比如:name: “kuang \n shen” 输出:kuang 换行 shen
- ‘’ 单引号,会转义特殊字符,特殊字符最终会变成和普通字符一样输出
比如:name: ‘kuang \n shen’ 输出:kuang \n shen
对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
在下一行来写对象的属性和值得关系,注意缩进;比如
student:
name: jcsune
age: 3
行内写法
student: {name: jcsune,age: 3}
数组(List、set)
用-值表示数组中的一个元素,比如:
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
修改SpringBoot的默认端口号
配置文件中添加端口号的参数,就可以切换端口;
server:
port: 8082
3.2 注入配置文件
yaml文件更强大的地方在于他可以给我们的实体类直接注入匹配值
yaml注入配置文件
- 在springboot-02项目中的resources目录下新建一个文件application.yml
- 编写一个实体类Dog
package com.jcsune.springboot02.pojo;
import lombok.Data;
import org.springframework.stereotype.Component;
@Data
@Component //注册bean到容器中
public class Dog {
private String name;
private Integer age;
}
- 思考,我们原来是如何给bean注入属性值的!@Value, 给Dog类测试一下
@Component //注册bean到容器中
public class Dog {
@Value("旺财")
private String name;
@Value("3")
private Integer age;
}
- 在SpringBoot的测试类下注入测试一下
@SpringBootTest
class Springboot02ApplicationTests {
//将狗狗自动注入进来
@Autowired
Dog dog;
@Test
void contextLoads() {
//打印看下狗狗对象
System.out.println(dog);
}
}
结果成功输出,@Value注入成功,这是我们原来的方法
- 我们在编写一个复杂一点的实体类:Person类
//注册bean到容器中
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birthday;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
- 我们来使用yaml配置的方式来进行注入。写的时候注意区别和优势,我们来编写一个yaml配置!
person:
name: jcsune
age: 18
happy: yes
birth: 1997/06/28
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: 旺财
age: 1
- 刚才已经把person这个对象的所有值都写好了,现在就注入到我们的类中
//注册bean到容器中
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "peron")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birthday;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
- 此时我们还需要添加一个依赖
<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
- 确认以上配置都OK后,去测试类中测试一下
@Autowired
Person person;//将person自动注入进来
@Test
void contextLoads() {
//打印看下狗狗对象
System.out.println(person);//打印person信息
}
结果:所有值全部注入成功
至此,yaml配置注入到实体类完全OK