Spring Boot 多模块自动装配

问题背景

在微服务架构中,通用代码(如全局异常处理、统一响应封装)放在 common 模块,其他服务通过 Maven 依赖引入。

现象

  • ✅ 正常响应:按预期格式返回
  • ❌ 异常响应:返回 Spring Boot 默认错误格式,自定义全局异常处理器未生效
// 期望的错误响应
{
    "code": 40001,
    "message": "用户不存在",
    "data": null,
    "timestamp": 1764591719827
}

// 实际返回(Spring Boot 默认格式)
{
    "timestamp": "2025-12-01T12:19:56.458+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "user not found",
    "path": "/user/login"
}

根本原因

Spring Boot 默认组件扫描规则

@SpringBootApplication 默认只扫描 启动类所在包及其子包

com.canvaschain.user           ← 启动类所在包(会扫描)
├── controller/
├── service/
└── UserServiceApplication.java

com.canvaschain.common         ← 不在扫描范围内!
├── exception/
│   └── GlobalExceptionHandler.java  ← 未被加载
└── config/

结论GlobalExceptionHandler 没有被 Spring 容器管理,所以不生效。


解决方案

方案一:手动 ComponentScan(不推荐)

@SpringBootApplication
@ComponentScan(basePackages = {"com.canvaschain.user", "com.canvaschain.common"})
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

缺点

  • 每个服务都需要手动添加
  • 容易遗漏,维护成本高

方案二:Spring Boot 自动装配(推荐)

1. 创建自动配置类

common 模块中创建:

package com.canvaschain.common.config;

import com.canvaschain.common.exception.GlobalExceptionHandler;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Slf4j
@Configuration
@Import({
    GlobalExceptionHandler.class,
    // 可以继续添加其他需要自动装配的组件
})
public class CommonAutoConfiguration {

    @PostConstruct
    public void init() {
        log.info("✅ CommonAutoConfiguration 已加载");
    }
}

2. 配置自动装配文件

根据 Spring Boot 版本选择:

Spring Boot 2.7 及以下

创建文件:src/main/resources/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.canvaschain.common.config.CommonAutoConfiguration

Spring Boot 3.x

创建文件:src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.canvaschain.common.config.CommonAutoConfiguration
image-b14f6488

3. 目录结构

common/
├── pom.xml
└── src/main/
    ├── java/com/canvaschain/common/
    │   ├── config/
    │   │   └── CommonAutoConfiguration.java
    │   ├── exception/
    │   │   ├── BusinessException.java
    │   │   ├── GlobalExceptionHandler.java
    │   │   └── ThrowUtils.java
    │   ├── response/
    │   │   ├── BaseResponse.java
    │   │   ├── ErrorCode.java
    │   │   └── ResultUtils.java
    │   └── ...
    └── resources/
        └── META-INF/
            ├── spring.factories                           # Spring Boot 2.x
            └── spring/
                └── org.springframework.boot.autoconfigure.AutoConfiguration.imports  # Spring Boot 3.x

4. 使用方服务(无需任何额外配置)

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

只需在 pom.xml 引入依赖即可自动生效:

<dependency>
    <groupId>com.canvaschain</groupId>
    <artifactId>common</artifactId>
    <version>${project.version}</version>
</dependency>

自动装配原理

┌─────────────────────────────────────────────────────────────┐
│                    Spring Boot 启动流程                      │
├─────────────────────────────────────────────────────────────┤
│  1. @SpringBootApplication 触发自动配置                       │
│                      ↓                                       │
│  2. 扫描所有 jar 包中的 META-INF/spring.factories            │
│     或 META-INF/spring/...AutoConfiguration.imports         │
│                      ↓                                       │
│  3. 加载配置的 AutoConfiguration 类                          │
│                      ↓                                       │
│  4. @Import 导入的组件被注册到 Spring 容器                    │
│                      ↓                                       │
│  5. GlobalExceptionHandler 生效                              │
└─────────────────────────────────────────────────────────────┘

验证方法

1. 查看启动日志

✅ CommonAutoConfiguration 已加载

2. 检查 Bean 是否注册

@SpringBootTest
class AutoConfigTest {

    @Autowired
    private ApplicationContext context;

    @Test
    void testGlobalExceptionHandlerLoaded() {
        boolean exists = context.containsBean("globalExceptionHandler");
        assertTrue(exists, "GlobalExceptionHandler 应该被自动装配");
    }
}

3. 测试异常响应

curl -X POST http://localhost:8080/user/login \
  -H "Content-Type: application/json" \
  -d '{"username":"notexist","password":"123456"}'

期望返回:

{
    "code": 40004,
    "message": "用户不存在",
    "data": null,
    "timestamp": 1764591719827
}

方案对比

特性 手动 ComponentScan 自动装配(spring.factories)
配置方式 每个服务手动配置 一次配置,自动生效
维护成本 高,容易遗漏 低,对使用方透明
扩展性 好,类似 Spring Boot Starter
推荐度

常见问题

Q1: 两个配置文件都需要吗?

不需要,根据 Spring Boot 版本选择一个:

  • Spring Boot 2.7 及以下:spring.factories
  • Spring Boot 3.x:AutoConfiguration.imports
  • 兼容两个版本:两个文件都配置

Q2: 自动装配不生效怎么排查?

  1. 检查配置文件路径是否正确
  2. 检查类的全限定名是否正确
  3. 检查 common 模块是否被正确打包(mvn clean install
  4. 启动时添加 --debug 查看自动配置报告
java -jar app.jar --debug

Q3: 如何有条件地启用自动配置?

@Configuration
@ConditionalOnProperty(name = "common.exception.enabled", havingValue = "true", matchIfMissing = true)
@Import(GlobalExceptionHandler.class)
public class CommonAutoConfiguration {
}

参考资料


项目分区导航:⬅️ 02-异常处理最佳实践代码 | 03-Spring Boot 多模块自动装配 | ➡️ 00-接口文档