}
}
@RestController 是 @Controller 和 @ResponseBody 的组合注解,可以直接返回 Json 格式数据。
运行结果
读取配置文件
在以前的项目中我们主要在 XML 文件中进行框架配置,业务的相关配置会放在属性文件中,然后通过一个属性读取的工具类来读取配置信息。
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 中的 spring.profiles.active 的值来激活对应环境的配置,在部署的时候可以通过 java–jar xxx.jar–spring.profiles.active=dev 来指定使用对应的配置。
热部署
开发过程中经常会改动代码,此时若想看下效果,就不得不停掉项目然后重启。
对于 Spring Boot 项目来说,启动时间是非常快的,在微服务的架构下,每个服务只关注自己的业务,代码量也非常小,这个启动时间是可以容忍的。
通过 spring-boot-devtools 就可以实现热部署。
只需要添加 spring-boot-devtools 的依赖即可实现热部署功能,代码如下所示。
org.springframework.boot
spring-boot-devtools
actuator 监控
Spring Boot 提供了一个用于监控和管理自身应用信息的模块,它就是 spring-boot-starter-actuator。该模块使用起来非常简单,只需要加入依赖即可,代码如下。
org.springframework.boot
spring-boot-starter-actuator
启动项目我们会发现在控制台输出的内容中增加了 所示的信息。
比如,我们访问 /actuator/health 可以得到下面的信息:
{
“status”: “UP”
}
Spring Boot启动控制台输出
Actuator端点信息
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 …
}
然后我们重新定义线程池的配置,代码如下所示。
最近我根据上述的技术体系图搜集了几十套腾讯、头条、阿里、美团等公司21年的面试题,把技术点整理成了视频(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分
中调用这个业务方法,它就是异步执行的,会在默认的线程池中去执行。需要注意的是,一定要在外部的类中去调用这个方法,如果在本类调用则不起作用,比如 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 …
}
然后我们重新定义线程池的配置,代码如下所示。
[外链图片转存中…(img-uZcYPggx-1714486792133)]
最近我根据上述的技术体系图搜集了几十套腾讯、头条、阿里、美团等公司21年的面试题,把技术点整理成了视频(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分
[外链图片转存中…(img-Bwd5hUMi-1714486792133)]