本文将分享跟我极速尝鲜SpringBoot2.3的详细内容,此外,我们还将为大家带来关于(七)跟我学习SpringCloud-SpringBootStarter的介绍及使用、(五)跟我学习Spring
本文将分享跟我极速尝鲜 Spring Boot 2.3的详细内容,此外,我们还将为大家带来关于(七) 跟我学习SpringCloud-Spring Boot Starter的介绍及使用、(五) 跟我学习SpringCloud-Spring Boot简介、(六) 跟我学习SpringCloud-Spring Boot项目详细搭建步骤、(第五篇) 跟我学习SpringCloud-Spring Boot简介的相关知识,希望对你有所帮助。
本文目录一览:- 跟我极速尝鲜 Spring Boot 2.3
- (七) 跟我学习SpringCloud-Spring Boot Starter的介绍及使用
- (五) 跟我学习SpringCloud-Spring Boot简介
- (六) 跟我学习SpringCloud-Spring Boot项目详细搭建步骤
- (第五篇) 跟我学习SpringCloud-Spring Boot简介
跟我极速尝鲜 Spring Boot 2.3
Spring Boot 2.3 已经发布一个月了,这两天才想起来尝一尝鲜儿。除了常规的升级外,很大部分的升级是针对 Docker 的,让你不得不相信,Docker 容器化微服务已然大势所趋。还没有用过的同学,再不下手就晚了。
此次升级主要包括如下几个方面,接下来就跟着我一起来尝一尝吧。
准备工作
为了说明 Spring Boot 2.3 的新特性,必须创建一个项目,以便试验。
创建一个项目并启动
1、创建一个 Spring Boot 项目,可以到 http://start.spring.io/ 上创建,也可以使用 IDEA 自带的功能创建。选择版本 2.3.1,JDK 还是选择亲爱的 Java 8,引入 Web 和 Actuator 两个依赖包。
有一点要注意一下,在我写本文的时候,Spring Boot 2.3.1 还不能从中央仓库下载,需要添加 Spring Boot 官方的里程碑仓库。
<repositories>
<repository>
<id>spring-milestone</id>
<name>Spring Milestone Repository</name>
<url>http://repo.spring.io/milestone</url>
</repository>
</repositories>
2、在 pom 文件中引入 Maven 插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.1.RELEASE</version>
</plugin>
</plugins>
</build>
3、添加一个 Controller,做测试用。
@RestController
public class PlayController {
@GetMapping(value = "play")
public String play(){
return "hey, play with me!";
}
}
4、启动项目
mvn spring-boot:run
5、访问 http://localhost:8080/play,说明项目启动成功
更好的 Docker 支持
如果不使用 Docker 呢,那就直接打成 jar
包,使用如下命令
mvn package spring-boot:repackage
然后就可以把这个 Jar
包部署到服务器了,当然这个过程可能是用自动化部署工具实现的,不如 jenkins 或者自研系统。
之前 Docker 打包方式
抛开公司(尤其是大厂)里成熟的自动化部署流程不谈,我这里说的是一般性小厂或者是个人项目。
如果你在之前的版本就已经用 Docker 方式,那基本上都是自己写 Dockerfile ,然后自己写脚本使用 Dockerfile 打镜像包,或者使用 Maven
插件,比如 dockerfile-maven-plugin
。
Cloud Native Buildpacks
如果你了解 Dockerfiles 的话,那你肯定了解用 Dockerfiles 构建镜像的过程,需要你创建一个 Dockerfile 文件然后在里面写上构建镜像所需的一系列动作,而 Cloud Native Buildpacks 则无需配置类似的过程文件,很大程度上减轻了开发者的工作,提高了效率。这还不是最重要的,最重要的是它提供了更高层次的抽象能力,使镜像的分层更加清晰,并且合理有效的利用层缓存,这样一来,当我们对应用程序进行修改之后,再次构建镜像时的速度飞快,比如我们的应用只改了几行代码,那当我们使用 Buildpacks 构建镜像时,只需要在应用程序层进行重新构建,其他层使用缓存就可以,也就是只对变化了的层重新构建。
Spring Boot 2.3 Docker 方式
首先要确保你本地已经正常启动了 Docker 服务。
Spring Boot 2.3 官方的 Docker Maven 插件,从此不用再借助第三方了。我们前面创建项目的时候已经引入了这个 Maven 插件。
此插件不仅提供了打镜像包的功能,还有其他的常用功能,比如 run、repackage 等。
为什么前面要说 Cloud Native Buildpacks 呢,不是跑题啊,是因为 Spring Boot 2.3 生成 Docker 镜像包的方式就是集成了 Cloud Native Buildpacks。
那我们就打个镜像包试一下吧
mvn spring-boot:build-image
你以为马上就能看到成果了吗,还是太年轻。
大中华区开发者怎么了
对于中国的开发者来说,打包这一步不会太顺利,原因大家都很清楚。不出意外的话,应该会出现这样的错误,不出错可能才是意外。
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.3.1.RELEASE:build-image
(default-cli) on project play: Execution default-cli of goal org.springframework.boot:spring-boot-maven-plugin:
2.3.1.RELEASE:build-image failed: Docker API call to ''localhost/v1.24/images/create?fromImage=gcr.io%2Fpaketo-
buildpacks%2Fbuilder%3Abase-platform-api-0.3'' failed with status code 500 "Internal Server Error" and message
"Get http://gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while
awaiting headers)" -> [Help 1]
出现这个问题的原因是因为 Buildpacks 调用 Docker API 创建镜像的方法,要访问 http://gcr.io ,从上面 pull 一些基础镜像下来,这是 Google 的 Google Cloud ,是 Google 的容器仓库,然而对于中国的开发者来说,这个地址是 404 的。
所以我们要加个系统级别代理,或者专门为 Docker 配置代理。我是在 Docker 中配置的代理,系统代理的影响太大。我本机安装的是 Docker Desktop,直接打开设置,在里面加上代理就可以了(别问我代理怎么搞,问我就是没有代理)。
好了,通过上面一顿猛如虎的操作,再次运行命令
mvn spring-boot:build-image
根据你的网速,等上一段时间,就会出现下面的结果,说明镜像创建成功了。
之后你可以使用 docker images
命令查看。这时间也是醉了,40 years ago。
使用此镜像启动容器
使用命令直接启动容器。
docker run -it -p8080:8080 play:0.0.1-SNAPSHOT
然后访问 8080 端口,得到正确的返回结果,说明启动成功了。
Docker Image 的一个特点是,每个层都是前一层变化的增量。有一个工具叫做 dive,可以清楚的查看分层结构里面包含的内容。具体安装和使用请自行搜索。
使用 dive 查看的一个小技巧,因为镜像层包含的指令很多,所以我们选择只查看相对于上一层的增量内容,使用 Ctrl+L
组合键。
然后按 Tab
进入视图,然后按 Ctrl+U
,去掉没有更改的选项,也就是只看变化的部分。
然后上下箭头可以切换层查看,比如下面这个图展示了一个 18 M 的层相对于上一层的变化内容,可以看出来这个层实际上就是应用程序层,包含了很多当前应用程序的类和第三方依赖包等。
分层 jar 包
分层打包配置很方便,最简单的方式就是在 pom 文件中加上如下配置:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.1.RELEASE</version>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
加上分层配置之后,仍然使用常规的命令打包
mvn package spring-boot:repackage
分层打包其实和以前的打包方式没有什么不同,打出来的包几乎和之前是完全一样的,分层其实只是逻辑上的抽象而已。打出的 jar 包结构如下(jar 包其实就是个压缩包,可以解压缩查看目录结构)
在 jar 包的 BOOT-INF 目录下可以看到 classpath.idx
和layers.idx
两个文件,这两个就是为了分层 jar 的关键。
默认情况下会分成如下四个层。
-
dependencies
对版本没有要求的依赖包,也就是你的应用程序无论怎么改,都几乎不会影响的依赖包。
-
spring-boot-loader
Spring Boot 加载类。 -
snapshot-dependencies
对应用版本有要求的依赖包,比如应用升级后,可能同时需要升级的依赖包。 -
application
应用程序编译类和配置文件等。
在 layers.idx
可以看出这个分层结构,用普通的文本编辑器就可以打开,比如 sublime。打开之后看到这样一个类似于 yaml 的结构,四个层以及他们所指的目录都清晰的列出来了。
- "dependencies":
- "BOOT-INF/lib/"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/classpath.idx"
- "BOOT-INF/layers.idx"
- "META-INF/"
classpath.idx
文件列出了依赖的 jar 包列表,到时候会按照这个顺序载入。
- "spring-boot-starter-actuator-2.3.1.RELEASE.jar"
- "spring-boot-starter-2.3.1.RELEASE.jar"
- "spring-boot-2.3.1.RELEASE.jar"
- "spring-boot-autoconfigure-2.3.1.RELEASE.jar"
- "spring-boot-starter-logging-2.3.1.RELEASE.jar"
- "logback-classic-1.2.3.jar"
- "logback-core-1.2.3.jar"
- "log4j-to-slf4j-2.13.3.jar"
自定义分层结构
如果我们想要在默认的 4 层上增加新的分层,Spring Boot 2.3 也提供了定制分层的功能。配置也很简单,在 plugin
配置如下,指定了 layers.xml
作为自定义分层配置
<configuration>
<layers>
<enabled>true</enabled>
<configuration>${project.basedir}/src/layers.xml</configuration>
</layers>
</configuration>
layers.xml
的配置像下面这样
<layers xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
http://www.springframework.org/schema/boot/layers/layers-2.3.xsd">
<application>
<into layer="spring-boot-loader">
<include>org/springframework/boot/loader/**</include>
</into>
<into layer="application" />
</application>
<dependencies>
<into layer="snapshot-dependencies">
<include>*:*:*SNAPSHOT</include>
</into>
<into layer="dependencies" />
</dependencies>
<layerOrder>
<layer>dependencies</layer>
<layer>spring-boot-loader</layer>
<layer>snapshot-dependencies</layer>
<layer>application</layer>
</layerOrder>
</layers>
当你开启分层功能后,可以使用 -Djarmode
查看分层情况。
java -Djarmode=layertools -jar target/play-0.0.1-SNAPSHOT.jar list
显示的结果就是分层情况,比如默认情况下就是这样,列出了 4 个默认分层。
dependencies
spring-boot-loader
snapshot-dependencies
application
题外话:
Djarmode
其实就是个 Java-Agent
。
分层包的意义
说了半天分层包了,那分层包到底有啥用呢?
这么说吧,它其实是为了和 Docker 配合使用的,如果你不用 Docker 方式部署,还是用原始 jar 包的方式,可以说没什么用,如果非得说有什么用,那就是让你更加清楚项目的依赖情况。
分层包 和 Docker 结合
前面介绍 Docker 镜像包的时候说了 Buildpacks 可以让你的镜像分层清晰,而 Spring Boot 2.3 提供的分层 jar 功能可以在镜像分层的基础上更上一层楼,使分层更加清晰。
那我们开启分层配置,然后重新打个 Docker 镜像出来看一看。
mvn spring-boot:build-image
然后再使用 dive 工具看一下启用分层 jar 功能后的 Docker 镜像分层情况,是不是变得更好了。前面的层都是一样的,都是一些集成镜像和配置,从 18 MB 的这个层开始的 4 个层就是启用分层后的 4 个层,分别对应 dependencies、spring-boot-loader、snapshot-dependencies、application
比如这个 5.4K 的 application 层。
那这样做有什么好处呢,前面不是说了吗,Buildpacks 打镜像包会使用缓存的,如果这一层没变那就不用重新打这一层,只需要重新打包修改过的层,这样一来,如果你只修改了 application 中的内容,比如新加了 Controller 或者配置文件等,那么只需要重新打包这一层,也就是几 K,几十 K 不会很大,这样一来打包速度就很快了,要不然一个上百兆的镜像包也得需要一段时间。
优雅停机功能
什么叫优雅停机呢,假设这是一个分布式服务,其中一台服务所在的实体机需要打安全补丁,需要关机重启,那实体机关机之前要先把这个服务停掉。
关掉服务的方式,比如:
-
我不管,我就直接关实体机,至于服务,你命由我不由天。
-
也好办,kill -9 ,一行命令解决,也挺省心。
额,还行吧,但是有点儿问题,比如当前服务实例正在处理请求,还没处理完,你咔嚓一下就给它结束了,谁受得了,不要太刺激。
我们把前面的那个 Controller 中的 play
方法改一下,加一个延时,等待 6 秒才返回,模拟一个比较慢的请求。
@GetMapping(value = "play")
public String play() throws InterruptedException{
Thread.sleep(6000);
return "hey, play with me!";
}
效果就是你访问这个地址,然后等了 6 秒之后才显示出 hey, play with me!
。
如果在这 6 秒钟之内我杀掉了进程,将会在浏览器中出现下面这个讨厌的界面。
启用优雅关机
只需要在配置文件中增加 server.shutdown
的配置,一种是 immediate
,也就是立即停止,另一种就是所谓的优雅关机 graceful
。
server:
port: 8081
shutdown: graceful
# 缓冲10s,上面定义的那个方法延时 6秒,所以10秒肯定够了
spring:
lifecycle:
timeout-per-shutdown-phase: 10s
之后,再启动服务,然后访问这个页面,这个过程中结束进程。然后会看到控制台有输出,提示优雅关机的过程,并提示说会等待活动状态的请求处理完成。
请求也变得正常了。
活动状态检测
之前版本的 spring-boot-starter-actuator
就已经有健康状态检测了,不开启活性状态检测,当我们访问 health 的时候,会看到下面的信息,说明服务是可用的。
通过在配置文件中配置如下信息,可开启活动状态检测。
management:
health:
probes:
enabled: true
endpoint:
health:
show-details: always
开启上述配置之后,重启服务,在访问 health 页面,看到的内容如下
除了状态标示外,还多了一个 groups
节点。
Liveness:应用程序是否处于可用状态
可通过 /actuator/health/liveness
路径查看
Readiness:应用程序是否准备好接受客户端请求了。
可通过 /actuator/health/readiness
路径查看
这个功能其实是针对部署在 Kubernetes 上的服务做的支持。Kubernetes 提供了 LivenessProbe 和 cProbe 两类探针,活动状态检查便是对这两类探针提供无缝支持。
在配置文件中增加配置即可,与 kubernetes 做无缝对接。
spring:
main:
cloud-platform: kubernetes
那应该怎么用呢
拿 Readiness 来说吧,假设我们要对外宣布次服务暂时不接受请求,那就改变 readiness 的状态,当探针过来的时候发现不接受请求,那就去请求其他实例了。
具体怎么做呢,我在 Controller 中加了两个方法,一个开启接受请求,一个停止接收请求。
@RestController
public class PlayController {
private final ApplicationEventPublisher publisher;
public PlayController(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@GetMapping(value = "play")
public String play() throws InterruptedException{
Thread.sleep(6000);
return "hey, play with me!";
}
@GetMapping(value = "up")
public String up(){
AvailabilityChangeEvent.publish(publisher,this, ReadinessState.ACCEPTING_TRAFFIC);
return "up";
}
@GetMapping(value = "down")
public String down(){
AvailabilityChangeEvent.publish(publisher,this, ReadinessState.REFUSING_TRAFFIC);
return "down";
}
}
通过 AvailabilityChangeEvent
这个类的 publish 方法,更改自身服务状态。当我们访问 down 接口之后,再次查看 health/readiness
的状态情况,会显示如下内容:OUT_OF_SERVICE
表示离线,不接受请求。
而当我们请求 up 接口后,服务状态又变成了 up,这也就实现了服务下线和上线的功能。
支持 JDK 14
Spring Boot 2.3 支持 JDK 14 了,但跟我有啥关系吗,没有。我依然用我的 Java 8。真香。
Spring Data Neumann
Spring Boot 2.3 发布了 Spring Data Neumann,其中包含许多主要版本和驱动程序升级。此版本还增加了对 R2DBC(Reactive Relational Database Connectivity) 的稳定版本支持。R2DBC 提供了异步编程方式访问数据库的 API,主要是配合开发异步非阻塞式的应用程序使用的。
总结
从中可以看出很大部分内容都是与 Docker 容器技术有关的,比如分层打镜像包、无缝支持 kubernetes 等,可见 docker 微服务已然成为很多开发者的选择。但是仍然有待改进,比如默认的 docker hub 是 Google Cloud,就不能灵活配置,支持国内的镜像仓库不好吗。
你们用的 Spring Boot 哪个版本,会来尝个鲜儿吗?
参考文档:
http://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/maven-plugin/reference/html/index.html#goals http://medium.com/@TimvanBaarsen/whats-new-in-spring-boot-2-3-22d01d036f11
(七) 跟我学习SpringCloud-Spring Boot Starter的介绍及使用
Spring Boot 的便利性体现在,它简化了很多烦琐的配置,这对于开发人员来说是一个福音,通过引入各种 Spring Boot Starter 包可以快速搭建出一个项目的脚手架。
目前提供的 Spring Boot Starter 包有:
- spring-boot-starter-web:快速构建基于 Spring MVC 的 Web 项目,使用 Tomcat 做默认嵌入式容器。
- spring-boot-starter-data-redis:操作 Redis。
- spring-boot-starter-data-mongodb:操作 Mongodb。
- spring-boot-starter-data-jpa:操作 Mysql。
- spring-boot-starter-activemq:操作 Activemq。
- ……
自动配置非常方便,当我们要操作 Mongodb 的时候,只需要引入 spring-boot-starter-data-mongodb 的依赖,然后配置 Mongodb 的链接信息 spring.data.mongodb.uri=mongodb://localhost/test 就可以使用 MongoTemplate 来操作数据,MongoTemplate 的初始化工作全部交给 Starter 来完成。
自动配置麻烦的是当出现错误时,排查问题的难度上升了。自动配置的逻辑都在 Spring Boot Starter 中,要想快速定位问题,就必须得了解 Spring Boot Starter 的内部原理。接下来我们自己动手来实现一个 Spring Boot Starter。
Spring Boot Starter项目创建
创建一个项目 spring-boot-starter-demo,pom.xml 配置代码如下所示。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
创建一个配置类,用于在属性文件中配置值,相当于 spring.data.mongo 这种形式,代码如下所示。
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
@Data
@ConfigurationProperties("spring.user")
public class UserPorperties {
private String name;
}
再定义一个 Client,相当于 MongoTemplate,里面定一个方法,用于获取配置中的值,代码如下所示。
public class UserClient {
private UserPorperties userPorperties;
public UserClient() {
}
public UserClient(UserPorperties p) {
this.userPorperties = p;
}
public String getName() {
return userPorperties.getName();
}
}
自动创建客户端
一个最基本的 Starter 包定义好了,但目前肯定是不能使用 UserClient,因为我们没有自动构建 UserClient 的实例。接下来开始构建 UserClient,代码如下所示。
@Configuration
@EnableConfigurationProperties(UserPorperties.class)
public class UserAutoConfigure {
@Bean
@ConditionalOnProperty(prefix = "spring.user", value = "enabled", havingValue = "true")
public UserClient userClient(UserPorperties userPorperties) {
return new UserClient(userPorperties);
}
}
Spring Boot 会默认扫描跟启动类平级的包,假如我们的 Starter 跟启动类不在同一个主包下,如何能让 UserAutoConfigure 生效?
在 resources 下创建一个 META-INF 文件夹,然后在 META-INF 文件夹中创建一个 spring.factories 文件,文件中指定自动配置的类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cxytiandi.demo.UserAutoConfigure
Spring Boot 启动时会去读取 spring.factories 文件,然后根据配置激活对应的配置类,至此一个简单的 Starter 包就实现了。
使用 Starter
现在可以在其他的项目中引入这个 Starter 包,代码如下所示。
<dependency>
<groupId>com.cxytiandi</groupId>
<artifactId>spring-boot-starter-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
引入之后就直接可以使用 UserClient,UserClient 在项目启动的时候已经自动初始化好,代码如下所示。
@RestController
public class UserController {
@Autowired
private UserClient userClient;
@GetMapping("/user/name")
public String getUserName() {
return userClient.getName();
}
}
属性文件中配置 name 的值和开启 UserClient:
spring.user.name=zhangsan
spring.user.enabled=true
访问 /user/name 就可以返回我们配置的 zhangsan。
使用注解开启 Starter 自动构建
很多时候我们不想在引入 Starter 包时就执行初始化的逻辑,而是想要由用户来指定是否要开启 Starter 包的自动配置功能,比如常用的 @EnableAsync 这个注解就是用于开启调用方法异步执行的功能。
同样地,我们也可以通过注解的方式来开启是否自动配置,如果用注解的方式,那么 spring.factories 就不需要编写了,下面就来看一下怎么定义启用自动配置的注解,代码如下所示。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({UserAutoConfigure.class})
public @interface EnableUserClient {
}
这段代码的核心是 @Import({UserAutoConfigure.class}),通过导入的方式实现把 UserAutoConfigure 实例加入 SpringIOC 容器中,这样就能开启自动配置了。
使用方式就是在启动类上加上该注解,代码如下所示。
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
使用配置开启 Starter 自动构建
在某些场景下,UserAutoConfigure 中会配置多个对象,对于这些对象,如果不想全部配置,或是想让用户指定需要开启配置的时候再去构建对象,这个时候我们可以通过 @ConditionalOnProperty 来指定是否开启配置的功能,代码如下所示。
@Bean
@ConditionalOnProperty(prefix = "spring.user",value = "enabled",havingValue = "true")
public UserClient userClient(UserPorperties userPorperties) {
return new UserClient(userPorperties);
}
通过上面的配置,只有当启动类加了 @EnableUserClient 并且配置文件中 spring.user.enabled=true 的时候才会自动配置 UserClient。
配置 Starter 内容提示
在自定义 Starter 包的过程中,还有一点比较重要,就是对配置的内容项进行提示,需要注意的是,Eclipse 中是不支持提示的,Spring Tools 4 for Eclipse 中可以提示。
定义提示内容需要在 META-INF 中创建一个 spring-configuration-metadata.json 文件,代码如下所示。
{ "properties": [ { "name": "spring.user.name", "defaultValue": "cxytinadi" }, { "name": "spring.user.enabled", "type": "java.lang.Boolean", "defaultValue": false } ] }
- name:配置名
- type:配置的数据类型
- defaultValue:默认值
推荐分布式架构源码
(五) 跟我学习SpringCloud-Spring Boot简介
Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式进行配置,从而使开发人员不再需要定义样板化的配置。
Spring Boot 致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
在使用 Spring Boot 之前,我们需要搭建一个项目框架并配置各种第三方库的依赖,还需要在 XML 中配置很多内容。
Spring Boot 完全打破了我们之前的使用习惯,一分钟就可以创建一个 Web 开发的项目;通过 Starter 的方式轻松集成第三方的框架;去掉了 XML 的配置,全部用注解代替。
Spring Boot Starter 是用来简化 jar 包依赖的,集成一个框架只需要引入一个 Starter,然后在属性文件中配置一些值,整个集成的过程就结束了。
不得不说,Spring Boot 在内部做了很多的处理,让开发人员使用起来更加简单了。
下面笔者总结了一些使用 Spring Boot 开发的优点:
- 基于 Spring 开发 Web 应用更加容易。
- 采用基于注解方式的配置,避免了编写大量重复的 XML 配置。
- 可以轻松集成 Spring 家族的其他框架,比如 Spring JDBC、Spring Data 等。
- 提供嵌入式服务器,令开发和部署都变得非常方便。
推荐布式架构源码
(六) 跟我学习SpringCloud-Spring Boot项目详细搭建步骤
在 Spring Tools 4 for Eclipse 中依次选择 File->New->Maven Project,然后在出现的界面中按图所示增加相关信息。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
编写启动类,代码如下所示。
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
启动类使用了 @SpringBootApplication 注解,这个注解表示该类是一个 Spring Boot 应用。直接运行 App 类即可启动,启动成功后在控制台输出信息,默认端口是 8080,如图所示。
可以看到,我们只在 pom.xml 中引入了一个 Web 的 Starter,然后创建一个普通的 Java 类,一个 Main 方法就可以启动一个 Web 项目。
与之前的使用方式相比,这种方式简单很多。以前需要配置各种 Spring 相关的包,还需要配置 web.xml 文件,还需要将项目放入 Tomcat 中去执行,搭建项目的过程还特别容易出错,会出现各种 jar 包冲突。有了 Spring Boot 后这些问题都解决了。
我们之所以能够通过一个 Main 方法启动一个 Web 服务,是因为 Sprig Boot 中内嵌了 Tomcat,然后通过内嵌的 Tomcat 来提供服务。当然,我们也可以使用别的容器来替换 Tomcat,比如 Undertow 或 Jetty。
Spring Tools 4 for Eclipse 还为我们提供了更加便捷的项目创建方式,在 File->New 选项中有 Spring Starter Project,可以直接选择 Spring Boot 的版本以及需要依赖的第三方包,直接生成 Spring Boot 项目,不用再去手动配置 Maven 依赖。
这个功能和 https://start.spring.io/ 提供的是同一个功能,方便快速搭建 Spring Boot 项目脚手架。
推荐布式架构源码
编写第一个 REST 接口
本节将创建一个控制器,编写第一个 REST 接口,访问地址使用 /hello,代码如下所示。
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
@RestController 是 @Controller 和 @ResponseBody 的组合注解,可以直接返回 Json 格式数据。
@GetMapping 其实就是 @RequestMapping(method=RequestMethod.GET),通过访问 http://localhost:8080/hello 可以看到输出的结果“hello”。
读取配置文件
在以前的项目中我们主要在 XML 文件中进行框架配置,业务的相关配置会放在属性文件中,然后通过一个属性读取的工具类来读取配置信息。
在 Spring Boot 中我们不再需要使用这种方式去读取数据了。Spring Boot 中的配置通常放在 application.properties 中,读取配置信息非常方便,总共分为 3 种方式。
1)Environment
可以通过 Environment 的 getProperty 方法来获取想要的配置信息,代码如下所示。
@RestController
public class HelloController {
// 注入对象
@Autowired
private Environment env;
@GetMapping("/hello")
public String hello() {
// 读取配置
String port = env.getProperty("server.port");
return port;
}
}
2)@Value
可以注入具体的配置信息,代码如下所示。
@RestController
public class HelloController {
// 注入配置
@Value("${server.port}")
private String port;
@GetMapping("/hello")
public String hello() {
return port;
}
}
3)自定义配置类
prefix 定义配置的前缀,代码如下所示。
@ConfigurationProperties(prefix = "net.biancheng")
@Component
public class MyConfig {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
读取配置的方法代码如下所示。
@RestController
public class HelloController {
@Autowired
private MyConfig myConfig;
@GetMapping("/hello")
public String hello() {
return myConfig.getName();
}
}
定义配置 application.properties 的方法如下:
net.biancheng.name=zhangsan
profiles 多环境配置
在平时的开发中,项目会被部署到测试环境、生产环境,但是每个环境的数据库地址等配置信息都是不一样的。通过 profile 来激活不同环境下的配置文件就能解决配置信息不一样的问题。在 Spring Boot 中可以通过 spring.profiles.active=dev 来激活不同环境下的配置。
可以定义多个配置文件,每个配置文件对应一个环境,格式为 application-环境.properties,如表 1 所示。
application.properties | 通用配置,不区分环境 |
application-dev.properties | 开发环境 |
application-test.properties | 测试环境 |
application-prod.properties | 生产环境 |
在开发环境中,可以通过修改 application.properties 中的 spring.profiles.active 的值来激活对应环境的配置,在部署的时候可以通过 java–jar xxx.jar--spring.profiles.active=dev 来指定使用对应的配置。
热部署
开发过程中经常会改动代码,此时若想看下效果,就不得不停掉项目然后重启。
对于 Spring Boot 项目来说,启动时间是非常快的,在微服务的架构下,每个服务只关注自己的业务,代码量也非常小,这个启动时间是可以容忍的。
对于那些臃肿的单体老项目,启动时间简直是浪费生命。虽然 Spring Boot 启动很快,但是我们还是要自己去重启。能不能做到有改动,它就会悄无声息地自己把改动的地方重新加载一遍?答案是肯定的,通过 spring-boot-devtools 就可以实现。
只需要添加 spring-boot-devtools 的依赖即可实现热部署功能,代码如下所示。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
actuator 监控
Spring Boot 提供了一个用于监控和管理自身应用信息的模块,它就是 spring-boot-starter-actuator。该模块使用起来非常简单,只需要加入依赖即可,代码如下所示。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
启动项目我们会发现在控制台输出的内容中增加了图 4 所示的信息。
下图所示的这些信息是 Actuator 模块提供的端点信息,具体如表 2 所示,通过访问这些端点我们可以得到很多监控信息。
比如,我们访问 /actuator/health 可以得到下面的信息:
{
"status": "UP"
}
Http方法 | 路径 | 描述 | Http默认暴露 |
---|---|---|---|
GET | /actuator/conflgprops | 查看配置属性,包含默认配置 | false |
GET | /actuator/beans | 查看bean及其关系列表 | false |
GET | /actuator/heapdump | 打印线程栈 | false |
GET | /actuator/env | 查看所有环境变量 | false |
GET | /actuator/env/ {name} | 查看具体变量值 | true |
GET | /actuator/health | 查看应用健康指标 | true |
GET | /actuator/info | 查看应用信息 | false |
GET | /actuator/mappings | 查看所有 URL 映射 | false |
GET | /actuator/metrics | 查看应用基本指标 | false |
GET | /actuator/metrics/{name} | 查看具体指标 | false |
POST | /actuator/shutdown | 关闭应用 | false |
GET | /actuator/httptrace | 查看基本追踪信息 | false |
GET | /actuator/loggers | 显示应用程序中 loggers 配置 | false |
GET | /actuator/scheduledtasks | 显示定时任务 | false |
UP 表示当前应用处于健康状态,如果是 DOWN 就表示当前应用不健康。增加下面的配置可以让一些健康信息的详情也显示出来:
management.endpoint.health.show-details=ALWAYS
再次访问 /actuator/health,就可以得到健康状态的详细信息:
{
"status": "UP",
"diskSpace": {
"status": "UP",
"total": 491270434816,
"free": 383870214144,
"threshold": 10485760
}
}
大部分端点默认都不暴露出来,我们可以手动配置需要暴露的端点。如果需要暴露多个端点,可以用逗号分隔,如下所示:
management.endpoints.web.exposure.include=configprops,beans
如果想全部端点都暴露的话直接配置成下面的方式:
management.endpoints.web.exposure.include=*
关于这些监控的信息不再赘述,大家可以自行了解。后面我们会介绍如何使用 Spring Boot Admin 在页面上更加直观地展示这些信息,目前都是 Json 格式的数据,不方便查看。
自定义 actuator 端点
在很多场景下,我们需要自定义一些规则来判断应用的状态是否健康,可以采用自定义端点的方式来满足多样性的需求。如果我们只是需要对应用的健康状态增加一些其他维度的数据,可以通过继承 AbstractHealthIndicator 来实现自己的业务逻辑。代码如下所示。
@Component
public class UserHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Builder builder) throws Exception {
builder.up().withDetail("status", true);
// builder.down().withDetail("status", false);
}
}
通过 up 方法指定应用的状态为健康,down 方法指定应用的状态为不健康。withDetail 方法用于添加一些详细信息。访问 /actuator/health,可以得到我们自定义的健康状态的详细信息:
{
"status": "UP",
"details": {
"user": {
"status": "UP",
"details": {
"status": true
}
},
"diskSpace": {
"status": "UP",
"details": {
"total":
249795969024,
"free": 7575375872,
"threshold": 10485760
}
}
}
}
上面我们是在框架自带的 health 端点中进行扩展,还有一种需求是完全开发一个全新的端点,比如查看当前登录的用户信息的端点。自定义全新的端点很简单,通过 @Endpoint 注解就可以实现。代码如下所示。
@Component
@Endpoint(id = "user")
public class UserEndpoint {
@ReadOperation
public List<Map<String, Object>> health() {
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> map = new HashMap<>();
map.put("userId", 1001);
map.put("userName", "zhangsan");
list.add(map);
return list;
}
}
访问 /actuator/user 可以看到返回的用户信息如下:
[
{
"userName": "zhangsan",
"userId": 1001
}
]
统一异常处理
对于接口的定义,我们通常会有一个固定的格式,比如:
{
"status": true,
"code": 200,
"message": null,
"data": [
{
"id": "101",
"name": "jack"
},
{
"id": "102",
"name": "jason"
}
]
}
但是,如果调用方在请求我们的 API 时把接口地址写错了,就会得到一个 404 错误:
{
"timestamp": 1492063521109,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/rest11/auth"
}
后端服务会告诉我们哪个地址没找到,其实也挺友好。但是因为我们上面自定义的数据格式跟下面的不一致,所以当用户拿到这个返回的时候是无法识别的,其中最明显的是 status 字段。
我们自定义的是 boolean 类型,用来表示请求是否成功,这里返回的就是 Http 的状态码,所以我们需要在发生这种系统错误时也能返回我们自定义的那种格式,那就要定义一个异常处理类(代码如下所示),通过这个类既可以返回统一的格式,也可以统一记录异常日志。
@ControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseData defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
logger.error("", e);
ResponseData r = new ResponseData();
r.setMessage(e.getMessage());
if (e instanceof org.springframework.web.servlet.NoHandlerFoundException) {
r.setCode(404);
} else {
r.setCode(500);
}
r.setData(null);
r.setStatus(false);
return r;
}
}
ResponseData 是我们返回格式的实体类,其发生错误时也会被捕获到,然后封装好返回格式并返回给调用方。最后关键的一步是,在 Spring Boot 的配置文件中加上如下代码所示配置。
# 出现错误时, 直接抛出异常
spring.mvc.throw-exception-if-no-handler-found=true
# 不要为我们工程中的资源文件建立映射
spring.resources.add-mappings=false
然后当我们调用一个不存在的接口时,返回的错误信息就是我们自定义的那种格式了:
{
"status": false, "code": 404,
"message": "No handler found for GET /rest11/auth", "data": null
}
最后贴上 ResponseData 的定义,代码如下所示。
public class ResponseData {
private Boolean status = true;
private int code = 200;
private String message;
private Object data;
// get set ...
}
异步执行
异步调用就是不用等待结果的返回就执行后面的逻辑;同步调用则需要等待结果再执行后面的逻辑。
通常我们使用异步操作时都会创建一个线程执行一段逻辑,然后把这个线程丢到线程池中去执行,代码如下所示。
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(() -> {
try {
// 业务逻辑
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});
这种方式尽管使用了 Java 的 Lambda,但看起来没那么优雅。在 Spring 中有一种更简单的方式来执行异步操作,只需要一个 @Async 注解即可,代码如下所示。
@Async
public void saveLog() {
System.err.println(Thread.currentThread().getName());
}
我们可以直接在 Controller 中调用这个业务方法,它就是异步执行的,会在默认的线程池中去执行。需要注意的是,一定要在外部的类中去调用这个方法,如果在本类调用则不起作用,比如 this.saveLog()。最后在启动类上开启异步任务的执行,添加 @EnableAsync 即可。
另外,关于执行异步任务的线程池我们也可以自定义,首先我们定义一个线程池的配置类,用来配置一些参数,具体代码如下所示。
@Configuration
@ConfigurationProperties(prefix = "spring.task.pool")
public class TaskThreadPoolConfig {
// 核心线程数
private int corePoolSize = 5;
// 最大线程数
private int maxPoolSize = 50;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 60;
// 队列长度
private int queueCapacity = 10000;
// 线程名称前缀
private String threadNamePrefix = "FSH-AsyncTask-";
// get set ...
}
然后我们重新定义线程池的配置,代码如下所示。
@Configuration
public class AsyncTaskExecutePool implements AsyncConfigurer {
private Logger logger = LoggerFactory.getLogger(AsyncTaskExecutePool.class);
@Autowired
private TaskThreadPoolConfig config;
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(config.getCorePoolSize());
executor.setMaxPoolSize(config.getMaxPoolSize());
executor.setQueueCapacity(config.getQueueCapacity());
executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
executor.setThreadNamePrefix(config.getThreadNamePrefix());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initia lize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
// 异步任务中异常处理
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) {
logger.error("==========================" + arg0.getMessage() + "=======================", arg0);
logger.error("exception method:" + arg1.getName());
}
};
}
}
配置完之后我们的异步任务执行的线程池就是我们自定义的了,我们可以在属性文件里面配置线程池的大小等信息,也可以使用默认的配置:
spring.task.pool.maxPoolSize=100
最后讲一下线程池配置的拒绝策略。当我们的线程数量高于线程池的处理速度时,任务会被缓存到本地的队列中。队列也是有大小的,如果超过了这个大小,就需要有拒绝的策略,不然就会出现内存溢出。目前支持两种拒绝策略:
- AbortPolicy:直接抛出 java.util.concurrent.RejectedExecutionException 异常。
- CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,这样可以有效降低向线程池内添加任务的速度。
建议大家用 CallerRunsPolicy 策略,因为当队列中的任务满了之后,如果直接抛异常,那么这个任务就会被丢弃。如果是 CallerRunsPolicy 策略,则会用主线程去执行,也就是同步执行,这样操作最起码任务不会被丢弃。
随机端口
在实际的开发过程中,每个项目的端口都是定好的,通过 server.port 可以指定端口。
当一个服务想要启动多个实例时,就需要改变端口,特别是在我们后面进行 Spring Cloud 学习的时候,服务都会注册到注册中心里去,为了能够让服务随时都可以扩容,在服务启动的时候能随机生成一个可以使用的端口是最好不过的。
在 Spring Boot 中,可以通过 ${random} 来生成随机数字,我们可以这样使用:
server.port=${random.int[2000,8000]}
通过 random.int 方法,指定随机数的访问,生成一个在 2000 到 8000 之间的数字,这样每次启动的端口就都不一样了。
其实上面的方法虽然能够达到预期的效果,但是也会存在一些问题:如果这个端口已经在使用了,那么启动必然会报错。所以我们可以通过代码的方式来随机生成一个端口,然后检测是否被使用,这样就能生成一个没有被使用的端口。
编写一个启动参数设置类,代码如下所示。
public class StartCommand {
private Logger logger = LoggerFactory.getLogger(StartCommand.class);
public StartCommand(String[] args) {
Boolean isServerPort = false;
String serverPort = "";
if (args != null) {
for (String arg : args) {
if (StringUtils.hasText(arg) && arg.startsWith("--server.port")) {
isServerPort = true;
serverPort = arg;
break;
}
}
}
// 没有指定端口, 则随机生成一个可用的端口
if (!isServerPort) {
int port = ServerPortUtils.getAvailablePort();
logger.info("current server.port=" + port);
System.setProperty("server.port", String.valueOf(port));
} else {
logger.info("current server.port=" + serverPort.split("=")[1]);
System.setProperty("server.port", serverPort.split("=")[1]);
}
}
}
通过对启动参数进行遍历判断,如果有指定启动端口,后续就不自动生成了;如果没有指定,就通过 ServerPortUtils 获取一个可以使用的端口,然后设置到环境变量中。在 application.properties 中通过下面的方式获取端口:
server.port=${server.port}
关于获取可用端口的代码如下所示。
public static int getAvailablePort() {
int max = 65535;
int min = 2000;
Random random = new Random();
int port = random.nextInt(max)%(max-min+1) + min;
boolean using = NetUtils.isLoclePortUsing(port);
if (using) {
return getAvailablePort();
} else {
return port;
}
}
获取可用端口的主要逻辑是指定一个范围,然后生成随机数字,最后通过 NetUtils 来检查端口是否可用。如果获取到可用的端口则直接返回,没有获取到可用的端口则执行回调逻辑,重新获取。检测端口是否可用主要是用 Socket 来判断这个端口是否可以被链接。
最后在启动类中调用端口即可使用,代码如下所示。
public class FshHouseServiceApplication {
public static void main(String[] args) {
// 启动参数设置, 比如自动生成端口
new StartCommand(args);
SpringApplication.run(FshHouseServiceApplication.class, args);
}
}
编译打包
传统的 Web 项目在部署的时候,是编译出一个 war 包放到 Tomcat 的 webapps 目录下。而在 Spring Boot 构建的 Web 项目中则打破了这一传统部署的方式,它采用更加简单的内置容器方式来部署应用程序,只需要将应用编译打包成一个 jar 包,直接可以通过 java–jar 命令启动应用。
在项目的 pom.xml 中增加打包的 Maven 插件,代码如下所示。
<build>
<plugins>
<!-- 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<mainClass>net.biancheng.spring_boot_example.App</mainClass>
</configuration>
</plugin>
<!-- 编译插件, 指定JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
mainClass 配置的是我们的启动入口类,配置完成后可以通过 Maven 的 mvn clean package 命令进行编译打包操作。编译完成后在 target 目录下会生成对应的 jar 包,部署的时候直接调用 java–jar xx.jar 即可启动应用。
推荐布式架构源码
(第五篇) 跟我学习SpringCloud-Spring Boot简介
Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式进行配置,从而使开发人员不再需要定义样板化的配置。
Spring Boot 致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
在使用 Spring Boot 之前,我们需要搭建一个项目框架并配置各种第三方库的依赖,还需要在 XML 中配置很多内容。
Spring Boot 完全打破了我们之前的使用习惯,一分钟就可以创建一个 Web 开发的项目;通过 Starter 的方式轻松集成第三方的框架;去掉了 XML 的配置,全部用注解代替。
Spring Boot Starter 是用来简化 jar 包依赖的,集成一个框架只需要引入一个 Starter,然后在属性文件中配置一些值,整个集成的过程就结束了。
不得不说,Spring Boot 在内部做了很多的处理,让开发人员使用起来更加简单了。
下面笔者总结了一些使用 Spring Boot 开发的优点:
- 基于 Spring 开发 Web 应用更加容易。
- 采用基于注解方式的配置,避免了编写大量重复的 XML 配置。
- 可以轻松集成 Spring 家族的其他框架,比如 Spring JDBC、Spring Data 等。
- 提供嵌入式服务器,令开发和部署都变得非常方便。
推荐布式架构源码
我们今天的关于跟我极速尝鲜 Spring Boot 2.3的分享就到这里,谢谢您的阅读,如果想了解更多关于(七) 跟我学习SpringCloud-Spring Boot Starter的介绍及使用、(五) 跟我学习SpringCloud-Spring Boot简介、(六) 跟我学习SpringCloud-Spring Boot项目详细搭建步骤、(第五篇) 跟我学习SpringCloud-Spring Boot简介的相关信息,可以在本站进行搜索。
本文标签: