Spring Boot
“what?”, “how?” and “why?”
1. what?
1.1 什么是Spring Boot?
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".
We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.
Spring Boot使您可以轻松地创建独立的、生产级的、基于Spring的应用程序,您可以“只是运行”。
我们对Spring平台和第三方库有一个独到的见解,这样您就可以从最少的麻烦开始了。大多数Spring引导应用程序需要很少的Spring配置。
Features
Create stand-alone Spring applications
Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
Provide opinionated 'starter' dependencies to simplify your build configuration
Automatically configure Spring and 3rd party libraries whenever possible
Provide production-ready features such as metrics, health checks, and externalized configuration
Absolutely no code generation and no requirement for XML configuration
特征
创建独立的Spring应用程序
直接嵌入Tomcat、Jetty或Undertow(不需要部署WAR文件)
提供自以为是的“starter”依赖项以简化构建配置
尽可能自动配置Spring和第三方库
提供生产就绪功能,如度量、运行状况检查和外部化配置
完全没有代码生成,也不需要XML配置
1.2 约定优于配置
约定由于配置(Convention over Configuration),又称按约定编程,是一种设计范式。
本质上是说,系统、类库或框架应该假定合理的默认值,而非要求提供不必要的配置。比如模型中有一个名为User的类,那么数据库中对应的表名默命名user。只有在偏离这一约定的时候,例如想要将表名命名为person,才需要写有关这个名字的配置。
比如项目架构师搭建项目就是限制软件开发随便写代码,在开发前,定制一套统一的规范,让开发人员按照统一的要求进行开发、编码、测试之类的,这样就加强了开发效率和审查代码效率。所以说写代码的时候就需要按照要求进行命名,这样统一规范的代码就有良好的可读性和维护性了。
约定优于配置简单来说就是遵循约定
2. how?
2.1 基础功能
2.1.1 相关说明
功能 | 说明 |
---|---|
Maven | 3.3+ |
JDK | 1.8+ |
Spring Boot | 2.1.14.RELEASE |
Spring Framework | 5.1.15.RELEASE |
2.1.2 构建 Spring Boot 项目
2.1.2.1 pom文件说明
添加 web 环境依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springboot-learn</artifactId>
<version>1.0-SNAPSHOT</version>
<description>基于Spring Boot的案例</description>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.14.RELEASE</version>
</parent>
<dependencies>
<!-- spring boot web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.1.2.2 项目结构说明
2.1.2.3 创建Controller
// @RestController 为组合注解,等同于 Spring中的:@Controller + @ResponseBody
@RestController
public class LearnController {
@RequestMapping("/hello")
public String hello() {
return "你好 Spring Boot";
}
}
2.1.2.4 编写启动主程序类
@SpringBootApplication
public class LearnApplication {
public static void main(String[] args) {
SpringApplication.run(LearnApplication.class, args);
}
}
问题:启动主程序类的位置是否可以随便放?
2.1.2.5 运行项目
运行项目主程序启动类LearnApplication类,启动成功后,可以看见默认端口为8080
页面输出为 “你好 Spring Boot”。
至此,构建Spring Boot项目就完成了。
2.1.3 单元测试
开发中,每当完成一个功能接口或业务方法编写后,通常需要借助单元测试验证功能是否正确。
Spring Boot对单元测试提供了很好的支持,在使用是,直接在pom.xml文件中添加
spring-boot-starter-test测试依赖启动器,可以通过相关注解实现单元测试。
2.1.3.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.1.3.2 编写测试代码
// 测试启动类,并加载Spring Boot测试注解
@RunWith(SpringRunner.class)
// 标记为Spring Boot单元测试类,并加载项目的ApplicationContext上下文环境
// classes 知道项目主程序启动类
@SpringBootTest(classes = LearnApplication.class)
public class LearnApplicationTest {
@Autowired
private LearnController learnController;
@Test
public void test() {
String hello = learnController.hello();
System.out.println(hello);
}
}
2.1.4 热部署
在开发过程中,通常会对一段业务代码不断的操作修改,在修改之后往往要重启服务,有些服务需要加载很久才能启动成功,这种不必要的重复操作极大的降低了程序开发效率。为此,Spring Boot框架专门提供了进行热部署的依赖启动器,用于进行项目热部署,无需手动重启项目。
2.1.4.1 添加依赖
<!-- 热部署依赖启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
2.1.4.2 IDEA工具设置
选择IDEA工具界面【File】-【Settings】,打卡Compiler界面,勾选【Build project automatically】
点击 Apply 应用,以及OK保存。
在项目任意界面使用组合快捷键【Ctrl+Alt+Shift+/】打开Maintenance选项框,选中并打开Registry页面
列表中找到【compiler.automake.allow.when.app.running】,将该值Value进行勾选。用于指定IDEA工具在程序运行过程中自动编译,最后单击【Close】按钮完成设置
2.1.4.3 效果测试
启动项目,访问 http://localhost:8080/hello
修改 “你好” 为 “hello”
为了测试热部署的是否有效,接下来,在不关闭项目的情况下,将LearnController类中数据修改,并保存,查看控制台会发现项目能够自动构建和编译,说明热部署生效
2.1.5 全局配置文件
全局配置文件能够对一些默认的配置值进行修改。Spring Boot使用一个application.properties或application.yml/application.yaml的文件作为全局配置文件,该文件放在 【src/main/resources】目录或者类路径的 【/config】,
一般为放在resources目录。
我们可以在 application.properties / application.yml文件中定义Spring Boot定义项目的相关属性,当然,这些属性可以是系统属性、环境变量、命令参数等信息,也可以是自定义配置文件名称和位置。
# 指定项目运行端口
server.port=8080
# 指定项目应用上下文路径
server.servlet.context-path=/learn
# 指定项目名
spring.application.name=learn
2.1.5.1 配置自动提示
编写配置时,由于我们配置的Person对象属性是我们自定义的,Spring Boot 无法自动识别,所有不会有任何属性提示。在实际开发中,为了出现代码提示的效果方便配置,在使用@ConfigurationProperties注解进行配置文件属性注入时,可以在pom.xml文件中添加Spring Boot提供的配置处理依赖器。
<!-- 配置处理器依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
添加配置后,需要重新编译、运行项目即可生效。
2.1.5.2 演示准备
首先准备两个实体类:Pet、Person,下面通过application.properties和application.yml配置文件中的自定义配置属性注入到Person实体类中
@Data
public class Pet {
private String type;
private String name;
}
@Data
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private int id; // id
private String name; // 名称
private List hobby; // 爱好
private String[] family; // 家庭成员
private Map map;
private Pet pet; // 宠物
}
@ConfigurationProperties(prefix = "person")注解的作用是将配置文件中以person开头的属性值通过setXXX()方法注入到实体类中
@Component注解的作用是将当前注入属性值的Person类对象作为Bean组件放到Spring容器中,只有这样才能被@ConfigurationProperties赋值
2.1.5.3 application.properties
person.id=1
person.name=张三
person.hobby=吃饭,睡觉,打豆豆
person.family=father,mother
person.map.k1=v1
person.map.k3=v2
person.pet.type=cat
person.pet.name=麻花
测试类
// 测试启动类,并加载Spring Boot测试注解
@RunWith(SpringRunner.class)
// 标记为Spring Boot单元测试类,并加载项目的ApplicationContext上下文环境
// classes 知道项目主程序启动类
@SpringBootTest(classes = LearnApplication.class)
public class LearnApplicationTest {
@Autowired
private Person person;
@Test
public void testProperties() {
System.out.println(person);
}
}
打印结果
Person(id=1, name=张三, hobby=[吃饭, 睡觉, 打豆豆], family=[father, mother], map={k1=v1, k3=v2}, pet=Pet(type=cat, name=麻花))
可以看出,正确打印出了Person类对象,说明application.properties配置文件属性配置正确,并通过相关注解自动完成了属性注入。
2.1.5.4 application.yaml
yaml文件格式是Spring Boot支持的一种JSON超集文件格式,相比传统的properties配置文件,yaml文件以数据为核心,是一种更为直观且容易被电脑识别的数据序列化格式。application.yaml配置文件的工作原理和application.properties是一样的,只不过yaml格式配置文件看起来更加简洁一些。
- yaml文件看扩展名可以为 .yaml或 .yml
- application.yml文件使用 “key:(空格)value” 格式配置属性,使用缩进控制层级关系
针对不同的数据类型有不同的格式
-
value值为普通数据类型(数字,字符串、布尔等)
server: port: 8081 spring: application: name: demo
-
value值为数组和单列集合
主要有两种书写方式:缩进式写法和行内写法
-
缩进式写法
person: hobby: - 吃饭 - 睡觉 - 打豆豆
或
person: hobby: 吃饭, 睡觉, 打豆豆
-
行内写法
person: hobby: [吃饭,睡觉,打豆豆]
或
person: hobby: 吃饭,睡觉,打豆豆
-
-
value值为Map集合和对象
主要有两种书写方式:缩进式写法和行内写法
-
缩进式写法
person: map: k1: v1 k2: v2
-
行内式写法
person: map: {k1: v1-1,k2: v2-2}
-
测试
在resources下创建 application.yml
person:
id: 1
name: 罗杰
hobby: [吃饭,睡觉,打豆豆]
family: father,mother
map: {k1: v1-1,k2: v2-2}
pet:
type: cat
name: 麻花
测试类
// 测试启动类,并加载Spring Boot测试注解
@RunWith(SpringRunner.class)
// 标记为Spring Boot单元测试类,并加载项目的ApplicationContext上下文环境
// classes 知道项目主程序启动类
@SpringBootTest(classes = LearnApplication.class)
public class LearnApplicationTest {
@Autowired
private Person person;
@Test
public void testProperties() {
System.out.println(person);
}
}
打印结果
Person(id=1, name=张三, hobby=[吃饭, 睡觉, 打豆豆], family=[father, mother], map={k1=v1, k3=v2}, pet=Pet(type=cat, name=麻花))
可以看出,正确打印出了Person类对象,说明application.yml配置文件属性配置正确,并通过相关注解自动完成了属性注入。
2.1.5.5 配置文件属性值注入
使用Spring Boot进行全局配置文件设置时:
- 如果配置的属性是Spring Boot已有属性,例如服务端口server.port,那么Spring Boot内部会自动扫描并读取这些配置文件中的属性值并默认覆盖。
- 如果配置的属性是用户自定义属性,例如刚刚自定义的Person实体类属性,还必须在程序中注入这些配置属性方可生效。
Spring Boot支持多种注入配置文件属性的方式,@ConfigurationProperties和@Value方式
2.1.5.5.1 @ConfigurationProperties
Spring Boot 提供 @ConfigurationProperties 注解用来快速、方便的将配置文件中自定义的属性批量注入到某个Bean对象的多个属性中。
如
@Data
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private int id; // id
private String name; // 名称
private List hobby; // 爱好
private String[] family; // 家庭成员
private Map map;
private Pet pet; // 宠物
}
上述代码使用 @Component 和 @ConfigurationProperties(prefix = "person")将配置文件中的每个属性映射到Person类组件中。
2.1.5.5.2 @Value
@Value是 Spring 框架提供的,用来读取配置文件中的属性值,并逐个注入到Bean对象的对应属性中,Spring Boot从Spring框架中对@Value注解进行了默认继承,所以在Spring Boot框架中还可以使用该注解读取和注入配置文件属性值。
如
@Data
@Component
public class People {
@Value("${people.id}")
private Integer id;
@Value("${people.name}")
private String name;
}
people:
id: 1
name: 李四
测试结果
People(id=1, name=李四)
可以看出,属性值正确打印,通过@Value可以进行配置文件属性值进行注入。
使用@Value注解使用注意事项
-
如果使用@Value,在配置文件中配置对应属性或者设置默认值,否则会出现异常
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'people.name' in value "${people.name}"
默认值设置方式示例
@Data @Component public class People { @Value("${people.id:10}") private Integer id; @Value("${people.flag:true}") private Boolean flag; // 设置默认值为空字符串 @Value("${people.name:}") private String name; // 设置默认值为null @Value("${people.remark:#{null}}") private String remark; }
-
@Value注解对Map集合、对象以及yml文件格式的行内式写法的配置文件的属性注入都不支持,如果赋值会出现错误
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'people.name' in value "${people.name}"
2.1.6 自定义配置
Spring Boot免除了项目中大部分的手动配置,对于一些特定情况,我们可以通过修改全局配置文件以适应具体生成环境,可以说,几乎所有的配置都可以写在application.properties配置文件中,Spring Boot会自动加载全局配置文件从而免除我们手动加载的烦恼。
但是,如果我们自定义配置文件,Spring Boot是无法识别这些配置文件的,此时需要我们手动加载。
2.1.6.1 @PropertySource
对应这种加载自定义配置文件的需求,可以使用@PropertySource注解结合@Component注解的方式来实现。
@PropertySource注解作用是用于指定自定义配置文件的具体位置和名称,同时,为了保证Spring Boot能够扫描该注解,还需要在其类上添加@Component,表明该类交于Spring容器进行维护。
当然对于自定义配置文件中的属性值注入到对应的类属性值中,可以使用@ConfigurationProperties或者@Value注解进行属性值注入
示例
-
创建test.properties
test.id=1 test.name=zhangsan
-
创建配置类
@Data @Component // 引入自定义配置文件的名称和位置 @PropertySource(value = "classpath:test.properties", encoding = "UTF-8") @ConfigurationProperties(prefix = "test") public class MyProperties { private int id; private String name; }
-
测试
// 测试启动器,并加载Spring boot 测试注解 @RunWith(SpringRunner.class) // 标记该类为Spring boot 单元测试类,并加载项目的ApplicationContext上下文环境 @SpringBootTest class SpringbootDemoApplicationTests { @Autowired private MyProperties myProperties; @Test void contextLoads() { System.out.println(myProperties); } }
2.1.6.2 @Configuration
在Spring Boot框架中,推荐使用配置类的方式向容器中配置和组件
在Spring Boot框架中,通常使用 @Configuration 注解定义一个配置类,Spring Boot 会自动扫描和识别配置类,从而替换传统Spring框架中的XML配置文件。
当定义一个配置类后,还需要在类中的方法上使用@Bean注解进行组件配置,将方法的返回对象注入到Spring容器中,并且组件名称默认为方法名,当然也可以使用@Bean注解的name、value属性自定义组件的名称。
-
创建Config配置类
// 标明该类为配置类 @Configuration public class MyConfig { @Bean // 将返回值对象作为组件,添加到Spring容器中,标识id默认为方法名或者自定义@Bean(id) public MyService myService() { return new MyService(); } }
-
测试
// 测试启动器,并加载Spring boot 测试注解 @RunWith(SpringRunner.class) // 标记该类为Spring boot 单元测试类,并加载项目的ApplicationContext上下文环境 @SpringBootTest class SpringbootDemoApplicationTests { @Autowired private ApplicationContext applicationContext; @Test void contextLoads() { boolean myService = applicationContext.containsBean("myService"); System.out.println(myService); } }
2.1.7 随机数设置
在Spring Boot配置文件中,随机值设置使用到了Spring Boot内嵌的RandomValuePropertySource类,对一些隐秘属性值或者测试用例属性值进行随机值注入
随机值设置的语法格式为 ${random.xx},xx标识需要指定生成的随机数类型和范围,可以是整数,uuid,或者字符串
@Data
@Component
@ConfigurationProperties(prefix = "myr")
public class MyRandom {
private String secret; // 配置随机值
private Integer number; // 配置随机整数
private Long bignumber; // 配置随机Long类型整数
private String uuid; // 配置随机uuid
private int lessthanten; // 配置小于10的随机整数
private int range; // 配置范围在[1024,5048]之间的随机整数
}
myr:
secret: ${random.value} # 配置随机值
number: ${random.int} # 配置随机整数
bignumber: ${random.long} # 配置随机Long类型整数
uuid: ${random.uuid} # 配置随机uuid
lessthanten: ${random.int(10)} # 配置小于10的随机整数
range: ${random.int[1024,5048]} # 配置范围在[1024,5048]之间的随机整数
2.1.8 参数间引用
在Spring Boot配置文件中,配置文件的 属性值还可以进行参数间的引用,也就是在后一个配置的属性值中引用先前已经定义多的属性,这样就可以直接解析其中的属性值了。
参数间引用的语法格式:${xxx},xxx表示先前在配置文件中已经配置过的属性名
好处:多处引用,一处配置
@Data
@Component
@ConfigurationProperties(prefix = "app")
public class App {
private String name;
private String describe;
}
app:
name: 测试APP
describe: ${app.name}是用来测试的
2.1.9 Profiles
在项目的开发中,有些配置文件在不同的环境(开发、测试、生成)中配置信息是不同的,例如数据库连接信息、Redis配置信息、日志控制级别等等都是不同的,那么就需要我们再项目中根据不同的环境配置不同的配置信息,做到不同的环境配置不同的配置,做到各个环境配置隔离。
在 Spring Boot 中多环境配置文件名需要使用 【application-{profile}.properties】或
【application-{profile}.yml】的格式,这里 {profile} 对应的是不同的环境标识。
如
application-test.properties 或 application-test.yml
application-prod.properties 或 application-prod.yml
需求:
测试环境服务端口:8081,生产环境端口:8082,如何进行实现
-
创建对应环境的配置文件
application-test.yml 和 application-prod.yml
application-test.yml配置文件内容:
server: port: 8081
application-prod.yml配置文件内容:
server:
port: 8082
-
application.yml激活对应环境配置
spring: profiles: active: prod
-
测试
java -jar learn.jar --spring.profiles.active=test
不同的环境启动脚本中激活不同的配置即可。
2.1.10 常用注解
注解 | 说明 |
---|---|
@SpringBootApplication | 组合注解:等价于@Configuration、@EnableAutoConfiguration、@ComponentScan |
@EnableAutoConfiguration | 启用自动配置功能 |
@ComponentScan | 组件扫描,可自动发现和装配一些Bean |
@Import | 导入其他配置类 |
@Bean | 相当于Spring中xml中的bean标签 |
@Configuration | 标记该类为配置类,等价于Spring中xml配置文件 |
@ConditionalOnMissingBean | 未在类路径下中找到对应Bean执行 |
@ConditionalOnBean | 在类路径下中找到对应Bean执行 |
@ConditionalOnProperty | 在全局配置文件中找到对应属性执行 |
@ConditionalOnMissingClass | 未在类路径下中找到对应的Class执行 |
@ConditionalOnClass | 在类路径下中找到对应的Class执行 |
@RestController | 组合注解,等价于@Controller、@ResponseBody |
@Autowired | Spring 依赖注入Bean,基于类型byType |
@Qualifier | 当容器中有多个同类型的Bean,通过该注解来指定,与@Autowired结合使用 |
@Service | 业务类 |
@Component | 组件类 |
@Repository | 数据访问层 |
2.1.11 优雅关闭
当线上某个应用需要升级部署时,常常简单粗暴地使用 kill 命令,这种停止应用的方式会让应用将所有处理中的请求丢弃,响应失败。这样的响应失败尤其是在处理重要业务逻辑时需要极力避免的,那么久需要平滑的关闭。
Spring Boot 框架提供健康监控依赖启动器,可以对Spring Boot服务进行监控,优雅停服等功能
2.1.11.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.1.11.2 添加配置
# 开启Spring Boot优雅关闭
management.endpoint.shutdown.enabled=true
# 暴露shutdown端点
management.endpoints.web.exposure.include=shutdown
# 自定义管理端点的前缀(保证安全)
management.endpoints.web.base-path=/me-actuator
# 自定义actuator端口
management.server.port=12581
# 不允许远程管理连接(不允许外部调用保证安全)
management.server.address=127.0.0.1
2.1.11.3 测试
执行 kill -9 / 或者 Ctrl+C操作,或者请求接口
http://localhost:12581/me-actuator/shutdown
返回数据
{"message":"Shutting down, bye..."}
说明优雅关闭成功
2.1.12 定时任务
Spring 框架自带任务调度功能,好比一个轻量级的Quartz,使用简单、方便,不需要依赖其他JAR包。
只需要在项目主程序启动类上添加@EnableScheduling开启任务调度功能即可
@SpringBootApplication
@EnableScheduling
public class LearnApplication {
public static void main(String[] args) {
SpringApplication.run(LearnApplication.class, args);
}
}
2.1.12.1 简单定时任务
@Component
public class TestTask {
@Scheduled(cron = "0/10 * * * * *")
public void testTask1() {
System.out.println("【任务一】测试定时任务" + LocalDateTime.now());
}
}
如上述,配置一个简单的定时任务只需要在调度方法上添加@Shceduled注解即可,就可以使用定时任务。
2.1.12.2 异步定时任务
@Component
// 开启异步支持
@EnableAsync
public class TestTask {
@Scheduled(cron = "0/10 * * * * *")
// 方法使用异步执行,每次任务创建一个线程执行任务
@Async
public void testTask1() {
System.out.println("【任务一】测试定时任务" + LocalDateTime.now() + " " + Thread.currentThread().getName());
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("【任务一】休眠" + (i + 1) + "秒测试定时任务" + LocalDateTime.now() + " " + Thread.currentThread().getName());
}
}
}
往往在我们的项目调度任务中,有的场景是需要在当前任务还没有执行完毕时,就需要执行下一个定时调度任务,在这种情况下需要使用异步的方式来执行定时任务。
@EnableAsync开启异步支持
@Async标记任务使用异步执行(下次任务将在下一个配置时间开始,不等待当前任务执行完毕)
2.1.12.3 动态定时任务
当我们编写定时任务是,流程大致为:编码->配置执行周期->启动服务。
当前我们配置的执行周期是每天早上8点执行,当我们有天,需求变更,需要每天晚上8点执行,我们的操作流程为:修改执行周期->新版打包->停服->启动新版服务。整个流程线步骤多,存在不可控因素。
那么我们怎么做到不停服更新我们的执行周期呢??
那么下面我们模拟将cron表达式存储在MySQL。
1)定义cron相关service
// 表达式相关接口
public interface SwitchService {
/**
* 获取最新 cron 表达式
*
* @param taskId 任务ID
* @return 最新 cron表达式
*/
String getCron(String taskId);
/**
* 修改 cron 表达式
*/
void modify();
}
// 表达式相关接口实现
@Service
public class SwitchServiceImpl implements SwitchService {
private static String DB_CRON = "";
@Override
public String getCron(String taskId) {
System.out.println("执行数据库查询 DB_CRON " + LocalDateTime.now());
return DB_CRON;
}
@Override
public void modify() {
DB_CRON = "0/20 * * * * *";
System.out.println("修改数据库中 DB_CRON " + LocalDateTime.now());
}
}
此处模拟修改以及查询
2)创建具体任务执行
@Component
public class DynamicCronTask implements SchedulingConfigurer {
// 模拟当前任务ID
private String TASK_ID = "5001";
@Autowired
private SwitchService switchService;
private String SpringDynamicCronTask() {
// 默认为 每5秒执行
String cron = "0/5 * * * * ?";
//从数据库获得配置的corn表达式
String dbCron = switchService.getCron(TASK_ID);
// 当查询为空时,使用默认的表达式
if (StringUtils.isNotBlank(dbCron)) {
return dbCron;
}
return cron;
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.addTriggerTask(new Runnable() {
@Override
public void run() {
// 任务逻辑
System.out.println("执行任务逻辑...." + LocalDateTime.now());
}
}, new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
String s = SpringDynamicCronTask();
// 任务触发,可修改任务的执行周期
CronTrigger trigger = new CronTrigger(s);
Date nextExec = trigger.nextExecutionTime(triggerContext);
return nextExec;
}
});
}
}
3)启动服务
查看执行日志
执行数据库查询 DB_CRON 2020-06-09T10:33:30.001
执行任务逻辑....2020-06-09T10:33:35.002
执行数据库查询 DB_CRON 2020-06-09T10:33:35.002
执行任务逻辑....2020-06-09T10:33:40.001
执行数据库查询 DB_CRON 2020-06-09T10:33:40.001
修改数据库中 DB_CRON 2020-06-09T10:33:42.085
执行任务逻辑....2020-06-09T10:33:45
执行数据库查询 DB_CRON 2020-06-09T10:33:45
执行任务逻辑....2020-06-09T10:34:00.001
执行数据库查询 DB_CRON 2020-06-09T10:34:00.001
执行任务逻辑....2020-06-09T10:34:20.002
执行数据库查询 DB_CRON 2020-06-09T10:34:20.002
通过日志可以看出,在应用启动时,会首先从数据库中查询配置的执行周期,然后执行定时任务,执行完毕后会再次查询执行周期,下一个执行时间结束后就会按照修改的执行时间执行。
生效时间为下一个执行时间结束后,做不到立即生效!!!
2.2 原理深入
传统的Spring框架实现一个WEB服务,需要导入各种依赖JAR包,然后编写对应的XML配置文件等,相比较而言,Spring Boot 显得更加方便、快捷、高效。那么Spring Boot 究竟是如何做到这些的呢?
2.2.1 依赖管理
问题:为什么Spring Boot导入dependency时不需要指定版本?
2.2.1.1 spring-boot-starter-parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.14.RELEASE</version>
</parent>
从上述可以看出,将spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理,并将项目版本号统一为2.1.14.RELEASE,该版本号可根据实际开发进行修改。
进入spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.14.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
继续进入spring-boot-dependencies底层源文件,其核心代码如下
<properties>
......
<jolokia.version>1.6.2</jolokia.version>
<jooq.version>3.11.12</jooq.version>
<jsonassert.version>1.5.0</jsonassert.version>
<json-path.version>2.4.0</json-path.version>
<jstl.version>1.2</jstl.version>
<jtds.version>1.3.1</jtds.version>
<junit.version>4.12</junit.version>
<junit-jupiter.version>5.3.2</junit-jupiter.version>
<kafka.version>2.0.1</kafka.version>
<kotlin.version>1.2.71</kotlin.version>
<lettuce.version>5.1.8.RELEASE</lettuce.version>
<liquibase.version>3.6.3</liquibase.version>
<log4j2.version>2.11.2</log4j2.version>
<logback.version>1.2.3</logback.version>
<lombok.version>1.18.12</lombok.version>
......
<spring.version>5.1.15.RELEASE</spring.version>
<spring-amqp.version>2.1.14.RELEASE</spring-amqp.version>
<spring-batch.version>4.1.4.RELEASE</spring-batch.version>
<spring-cloud-connectors.version>2.0.7.RELEASE</spring-cloud-connectors.version>
<spring-data-releasetrain.version>Lovelace-SR17</spring-data-releasetrain.version>
<spring-framework.version>${spring.version}</spring-framework.version>
<spring-hateoas.version>0.25.2.RELEASE</spring-hateoas.version>
<spring-integration.version>5.1.10.RELEASE</spring-integration.version>
<spring-kafka.version>2.2.13.RELEASE</spring-kafka.version>
<spring-ldap.version>2.3.3.RELEASE</spring-ldap.version>
<spring-plugin.version>1.2.0.RELEASE</spring-plugin.version>
<spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
<spring-retry.version>1.2.5.RELEASE</spring-retry.version>
<spring-security.version>5.1.10.RELEASE</spring-security.version>
<spring-session-bom.version>Bean-SR10</spring-session-bom.version>
<spring-ws.version>3.0.9.RELEASE</spring-ws.version>
<sqlite-jdbc.version>3.25.2</sqlite-jdbc.version>
......
</properties>
从底层spring-boot-dependencies底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如activemq、spring、lombok等,都有与2.1.14.RELEASE版本相匹配的版本,这也就是pom.xml引入依赖文件不需要标注依赖jar版本号的原因。
问题:spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来?
2.2.1.2 spring-boot-starter-web
查看 spring-boot-starter-web源文件源码
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.14.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.1.14.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.14.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.19.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.15.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.15.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
从上述代码可以看出,spring-boot-starter-web依赖启动器主要作用是提供了web开发场景需要的底层所有依赖。
正是因为如此,在pom.xml中引入 spring-boot-starter-web 依赖启动器时,就可以实现web场景开发,而不需要额外导入Tomcat服务器依赖以及其他web依赖文件。
当然,这些引入依赖的文件版本还是由 spring-boot-starter-parent进行统一管理。
2.2.1.3 starter
Spring Boot除了提供上述web依赖启动器以外,还提供了许多场景所需要的依赖
列出的是Spring Boot提供的一部分启动依赖器,还有许多,可以从spring官网上查看。不同的场景依赖,我们可以根据我们不同的业务场景,直接在pom.xml中引入即可。
但是Spring Boot官网并不是针对所有的场景的开发技术框架都提供了依赖启动器,如mybatis、druid等,但是为了充分利用Spring Boot框架的优势,mybatis、druid等技术框架团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器。mybatis-spring-boot-starter、druid-spring-boot-starter。在需要的时候直接在pom.xml文件中导入即可,但是需要自己管理版本号。
2.2.2 自动配置
能够在我们添加jar包依赖时,自动为我们进行配置一下配置,我们可以不需要配置或者少量配置就能运行编写的项目。
问题:Spring Boot到底是如何进行自动配置的,都把那些组件进行了自动配置?
2.2.2.1 @SpringBootApplication
Spring Boot 应用启动的入口是@SpringBootApplication注解标注类的main方法,
@SpringBootApplication能够扫描Spring组件并且自动配置Spring Boot
@SpringBootApplication
public class LearnApplication {
public static void main(String[] args) {
SpringApplication.run(LearnApplication.class, args);
}
}
@SpringBootApplication注解类
// 注解的适用范围:类、接口、枚举
@Target({ElementType.TYPE})
// 注解的生命周期:运行时
@Retention(RetentionPolicy.RUNTIME)
// 标明注解可标注在javadoc中
@Documented
// 标明注解可以被子类继承
@Inherited
// 标明该类为配置类
@SpringBootConfiguration
// 启动自动配置功能
@EnableAutoConfiguration
// 包扫描器
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
从上面可以看出,@SpringBootApplication注解主要由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个核心注解组成。
2.2.2.1.1 @SpringBootConfiguration
@SpringBootConfiguration 注解标明其类为Spring Boot配置类
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 配置到IOC容器
@Configuration
public @interface SpringBootConfiguration {
}
从上述可以看出,@SpringBootConfiguration注解类主要注解为@Configuration注解,该注解由Spring框架提供,表示当前类为一个配置类,并且可以被组件扫描器扫描。
2.2.2.1.2 @EnableAutoConfiguration
@EnableAutoConfiguration注解表示为自动配置类,该注解是Spring Boot最重要的注解,也是实现自动配置的注解。
@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 {};
}
从源码可以发现,@EnableAutoConfiguration注解为一个组合注解,其作用就是借助@Import注解导入特定场景需要向IOC注册的Bean,并且加载到IOC容器。@AutoConfigurationPackage就是借助@Import来搜集所有符合自动配置条件的Bean定义,并且加载到IOC容器中。
-
@AutoConfigurationPackage
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 导入Registrar中注册的组件 @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
从上述源码中可以看出@AutoConfigurationPackage注解的功能是有@Import注解实现的。@Import它是Spring框架底层注解,它的作用就是给容器导入某个组件类
Registrar类源码
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 将主程序类所在的包以及所有子包下的组件扫描到Spring容器
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
从上述可以看出,@AutoConfigurationPackage注解的主要作用就是将主程序类所在的包以及所有子包下的组件加载到IOC容器中。
因此:在定义项目包目录时,要求定义的包结构必须规范,项目主程序启动类要放在最外层的根目录位置,然后在根目录的位置内部建立子包和类进行业务开发,这样才能保证定义的类才能被组件扫描器扫描。
-
@Import({AutoConfigurationImportSelector.class})
将AutoConfigurationImportSelector类导入到Spring容器中。
AutoConfigurationImportSelector可以帮助 Spring Boot 应用将所有符合条件@Configuration的配置都导入到当前Spring Boot创建并使用的IOC容器(ApplicationContext)中。
// 自动配置的过程 public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); // 获取自动配置的配置类 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } } // 获取自动配置元信息 private AutoConfigurationMetadata getAutoConfigurationMetadata() { if (this.autoConfigurationMetadata == null) { // 加载自动配置元信息,需要传入beanClassLoader这个类加载器 this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); } return this.autoConfigurationMetadata; } // 获取自动配置的配置类 protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); // 从META-INF/spring.factories配置文件中将对于的自动配置类获取到 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
深入AutoConfigurationMetadataLoader.loadMetadata()方法
// 文件中需要加载的配置类的类路径
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
private AutoConfigurationMetadataLoader() {
}
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
// 读取spring-boot-autoconfigure-2.1.14.RELEASE.jar中spring-autoconfigure-metadata.properties的信息生成URL
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
深入**AutoConfigurationImportSelector.getCandidateConfigurations() **方法
这个方法有一个重要的loadFactoryNames方法,这个方法让SpringFactoriesLoader去加载一些组件的名字。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 这个方法需要两个参数,getSpringFactoriesLoaderFactoryClass()、getBeanClassLoader()
// getSpringFactoriesLoaderFactoryClass() 返回的:EnableAutoConfiguration.class
// getBeanClassLoader() 返回的:beanClassLoader类加载器
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
继续深入loadFactoryNames()方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 获取出的健
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 如果类加载器不为空,这加载类路径下的META-INF/spring.factories,将其中设置的配置类的类路径信息封装为Enumeration对象
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources("META-INF/spring.factories") :
ClassLoader.getSystemResources("META-INF/spring.factories"));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
会去读取一个 spring.factories 的文件,读取不到会报错
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
它其实是去加载一个外部的文件,而这个文件是在
@EnableAutoConfiguration 注解就是从classpath中搜寻META-INF/spring.factories配置文件,并将其org.springframework.boot.autoconfigure.EnableAutoConfiguration对于的配置通过反射实例化对应的标注了@Configuration的JavaConfig配置类,并且加载到IOC容器中。
以web项目为例,在项目中加入了web环境依赖启动器,对应的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration自动配置就会生效,打开自动配置就会发现,在配置类中通过全注解的方式对 Spring MVC 运行环境所需要环境进行了默认配置,包括前缀、后缀、试图解析器、MVC校验器等。
总结
Spring Boot 底层自动配置的步骤:
1)Spring Boot 应用启动
2)@SpringBootApplication 注解起作用
3)@EnableAutoConfiguration 注解起作用
4)@AutoConfigurationPackage 注解起作用
@AutoConfigurationPackage 这个注解主要作用就是@Import({AutoConfigurationPackages.Registrar.class}),它通过Registrar类导入容器中,而Registrar的作用就是将扫描主配置类的包以及子包,并将对应的组件导入IOC容器中。
5)@Import(AutoConfigurationImportSelector.class)
@Import(AutoConfigurationImportSelector.class) 它将 AutoConfigurationImportSelector 类导入容器中,AutoConfigurationImportSelector 类的作用是通过getAutoConfigurationEntry()方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有的jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给Spring Factory加载器进行一系列的容器创建过程。
2.2.2.1.3 @ComponentScan
@ComponentScan 注解具体扫描包的路径,由Spring Boot主程序所在包的位置决定。在扫描的过程中由@AutoConfigurationPackage注解进行解析,从而得到Spring Boot主程序类所在包的具体位置
2.2.3 自定义starter
Spring Boot由众多Starter组成(一系列的自动化配置的starter插件),Spring Boot之所以流行,也是因为starter。
starter是 Spring Boot非常重要的一部分,可以理解为一个可以插拔的插件,正是因为这些starter使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由 Spring Boot 自动通过classpath路径下的类发现需要的Bean,并织入对应的Bean。
例如,我们需要 Redis 插件,那么可以使用 spring-boot-starter-data-redis,如果需要MongoDB,那么可以使用spring-boot-starter-data-mongodb
2.2.3.1 为什么需要自定义starter
开发过程中,经常会有一些独立于业务之外的配置模块。如果我们将这些可以独立业务代码之外的功能配置封装成一个个的starter,复用的时候只需要将其在pom.xml中引用依赖即可,由Spring Boot帮我们完成自动装配。
2.2.3.2 自定义starter命名规则
Spring Boot提供的starter以 spring-boot-starter-xxxx 的方式命名的,官方建议自定义的starter使用
xxxx-spring-boot-starter 命名规则,以区分Spring Boot生态提供的starter。
2.2.3.3 自定义
2.2.3.3.1 custom-spring-boot-starter
-
新建Maven工程,工程命名custom-spring-boot-starter,导入依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>custom-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.1.14.RELEASE</version> </dependency> </dependencies> </project>
-
编写JavaBean
@EnableConfigurationProperties(CustomBean.class) @ConfigurationProperties(prefix = "custom") public class CustomBean { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
编写配置类
@Configuration // 当类路径classpath下有指定的类的情况下进行自动配置 @ConditionalOnClass public class CustomConfig { @Bean public CustomBean customBean() { return new CustomBean(); } }
-
resources下创建META-INF/spring.factories
注意:META-INF 目录和 spring.factories文件 需要手动创建
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
site.luojie.custom.config.CustomConfig
2.2.3.3.2 使用custom-spring-boot-starter
-
导入自定义starter依赖
<dependency> <groupId>org.example</groupId> <artifactId>custom-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
编写配置文件
custom.id=1 custom.name=测试
-
单元测试
@RunWith(SpringRunner.class) @SpringBootTest public class LearnApplicationTest { @Autowired private CustomBean customBean; @Test public void testCustomBean(){ System.out.println(customBean); } }
3. why?
3.1 为什么选择Spring Boot?
3.1.1 Spring MVC 优缺点
优点:
Spring是Java企业级(Java Enterprise Edition,JEE,也称 J2EE)的轻量级代替品。无需开发重量级的 Enterprise JavaBean(EJB),Spring作为企业级Java开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的 Java 对象(Plain Old Java Object,POJO)实现了EJB功能。
Spring核心:IOC、AOP
缺点:
虽然Spring的组件代码是轻量级的,但它的配置确实重量级的。一开始,Spring用XML配置,而且是很多的XML配置。Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件显示XML配置。Spring3.0引入了基于Java的配置,这是一种安全的可重构配置方式,可以代替XML。
所以这些配置代表了开发时的耗时。因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring实用,但与此同时它要求的回报也不少。
除此之外,项目的依赖管理也是一件耗时耗力的事情。在搭建环境时,需要分析要导入那些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。
- 重量级配置、配置繁琐
- 项目整合耗时耗力
3.1.2 Spring Boot 如何解决?
底部依赖
整合相关 starter。
常用starter
spring-boot-starter-web
spring-boot-starter-test
spring-boot-starter-data-redis
spring-boot-starter-tomcat
mybatis-spring-boot-starter
druid-spring-boot-starter
自动配置
自动注册相关Bean到IOC容器中,需要用时直接使用@Autowired
简单、快速、方便搭建项目,对主流开发框架无配置集成,极大提高了开发、部署效率