# springboot快速开发 **Repository Path**: Junior-L/springboot-rapid-development ## Basic Information - **Project Name**: springboot快速开发 - **Description**: 该项目做为web模板,集成了接口监控服务,同时拥有全局异常处理,对接口响应数据进行了封装等一系列常用功能。为后续开发缩减了工作量 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2024-07-23 - **Last Updated**: 2025-09-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 介绍 该项目做为web模板,集成了接口监控服务,同时拥有全局异常处理,对接口响应数据进行了封装等一系列常用功能。为后续开发缩减了工作量 # 环境 - java: 11 - springboot: 2.3.12.RELEASE - jackson: 2.17.1 - mybatis: 2.2.2 - mysql-connector-java: 8.0.28 # 使用说明 ## 1. 全局响应体封装的使用 - ### 项目提供了全局响应体封装类`ResponseResultVo`, 该类提供静态`success()`方法和`fail()`方法以及多个重载方法 ```java @GetMapping("success") public ResponseResultVo success() { return ResponseResultVo.success(); } @GetMapping("success") public ResponseResultVo success() { return ResponseResultVo.success(data); } @GetMapping("success") public ResponseResultVo success() { return ResponseResultVo.success(BaseHttpCodeEnum.SUCCESS); } @GetMapping("success") public ResponseResultVo success() { return ResponseResultVo.success(BaseHttpCodeEnum.SUCCESS, data); } // fail() 方法使用类似 ``` - ### 本项目还通过`GlobalResponseAdvice`实现`ResponseBodyAdvice`接口获得全局请求响应类型都会自动转换为`ResponseResultVo`响应类, **不想自动转换可以添加注解` @IgnoreRestControllerResponseAdvice`** ```java // 响应内容为不同类型, 下面的方法返回的 List 自动封装到 ResponseResultVo @GetMapping(value = "test") public List test(){ ArrayList list = new ArrayList<>(); list.add("d"); return list; } // 如果想保持原来的内容, 在方法是添加 @IgnoreRestControllerResponseAdvice 即可 @GetMapping(value = "test") @IgnoreRestControllerResponseAdvice public List test(){ ArrayList list = new ArrayList<>(); list.add("d"); return list; } ``` ## 2. 在项目中添加新的状态码枚举类以及如何在响应体中应用 - ### **首先实现`EnumInfoInterface`接口**, 代码如下 ```java @Getter @AllArgsConstructor @ToString public enum CustomHttpCodeEnum implements EnumInfoInterface{ // 编写自己需要的枚举值 SUCCESS(10, "成功"); private final Integer code; private final String description; } ``` - ### 在`controller`中的方法使用 ```java @GetMapping("success") public ResponseResultVo success() { return ResponseResultVo.success(CustomHttpCodeEnum.SUCCESS, "测试成功响应体"); } ``` ## 3. 自定义异常以及在全局异常中使用 - ### 首先自定义一个异常类继承`ExceptionTemplate`异常模板, 如下 ```java public class BusinessException extends ExceptionTemplate { public BusinessException(EnumInfoInterface enumInfoInterface) { super(enumInfoInterface); } public BusinessException(String errorMsg) { super(errorMsg); } @Override public String toString() { return "BusinessException(" + "errorCode=" + errorCode + ", errorMsg='" + errorMsg + '\'' + ')'; } } ``` - ### 在`GlobalExceptionAdvice`中添加异常捕获, 没有添加将会被上层异常捕获. 如下 ```java @ExceptionHandler(BusinessException.class) public ResponseResultVo HandleBusinessException(BusinessException e) { log.error(EXCEPTION_PREFIX, e.toString(), e); if (e.getErrorCode() == null) { return ResponseResultVo.fail(e.getMessage()); } return ResponseResultVo.fail(e.getErrorCode(), e.getErrorMsg()); } ``` - ### 根据需要是否在`ErrorHttpCodeEnum`添加异常枚举, 或者也可以自己在创建一个枚举类(**需要实现`EnumInfoInterface`接口**) ```java @Getter @ToString @AllArgsConstructor public enum ErrorHttpCodeEnum implements EnumInfoInterface { BUSINESS_ERROR(1000, "业务出现异常"), ; private final Integer code; private final String description; } ``` - ### 最后在代码中抛出`BusinessException`异常, 全局异常就能捕获到该异常, 如下 ```java public void test(){ throw new BusinessException(ErrorHttpCodeEnum.BUSINESS_ERROR); } ``` ## 4. 打印监控日志 - ### `Controller`监控日志 项目默认对`controller`包下面的所有方法切面, 打印日志. 如果不想打印监控日志, **可以在类或者方法上添加`@NoMonitor`注解** ```java // 在类上添加注解, 该类所有方法将不会打印日志 @RestController @RequestMapping("demo") @NoMonitor public class TestAPI { // ... } // 在方法上添加注解, 该方法将不打印日志 @GetMapping(value = "test") @NoMonitor public List test(String param){ // ... } ``` - ### 方法监控日志 项目默认不对方法进行日志打印, **可以通过在类或者方法上添加`@Monitor`注解**. 推荐在业务层需要监控的方法上使用, **异步方法不需要使用该注解** ```java // 在类上使用, 该类所有方法都会打印日志 @Service @Monitor public class TestService { // ... } // 在方法上使用, 该方法会打印日志 @Monitor public List t(List name) { // ... } ``` - ### 异步方法监控日志 #### 1. 使用`@Async`注解的异步方法, 推荐不关心返回值时使用 项目默认对所有类或者方法上使用`@Async`注解进行监控打印日志, 如果不想打印监控日志, **可以在类或者方法上添加`@NoMonitor`注解** ```java @Component @Async("ioTaskExecutor") @Slf4j public class AsyncTask { // 此方法将使用类级别指定的ioTaskExecutor异步执行 public void processData() throws InterruptedException { Thread.sleep(2000); throw new BusinessException(ErrorHttpCodeEnum.BUSINESS_ERROR); } // 不对该方法进行监控 @NoMonitor public void generateReport() throws InterruptedException { Thread.sleep(3000); } // 此方法覆盖类级别的设置,使用指定的cpuTaskExecutor @Async("cpuTaskExecutor") public void calculateStatistics() throws InterruptedException { Thread.sleep(1000); } } ``` #### 2. 使用`CompletableFuture`创建的异步任务, 关心返回值时推荐使用此种方式 项目对类或者方法上有`@CompletableFutureMethod`注解进行监控打印日志, 所以在使用`CompletableFuture`创建异步任务时添加上`@CompletableFutureMethod`注解, 如果不想监控可以不加注解. 由于`CompletableFuture`可以组合链式调用等, **强烈建议创建一个方法, 在该方法中进行组合或者链式调用并加上`@CompletableFutureMethod`注解** ```java @Component @Slf4j public class AsyncTask2 { @Resource private Executor cpuTaskExecutor; @Resource private Executor ioTaskExecutor; // 创建一个 CompletableFuture 异步任务, 使用自定义线程池 @CompletableFutureMethod public CompletableFuture asyncTaskResult(Integer num) { return CompletableFuture.supplyAsync(() -> dosomething(), ioTaskExecutor); } // 再创建一个 CompletableFuture 异步任务, 使用自定义线程池 @CompletableFutureMethod public CompletableFuture asyncTaskResult2(Integer num) { return CompletableFuture.supplyAsync(() -> dosomething(), cpuTaskExecutor); } // 将上面两个异步任务组合 @CompletableFutureMethod public CompletableFuture asyncTaskResult3() { CompletableFuture future = asyncTaskResult(10); CompletableFuture future1 = asyncTaskResult2(10); return future.thenCombineAsync(future1, (r1, r2) ->{ log.info(Thread.currentThread().getName()); return r1 + r2; }); } } ``` ## 5. 线程池以及异步任务 - ### 项目已经创建了`cpu`密集型`cpuTaskExecutor`和`io`密集型`ioTaskExecutor`两种线程池, 如需要再定义线程池, 在`BaseThreadPoolConfig`类中添加 ```java @Configuration public class BaseThreadPoolConfig { // ... @Bean("ioTaskExecutor") public Executor ioTaskExecutor() { return createExecutor(CPU_COUNT * 2, 20, 200, 60, new ThreadPoolExecutor.AbortPolicy(), "io-task-", true); } // 在后面继续添加就行 // ... } ``` - ### 异步任务使用 第一种: 利用`CompletableFuture`创建线程, 首先注入线程池`Bean`, 在CompletableFuture中手动指定. 如下 ```java @Component public class AsyncTask { @Resource private Executor ioTaskExecutor; // 创建有返回值的异步方法 @CompletableFutureMethod public CompletableFuture asyncTaskResult(Integer num) { return CompletableFuture.supplyAsync(() -> { // 具体逻辑 return "返回内容"; }, ioTaskExecutor); } } ``` 使用异步方法尽量不要使用`future.get()`去获取结果, 会阻塞主线程. 推荐使用下面方式 ```java import org.springframework.scheduling.annotation.Async; public void test() { // 有返回值推荐使用 future.thenAccept() CompletableFuture future1 = asyncTask.asyncTaskResult(5); future1.thenAccept(integer -> log.info("计算完成, 结果是{}", integer)); } ``` 第二种: 使用注解 `@Async` 创建无返回值的线程 ```java @Component public class AsyncTask { @Async("cpuTaskExecutor") public void async() { log.info("正在执行的线程{}", Thread.currentThread().getName()); } } ``` - ### 监控日志打印 具体见 [异步方法监控日志](#async-log)