实战:软件架构设计之【Spring Boot 动态注册Controller:实现Mock Server】

概叙

科普文:HTTP协议【GET、DELETE一定没有请求体RequestBody吗?】-CSDN博客

科普文:软件架构设计之【Spring MVC小结】-CSDN博客

科普文:软件架构设计之【Spring Boot 动态注册Controller】-CSDN博客

前面梳理了Spring Boot 动态注册Controller,这里我们来实现一个Mock Server,体验一下动态注册Controller。

Mock Server在测试过程中快速创建临时的API端点,模拟各种响应,而不需要预先在代码中定义这些Controller。这对于测试不同的场景、异常情况或者第三方服务不可用时非常有用。

Mock Server应用场景

  1. 第三方接口模拟:在开发阶段模拟支付、短信等第三方接口,避免依赖外部服务不稳定。

  2. 前端联调:后端未完成时,前端可通过 Mock Server 快速获取模拟数据。

  3. 自动化测试:在单元测试或集成测试中动态创建接口,验证系统对不同响应的处理逻辑。

Mock Server实现步骤

具体实现步骤,可能需要以下流程:

  1. 用户通过某种方式(如REST API或配置文件)定义需要模拟的接口信息,包括路径、HTTP方法、响应体、状态码等。
  2. Mock Server接收到这些信息后,动态生成一个Controller类,或者使用已有的模板类,配置相应的处理方法。
  3. 使用BeanDefinitionRegistry注册这个Controller的Bean到Spring容器中。
  4. 通过RequestMappingHandlerMapping注册路由映射,使得该路径和方法可以被访问。
  5. 当测试完成后,可能需要注销这些动态注册的Controller,释放资源,避免污染后续测试环境。

Mock Server注意问题

在实现过程中,需要注意几个问题:

  • 线程安全‌:动态注册和注销Bean可能在多线程环境下发生,需要确保操作的原子性,避免并发冲突。
  • 路由冲突‌:需要检查新注册的路径和方法是否与现有Controller冲突,避免覆盖或重复。
  • 内存管理‌:动态生成的Controller如果未被正确注销,可能会导致内存泄漏,需要确保在不需要时及时清理。
  • 响应灵活性‌:可能需要支持动态响应,比如根据请求参数返回不同的结果,或者引入延迟模拟网络延迟。

此外,可能需要考虑如何让用户方便地配置这些Mock接口。例如,提供一个REST接口,允许用户发送一个POST请求来创建一个新的Mock端点,包括路径、方法、响应体等信息。或者使用配置文件,在启动时加载多个预定义的Mock接口。

动态注册 Controller 实现 Mock Server 的步骤与代码示例

1. 定义 Mock 接口配置模型

创建配置类,描述需要模拟的接口属性:

public class MockApiConfig implements Serializable {

    private static final long serialVersionUID = 1L;

    private String path;        // 请求路径,如 "/api/user"
    private RequestMethod method;  // HTTP 方法,如 GET、POST
    private String response;    // 响应内容(JSON/XML等)
    private int statusCode;     // HTTP 状态码,如 200、404

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public RequestMethod getMethod() {
        return method;
    }

    public void setMethod(RequestMethod method) {
        this.method = method;
    }

    public String getResponse() {
        return response;
    }

    public void setResponse(String response) {
        this.response = response;
    }

    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }
}

2. 创建动态 Controller 管理类

编写管理类,处理 Mock 接口的注册与注销:

@Component
public class MockControllerManager {
    @Autowired
    private GenericApplicationContext context;
    @Autowired
    private RequestMappingHandlerMapping handlerMapping;

    // 存储已注册的 Mock Controller Bean 名称
    private final Set<String> registeredBeans = new HashSet<>();

    /**
     * 注册 Mock 接口
     */
    public void registerMockApi(MockApiConfig config) throws Exception {
        String beanName = "mockController_" + UUID.randomUUID().toString().replace("-", "");

        // 动态注册 Controller Bean
        context.registerBean(beanName, MockController.class, () -> new MockController(config));
        registeredBeans.add(beanName);

        // 获取 Controller 实例
        MockController controller = context.getBean(beanName, MockController.class);

        // 注册路由映射
        RequestMappingInfo mappingInfo = RequestMappingInfo
                .paths(config.getPath())
                .methods(config.getMethod())
                .options(getDefaultConfig())  // 显式配置路径解析规则   Expected lookupPath in request attribute "org.springframework.web.util.UrlPathHelper.PATH".
                .build();
        Method handleMethod = MockController.class.getMethod("handle");
        handlerMapping.registerMapping(mappingInfo, controller, handleMethod);
    }

    /**
     * 注销所有 Mock 接口
     */
    public void unregisterAllMocks() {
        registeredBeans.forEach(beanName -> {
            context.removeBeanDefinition(beanName);
            handlerMapping.getHandlerMethods().forEach((info, handlerMethod) -> {
                if (handlerMethod.getBean().equals(beanName)) {
                    handlerMapping.unregisterMapping(info);
                }
            });
        });
        registeredBeans.clear();
    }

    // 显式配置路径解析规则(解决路径解析冲突)
    //在构建 RequestMappingInfo 时显式注入 UrlPathHelper 和 AntPathMatcher
    private RequestMappingInfo.BuilderConfiguration getDefaultConfig() {
        RequestMappingInfo.BuilderConfiguration config =
                new RequestMappingInfo.BuilderConfiguration();
        //动态注册时同步全局配置‌
        //在动态注册接口的代码中,‌无需手动创建 UrlPathHelper‌,直接复用全局配置:
        // 直接复用全局配置的 PathMatcher 和 PatternParser
        config.setPatternParser(new PathPatternParser());  // Spring 5.3+ 推荐模式
        // 显式配置路径匹配规则
        config.setPathMatcher(new AntPathMatcher());
        return config;
    }
}

3. 实现动态 Controller 逻辑

编写动态 Controller 类,处理请求并返回配置的响应:

public class MockController {
    private final MockApiConfig config;

    public MockController(MockApiConfig config) {
        this.config = config;
    }

    public ResponseEntity<String> handle() {
        // 返回预设的响应内容与状态码
        return ResponseEntity
                .status(config.getStatusCode())
                .contentType(MediaType.APPLICATION_JSON)
                .body(config.getResponse());
    }
}

4. 提供 REST 接口管理 Mock 接口

通过 Controller 暴露接口,供用户动态添加/删除 Mock 接口:

@RestController
@RequestMapping("/mock-admin")
public class MockAdminController {
    @Autowired
    private MockControllerManager manager;

    @PostMapping("/add")
    public ApiResult addMockApi(@RequestBody MockApiConfig config) throws Exception {
        manager.registerMockApi(config);
        return ApiResult.success(config);
    }

    @PostMapping("/clearAll")
    public ApiResult clearAllMocks() {
        manager.unregisterAllMocks();
        return ApiResult.success("所有 Mock 接口已注销");
    }
}

5. 测试 Mock Server

使用工具(如 Postman 或 curl)测试动态接口:

1.注册 Mock 接口

2.访问 Mock 接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

01Byte空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值