背景

本文是《Java 后端从小白到大神》修仙系列之框架学习,Spring Cloud 微服务实战系列的 第八篇。Spring Cloud 是构建微服务架构的基石,拥有完整的服务治理生态,在云原生架构中广泛应用。本系列将从架构认知到实际工程,逐步构建一套企业级 Spring Cloud 微服务项目。若想详细学习请点击首篇博文开始,现在开始学习。

文章概览

统一异常处理 + 日志体系 + 自定义 Starter 封装:

  1. ControllerAdvice 全局异常封装标准
  2. 通用响应体结构(Result / ResponseWrapper)
  3. 日志体系设计:info / warn / error + 请求体打印
  4. 编写自定义 Starter(切面 + 注解 +配置自动装配)
  5. 将日志注解式管理(如 @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 模块实现跨服务复用。这种结构在企业项目中极为常见,是高质量微服务体系的重要支撑。