如何在Spring Boot REST API多模块中复用同一模型类
这是Spring Boot中复用REST请求模型非常典型的场景,我结合你的需求给出几个落地性强的方案,你可以根据实际场景选择:
核心思路是把固定的公共字段(query)和可变的参数部分(variables)拆分,用泛型来适配不同的参数结构,既保证类型安全,又能最大化复用。
步骤1:定义通用请求父类
创建一个泛型类来封装所有请求都有的query和泛型类型的variables:
import lombok.Data; @Data public class GenericGraphQLRequest<T> { private String query; private T variables; }
这里用了Lombok的@Data来简化getter/setter/toString等方法,如果你没用到Lombok,手动生成这些方法即可。
步骤2:定义各接口专属的Variables类
针对你给出的3种请求场景,分别创建对应的Variables模型:
// 对应第一个请求:包含input(name/description/gitUrl/repoName) @Data public class CreateRepoVariables { private RepoInput input; @Data public static class RepoInput { private String name; private String description; private String gitUrl; private String repoName; } } // 对应第二个请求:包含input(仅name) @Data public class UpdateNameVariables { private NameInput input; @Data public static class NameInput { private String name; } } // 对应第三个请求:包含search字段 @Data public class SearchVariables { private String search; }
步骤3:在接口中使用泛型请求类
在你的Controller方法里,直接指定对应的泛型类型即可:
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class RepoController { @PostMapping("/create-repo") public String createRepo(@RequestBody GenericGraphQLRequest<CreateRepoVariables> request) { // 可以直接获取query和variables里的参数 String query = request.getQuery(); String repoName = request.getVariables().getInput().getRepoName(); // 业务逻辑... return "Created repo: " + repoName; } @PostMapping("/update-name") public String updateName(@RequestBody GenericGraphQLRequest<UpdateNameVariables> request) { String name = request.getVariables().getInput().getName(); // 业务逻辑... return "Updated name to: " + name; } @PostMapping("/search") public String search(@RequestBody GenericGraphQLRequest<SearchVariables> request) { String searchKeyword = request.getVariables().getSearch(); // 业务逻辑... return "Searching for: " + searchKeyword; } }
这样每个接口只需要关注自己需要的参数结构,公共的query字段完全复用,而且Spring Boot的Jackson会自动正确解析JSON请求体。
如果你的场景更复杂(比如同一个接口可能接收多种不同的variables结构),可以用Jackson的多态注解来实现动态类型解析。
步骤1:定义Variables父类并添加多态注解
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import lombok.Data; @Data @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type" // 通过type字段来区分不同的variables类型 ) @JsonSubTypes({ @JsonSubTypes.Type(value = CreateRepoVariables.class, name = "create"), @JsonSubTypes.Type(value = UpdateNameVariables.class, name = "update"), @JsonSubTypes.Type(value = SearchVariables.class, name = "search") }) public abstract class BaseVariables { // 公共字段可以放在这里,如果有的话 }
步骤2:让各Variables类继承父类
比如修改CreateRepoVariables:
@Data public class CreateRepoVariables extends BaseVariables { private RepoInput input; @Data public static class RepoInput { private String name; private String description; private String gitUrl; private String repoName; } }
其他两个类同理继承BaseVariables。
步骤3:接口中使用父类接收请求
@PostMapping("/multi-operation") public String multiOperation(@RequestBody GenericGraphQLRequest<BaseVariables> request) { BaseVariables variables = request.getVariables(); if (variables instanceof CreateRepoVariables) { // 处理创建逻辑 } else if (variables instanceof UpdateNameVariables) { // 处理更新逻辑 } else if (variables instanceof SearchVariables) { // 处理搜索逻辑 } return "Operation completed"; }
这种方式适合同一个接口需要处理多种请求类型的场景,但需要在请求JSON中额外添加type字段来标识类型,比如:
{ "query": "", "variables": { "type": "create", "input": { "name": "", "description": "", "gitUrl": "", "repoName": "" } } }
如果只是快速搭建原型,不想创建太多类,可以直接用Map<String, Object>作为variables的类型:
@Data public class SimpleGraphQLRequest { private String query; private Map<String, Object> variables; }
然后在接口中获取参数:
@PostMapping("/quick-test") public String quickTest(@RequestBody SimpleGraphQLRequest request) { String search = (String) request.getVariables().get("search"); // 注意:这里需要手动强转,类型不安全,容易出现ClassCastException return "Search keyword: " + search; }
这种方式优点是快速,不需要创建多个模型类,但缺点很明显:类型不安全、没有参数校验、可读性差,不推荐在生产环境使用。
- 如果是不同接口对应不同参数结构,方案1的泛型封装是最优选择,兼顾类型安全和复用性;
- 如果是同一个接口需要处理多种参数类型,方案2的多态处理更合适;
- 方案3仅适合快速原型验证,生产环境不建议使用。
内容的提问来源于stack exchange,提问作者user3274140

