1 认识SpringBoot
1.1 介绍
SpringBoot的底层是Spring,SpringBoot是为了解决Spring整合开发过程中配置过于复杂而诞生的,它简化了Spring技术栈的开发,是一个开发Spring技术栈的快速开发的脚手架,也可以说是整合Spring技术栈的一站式框架。
SpringBoot的字面意思就是”Spring的开始”,也就是说,开发和使用Spring技术栈就从SpringBoot开始。
1.2 SpringBoot特性
优点:
- Create stand-alone Spring applications
- 创建独立Spring应用
- Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
- 内嵌web服务器(默认是Tomcat,不需要部署成war包了)
- Provide opinionated ‘starter’ dependencies to simplify your build configuration
- 通过选择‘started”来简化构建配置
- Automatically configure Spring and 3rd party libraries whenever possible
- 自动配置Spring和第三方功能
- Provide production-ready features such as metrics, health checks, and externalized configuration
- 提供生成级别的监控、健康检查及外部配置的特性
- Absolutely no code generation and no requirement for XML configuration
- 绝对的无代码生成和无需编写xml配置
缺点:
- 人称版本帝,版本发布频繁。
- 封装深,内部原理复杂
2 时代背景
2.1 微服务时代
James Lewis and Martin Fowler (2014) 提出微服务完整概念。https://martinfowler.com/microservices/
In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.-- James Lewis and Martin Fowler (2014)
微服务介绍:
微服务是一种架构风格,将一个应用拆分为多个小型服务,每个服务独立运行、部署和升级,服务之间通过轻量级HTTP交互。微服务根据业务拆分,可以由全自动部署机制独立部署。这种架构风格去中心化,每个服务可以使用不同的语言编写。
2.2 分布式
微服务架构涉及到分布式的部署方式。由此带来的困难:
远程调用、服务发现、负载均衡、服务容错、配置管理、服务监控、链路追踪、日志管理、任务调度、…
分布式的解决:
- SpringBoot + SpringCloud
2.3 云原生
简单理解就是充分利用云优势。原生应用如何上云。 Cloud Native。相对于本地而言的。
实现方式有以下几种。
3 第一个SpringBoot
参照官方文档:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/getting-started.html#getting-started-first-application
3.1 创建POM
使用IDEA创建简单MAVEN项目,增加。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
</parent>
3.2 增加starter依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3.3 编写代码
入口程序,@SpringBootApplication表示是一个SpringBoot应用。
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
编写组件:
@RestController
public class HelloController {
@RequestMapping("/hello")
public String test(){
return "hello, SpringBoot !";
}
}
3.4 运行
直接运行main函数,即可在浏览器中访问。
3.5 简化配置
SpringBoot集中管理配置,全在application.properties配置文件中管理,正所谓约定大于配置,配置文件的名字必须是这个。
#配置端口号为8888
server.port=8888
3.6 简化部署
可以直接打包成可执行jar包,
在SpringBoot之前,web应用需要打包成war包,在部署到Tomcat中。现在SpringBoot已经可以全部打包到一个jar包中,嵌入了Tomcat服务器,可直接运行。
配置并打包:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-51RAWTV4-1614423715460)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210226160357869.png)]
打开cmd运行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dmuH4EIa-1614423715465)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210226160632001.png)]
4 自动装配原理
4.1 依赖管理
父项目的依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
</parent>
spring-boot-starter-parent-2.4.3.pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.3</version>
</parent>
spring-boot-dependencies-2.4.3.pom:这里面包含了几乎所用到的依赖版本号。
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.3</version>
无需版本号,也可以在pom中加入想修改的版本号,如MySQL
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
4.2 场景启动器starter
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
针对于常用的Spring使用场景,SpringBoot都定义一个启动器,里面就是写好的依赖。
官方常用的starter都是spring-boot-starter-*的形式,常用starter见官网
4.3 自动配置
-
自动装配Tomcat
- spring-boot-dependencies-2.4.3.pom中可以看到加载了Tomcat的依赖。
-
自动装配SpringMVC
-
引入SpringMVC全套组件
-
通过getBeanDefinitionNames可以查看加载的组件,其中就有dispatcherServlet等。
-
String[] beanDefinitionNames = run.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); }
-
-
自动配置web常用功能
- 如字符编码characterEncodingFilter
-
默认的包结构
- 已经配置了扫描主程序同目录下的所有组件。
- 官方提供的默认包结构:
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q5ues02M-1614423715468)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210226191217563.png)]
-
各种配置拥有默认值
- 所有的配置都有默认值,在相对于的properties文件中。
- 需要修改配置值,就在application.properties中重新配置即可。
-
按需加载自动配置项
- 根据应用场景,会按需自动加载该场景下的依赖包。
- SpringBoot的所有的自动装配功能都在spring-boot-autoconfigure包里。
- 每个xxxautoConfiguration类都是一个配置类。
5 容器中注解
5.1 @Configuration
- 放在类上代表该类是个配置类,相当于xml配置文件。
- 该类也会当做一个组件加入到容器中。
- 新增了proxyBeanMethods的变量
- full模式(proxyBeanMethods = true) 内部的bean相当于是单例模式,默认模式
- Lite模式 (proxyBeanMethods = false) 内部的bean相当于原型模式。
- @Bean 注解的方法名就是组件ID,返回类型就是组件的类型,返回值就是容器中对象。
@Configuration(proxyBeanMethods=true)
public class UserConfig {
@Bean
public User getUser(){
return new User("Bob", 12);
}
@Bean
public Pet getPet(){
return new Pet("Tomcat");
}
}
5.2 @Import
该注解的参数就是class类型,给容器中自动创建class类型的组件,组件ID就是全类限定名。
用法想示例所示
@Import({User.class, Pet.class})
User.class的ID就是com.zl.boot.pojo.User
5.3 @Conditional
条件注解,一般都会是它的子注解,条件注解指定需要满足某些条件,组件才会注入容器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4KvxQGKs-1614423715471)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210226205406906.png)]
比如@ConditionalOnBean表示某个对象在容器中存在,才会注入。
@Configuration
public class UserConfig {
@Bean
@ConditionalOnBean(name = "Tom") //表示必须有ID为“TOM”的组件,才会将该组件注入到容器。
public User getUser(){
return new User("Bob", 12);
}
@Bean("Tom")
public Pet getPet(){
return new Pet("Tomcat");
}
}
5.4 @ImportResource
该注解作用就是导入xml配置文件。
比如导入bean.xml配置文件:
@Configuration
@ImportResource("classpath:bean.xml")
public class UserConfig {
...
}
5.5 @ConfigurationProperties
将配置文件中的属性值注入到组件中。
有两种用法:
-
@Component + @ConfigurationProperties
-
组件必须注入,才能将配置文件中的值注入到该组件中
-
@Component @ConfigurationProperties(prefix = "mycar") public class Car { private String brand; private int price; public void setBrand(String brand) { this.brand = brand; } public void setPrice(int price) { this.price = price; } } application.properties文件中 mycar.brand=BM mycar.price=10000
-
这样就可以在配置文件配置组件属性的值了。
-
-
@Configuration + @EnableConfigurationProperties + @ConfigurationProperties
-
该方法会直接将相应的类型的组件加入到容器中,并配置属性,而无需将Car.class单独注入容器中。
-
@Configuration @EnableConfigurationProperties(Car.class) public class UserConfig { ... } //@Component 不需要手动注入 @ConfigurationProperties(prefix = "mycar") public class Car { private String brand; private int price; public void setBrand(String brand) { this.brand = brand; } public void setPrice(int price) { this.price = price; } }
-
该方法对于第三方的类很有用。
-
6 自动配置源码分析
6.1 @SpringBootApplication
@SpringBootApplication注解的结构图:
@SpringBootApplication
- @SpringBootConfiguration
- @Configuration – 说明SpringBootApplication也是个配置类
- @EnableAutoConfiguration
- @AutoConfigurationPackage
- @Import({Registrar.class}) – 注入Registrar.class
- @Import({AutoConfigurationImportSelector.class}) – 注入AutoConfigurationImportSelector.class
- @AutoConfigurationPackage
- @ComponentScan – 扫描
从结构中可以看出,@SpringBootApplication是一个配置类,并且导入了两个组件,Registrar.class和AutoConfigurationImportSelector.class。
6.2 为什么SpringBoot自动扫描主程序下的组件
分析Registrar.class
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
//metadata注解原信息
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//register
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
}
AnnotationMetadata metadata 是注解原信息,这个注解在入口程序MainApplication上,获得的包名是就是
MainApplication所在的包。
AutoConfigurationPackages.registe将该包名下的组件注入到容器中,这就解释了为什么SpringBoot会自动扫描MainApplication所在目录下的所有组件。
6.3 自动配置类的注入
分析AutoConfigurationImportSelector.class
这个类中的selectImports方法,返回哪些类是需要注入的。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
...
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
...
}
重点是这个方法:
getAutoConfigurationEntry(annotationMetadata)
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
...
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
...
}
这里重要是getCandidateConfigurations获取候选配置方法。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
return configurations;
}
接下来是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");
...
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
在这里可以看到: Enumeration urls = classLoader.getResources(“META-INF/spring.factories”);
说明会加载各个依赖包下META-INF/spring.factories这个配置文件。
特别是spring-boot-autoconfigure-2.4.3.jar这个包里面的META-INF/spring.factories配置文件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0P4TORlq-1614423715473)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210227130919515.png)]
可以看到,其中包含了大量AutoConfiguration结尾的包,这些包都是响应功能的配置类
但是这些配置类里的组件不会全部注入到容器中,而是按需注入,实现的方式是@Conditional注解。
6.4 配置类按需注入
比如AopAutoConfiguration配置类。这个配置类组件会注入容器,但是内部配置类就会根据条件是否注入。
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"auto"},
havingValue = "true",
matchIfMissing = true
)
public class AopAutoConfiguration {
...
@ConditionalOnClass({Advice.class})
static class AspectJAutoProxyingConfiguration {
...
}
...
}
AOP有这个注解@ConditionalOnClass({Advice.class}),代表必须存在org.aspectj.weaver.Advice组件才会在容器中注入AspectJAutoProxyingConfiguration这个配置类。
比如HttpEncodingAutoConfiguration配置类
@Configuration(
proxyBeanMethods = false
)
@EnableConfigurationProperties({ServerProperties.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
prefix = "server.servlet.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.RESPONSE));
return filter;
}
...
}
配置了@EnableConfigurationProperties({ServerProperties.class})注解,即绑定配置文件,在characterEncodingFilter中,使用的配置文件中值。
ServerProperties中有默认值,也会从application.properties中获取值,这样就可用通过配置application.properties文件来改变组件中的配置。
@ConditionalOnMissingBean条件
characterEncodingFilter方法中的这个注解也说明该组件注入时会先判断容器中是否已经存在该组件,存在就不注入。所以,用户可以在自己的配置类中注入characterEncodingFilter组件,就达到了自定义默认组件的功能。
比如用户注入一个bean
@Bean
public CharacterEncodingFilter myFilter(){}
6.5 总结
- SpringBoot会先加载所有的自动配置类(xxxAutoConfiguration)。 当然,有些自动配置类因为条件过滤而不加载。
- 每个加载的自动配置类会按需向容器中注入组件,即加入了相应的功能。如dispatcherServlet
- 自动配置类会默认绑定配置文件,默认配置又会读取application.properties文件中的属性,最后将属性注入到组件中
- 定制配置的方式
- 用户配置application.properties
- 用户自己注入组件
7 SpringBoot的最佳实践
7.1 引入场景依赖
- 根据需求选择starter引入场景依赖
- starter选择参照官网
7.2 查看自动配置了哪些
- 可以分析依赖包spring-boot-autoconfigure-2.4.3.jar
- 在application.properties中配置debug=true,会打印出(注入组件)Positive matches和(未注入组件)Negative matches
7.3 修改配置
-
在application.properties配置,配置项查看官网
-
可以分析自动配置类@EnableConfigurationProperties注解的xxxproperties类。
-
在自己的配置类中注入组件覆盖SpringBoot底层的默认组件
-
自定义器 XXXXXCustomizer
-
…
以上做完,就是开始编写自己的业务代码了。
8 开发小技巧
8.1 Lombok
自动添加get、set、构造等方法。适用于pojo类。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private int age;
private Pet pet;
}
8.2 log4j
使用注解可以快速引入日志打印
import lombok.extern.log4j.Log4j2;
@Configuration
@Log4j2
public class UserConfig {
@Bean
public User getUser(Pet pet){
User bob = new User();
log.info("create new User"); //直接使用
return bob;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EIN3rJcD-1614423715474)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210227184028834.png)]
8.3 dev-tools
官方提供的类似于热重启的开发者工具,比如在开发web中,就不需要重启整个web应用了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
IDEA中修改代码之后,点击Build —> Build Project ,快捷键Ctrl + F9
8.4 Spring Initialize
在IDEA中创建新项目时,可以使用Spring Initialize(项目创建向导)进行创建。如下图,可以勾选自己想要支持的业务功能。会自动编写pom文件及一些目录的创建。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bzMve46p-1614423715475)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210227185515977.png)]