背景
本文是《Java 后端从小白到大神》修仙系列之框架学习,Spring Cloud 微服务实战系列的 第八篇
。Spring Cloud 是构建微服务架构的基石,拥有完整的服务治理生态,在云原生架构中广泛应用。本系列将从架构认知到实际工程,逐步构建一套企业级 Spring Cloud 微服务项目。若想详细学习请点击首篇博文开始,现在开始学习。
文章概览
统一异常处理 + 日志体系 + 自定义 Starter 封装:
- ControllerAdvice 全局异常封装标准
- 通用响应体结构(Result / ResponseWrapper)
- 日志体系设计:info / warn / error + 请求体打印
- 编写自定义 Starter(切面 + 注解 +配置自动装配)
- 将日志注解式管理(如 @TaskLog)并统一落库
本篇聚焦于微服务中的“统一异常处理 + 日志体系封装”,并首次引入 自定义 Starter
概念,提升工程复用度。
1. 全局异常处理机制
在每个服务中通过 @RestControllerAdvice
+ @ExceptionHandler
方式,统一异常响应结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("系统异常:", e);
return Result.fail("系统开小差了,请稍后重试");
}
@ExceptionHandler(CustomException.class)
public Result handleCustom(CustomException e) {
return Result.fail(e.getCode(), e.getMessage());
}
}
|
其中 Result
是统一返回结果结构,后续介绍。
2. 通用响应体结构
定义一个标准返回体 Result<T>
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
public static <T> Result<T> fail(String message) {
return new Result<>(500, message, null);
}
public static <T> Result<T> fail(Integer code, String message) {
return new Result<>(code, message, null);
}
}
|
- 所有接口返回均采用该结构封装
- 异常处理器中调用 fail 构造失败响应
3. 日志统一结构输出 + MDC 自动注入
- 使用 MDC 自动注入用户ID、IP、TraceId 等上下文信息。
- 在 Filter 中实现注入:
1
2
|
MDC.put("userId", userContext.getUserId());
MDC.put("traceId", tracer.currentSpan().context().traceId());
|
4. 注解式日志 + AOP 实现
1
2
3
4
5
|
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TaskLog {
String value();
}
|
通过切面实现请求入参、出参日志打印:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Slf4j
@Aspect
@Component
public class TaskLogAspect {
@Pointcut("@annotation(com.yutao.annotation.TaskLog)")
public void logPointcut() {}
@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
log.info("【请求开始】方法: {} 参数: {}", joinPoint.getSignature(), joinPoint.getArgs());
Object result = joinPoint.proceed();
log.info("【请求结束】返回: {},耗时: {}ms", result, System.currentTimeMillis() - start);
return result;
}
}
|
通过 @TaskLog
即可开启日志打印。
5. 自定义 Starter 模块封装
模块名称:starter-task-common
目录结构:
1
2
3
4
5
|
starter-task-common
├── annotation/TaskLog.java
├── aspect/TaskLogAspect.java
├── config/TaskLogAutoConfiguration.java
├── resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
|
配置类自动注入:
1
2
3
4
5
6
7
8
|
@Configuration
@ConditionalOnClass(TaskLogAspect.class)
public class TaskLogAutoConfiguration {
@Bean
public TaskLogAspect taskLogAspect() {
return new TaskLogAspect();
}
}
|
resources 下添加:
1
2
|
# 文件路径:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.yutao.config.TaskLogAutoConfiguration
|
只需引入 starter 依赖,即可在其他服务中使用 @TaskLog
。
使用演示
在 user-service 中引入 starter:
1
2
3
4
5
|
<dependency>
<groupId>com.yutao</groupId>
<artifactId>starter-task-common</artifactId>
<version>1.0.0</version>
</dependency>
|
Controller 中使用注解:
1
2
3
4
5
|
@GetMapping("/info")
@TaskLog
public Result<UserDTO> getInfo() {
return Result.success(new UserDTO(1L, "Tom"));
}
|
运行日志示例:
1
2
|
【请求开始】方法: UserController.getInfo 参数: []
【请求结束】返回: Result{code=200, message='操作成功', data=UserDTO{id=1, name='Tom'}},耗时: 12ms
|
总结
至此完成了异常、响应体、日志切面封装,并通过 Starter 模块实现跨服务复用。这种结构在企业项目中极为常见,是高质量微服务体系的重要支撑。