Spring Boot控制器路径复用:多URL共享Person类接口逻辑
可行实现方案
针对你提出的Spring Boot中复用Person相关接口逻辑的需求,以下是几种实用的实现方案:
方案1:基于控制器继承复用公共方法
通过定义抽象父控制器封装所有需要复用的接口方法,不同路径的控制器继承该父类并实现各自的Person获取逻辑,避免重复编写GetMapping注解和业务代码。
// 抽象父控制器:封装公共的Person属性接口 public abstract class AbstractPersonController { // 子类需实现该方法,根据当前请求路径获取对应的Person对象 protected abstract Person getPerson(HttpServletRequest request); @GetMapping("/first-name") public String getFirstName(HttpServletRequest request) { return getPerson(request).getFirstName(); } @GetMapping("/last-name") public String getLastName(HttpServletRequest request) { return getPerson(request).getLastName(); } // 可添加更多需要复用的属性接口,如 /age、/email 等 } // 处理 /person/{id} 路径的控制器 @RestController @RequestMapping("/person/{id}") public class PersonController extends AbstractPersonController { @Autowired private PersonService personService; @Override protected Person getPerson(HttpServletRequest request) { // 从路径变量中提取id,查询Person String personId = (String) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE).get("id"); return personService.getById(personId); } } // 处理 /payment/{id}/payer 路径的控制器 @RestController @RequestMapping("/payment/{id}/payer") public class PaymentPayerController extends AbstractPersonController { @Autowired private PaymentService paymentService; @Override protected Person getPerson(HttpServletRequest request) { String paymentId = (String) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE).get("id"); Payment payment = paymentService.getById(paymentId); return payment.getPayer(); } } // 处理 /payment/{id}/receiver 路径的控制器 @RestController @RequestMapping("/payment/{id}/receiver") public class PaymentReceiverController extends AbstractPersonController { @Autowired private PaymentService paymentService; @Override protected Person getPerson(HttpServletRequest request) { String paymentId = (String) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE).get("id"); Payment payment = paymentService.getById(paymentId); return payment.getReceiver(); } }
此方案下,三个路径/person/{id}/first-name、/payment/{id}/payer/first-name、/payment/{id}/receiver/first-name会自动生效,且所有属性接口逻辑完全复用。
方案2:自定义参数解析器+单控制器多路径映射
通过自定义HandlerMethodArgumentResolver自动根据请求路径解析Person对象,再在单个控制器中映射所有需要支持的路径,统一处理属性接口。
步骤1:自定义参数解析器和注解
// 自定义注解,标记需要注入当前请求对应的Person参数 @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentPerson { } // 参数解析器:根据请求路径自动获取Person对象 @Component public class PersonArgumentResolver implements HandlerMethodArgumentResolver { @Autowired private PersonService personService; @Autowired private PaymentService paymentService; @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType() == Person.class && parameter.hasParameterAnnotation(CurrentPerson.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); Map<String, String> pathVars = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); String requestURI = request.getRequestURI(); if (requestURI.startsWith("/person/")) { return personService.getById(pathVars.get("id")); } else if (requestURI.contains("/payment/")) { Payment payment = paymentService.getById(pathVars.get("id")); return requestURI.contains("/payer/") ? payment.getPayer() : payment.getReceiver(); } throw new IllegalArgumentException("无法解析当前请求的Person对象"); } }
步骤2:注册参数解析器
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private PersonArgumentResolver personArgumentResolver; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(personArgumentResolver); } }
步骤3:编写公共控制器
@RestController @RequestMapping({"/person/{id}", "/payment/{id}/payer", "/payment/{id}/receiver"}) public class PersonCommonController { @GetMapping("/first-name") public String getFirstName(@CurrentPerson Person person) { return person.getFirstName(); } @GetMapping("/last-name") public String getLastName(@CurrentPerson Person person) { return person.getLastName(); } // 其他复用接口... }
此方案无需创建多个控制器,所有路径的属性接口都由同一个控制器处理,参数解析器负责适配不同路径的Person获取逻辑。
方案3:服务层逻辑复用(极简版)
如果不想引入复杂的Spring MVC扩展,可将Person的属性获取逻辑和不同场景的Person查询逻辑抽离到服务层,控制器只需简单调用服务方法即可。
// 服务层:封装所有Person相关的复用逻辑 @Service public class PersonServiceFacade { @Autowired private PersonService personService; @Autowired private PaymentService paymentService; // 从Person路径获取Person public Person getPerson(String personId) { return personService.getById(personId); } // 从Payment路径获取付款人 public Person getPaymentPayer(String paymentId) { return paymentService.getById(paymentId).getPayer(); } // 从Payment路径获取收款人 public Person getPaymentReceiver(String paymentId) { return paymentService.getById(paymentId).getReceiver(); } // 公共属性获取方法 public String getPersonFirstName(Person person) { return person.getFirstName(); } public String getPersonLastName(Person person) { return person.getLastName(); } }
然后在控制器中调用:
@RestController @RequestMapping("/person/{id}") public class PersonController { @Autowired private PersonServiceFacade facade; @GetMapping("/first-name") public String getFirstName(@PathVariable String id) { Person person = facade.getPerson(id); return facade.getPersonFirstName(person); } } @RestController @RequestMapping("/payment/{id}") public class PaymentController { @Autowired private PersonServiceFacade facade; @GetMapping("/payer/first-name") public String getPayerFirstName(@PathVariable String id) { Person payer = facade.getPaymentPayer(id); return facade.getPersonFirstName(payer); } @GetMapping("/receiver/first-name") public String getReceiverFirstName(@PathVariable String id) { Person receiver = facade.getPaymentReceiver(id); return facade.getPersonFirstName(receiver); } }
此方案实现简单,适合逻辑相对简单的场景,核心逻辑都在服务层复用,控制器仅做路由转发。
内容的提问来源于stack exchange,提问作者Rens Pols
相关产品推荐
相关产品推荐

