Spring Boot路径继承与统一路径变量校验方案咨询
解决方案
针对你在Spring Boot 3中遇到的统一校验{project-name}路径变量并注入项目实体的需求,以下是几种符合MVC/Webflux层面的实现方案:
方案一:@ControllerAdvice + @ModelAttribute(推荐MVC使用)
通过全局控制器通知,为指定范围的Controller统一处理project-name的校验与实体注入,无需在每个Controller中重复编写逻辑。
步骤1:定义范围标记(可选)
创建自定义注解,用来标记需要应用该逻辑的Controller:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ProjectScoped { }
步骤2:编写全局处理类
创建全局控制器通知类,拦截所有标记了@ProjectScoped的Controller,处理路径变量并注入项目实体:
@ControllerAdvice(annotations = ProjectScoped.class) public class ProjectGlobalAdvice { @Autowired private ProjectService projectService; @ModelAttribute("project") public Project resolveProject(@PathVariable("project-name") String projectName) { // 校验项目名称合法性,查询项目实体 Project project = projectService.getValidProject(projectName); if (project == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "项目不存在"); } return project; } }
步骤3:在业务Controller中使用
每个业务Controller(a、b、c、d)只需标记@ProjectScoped,并在方法中直接注入项目实体:
@RestController @ProjectScoped @RequestMapping("/projects/{project-name}/a") public class AController { @GetMapping("/details") public ResponseEntity<AEntity> getADetails(@ModelAttribute("project") Project project) { // 直接使用已校验的project实体处理业务 AEntity entity = getADataByProject(project); return ResponseEntity.ok(entity); } }
方案二:RequestMappingHandlerInterceptor(MVC进阶方案)
通过Spring MVC的拦截器,在请求进入Controller前统一处理路径变量,将校验后的项目实体存入请求属性,供后续Controller使用。
步骤1:实现拦截器
@Component public class ProjectInterceptor implements HandlerInterceptor { @Autowired private ProjectService projectService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从路径中提取project-name参数 String projectName = (String) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE).get("project-name"); // 校验并查询项目实体 Project project = projectService.getValidProject(projectName); if (project == null) { response.sendError(HttpStatus.NOT_FOUND.value(), "项目不存在"); return false; } // 将实体存入请求属性,供Controller获取 request.setAttribute("project", project); return true; } }
步骤2:注册拦截器
通过WebMvcConfigurer配置拦截器的生效路径:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private ProjectInterceptor projectInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(projectInterceptor) .addPathPatterns("/projects/**"); // 匹配所有项目相关路径 } }
步骤3:Controller中使用
在业务Controller方法中,通过@RequestAttribute直接获取已校验的项目实体:
@RestController @RequestMapping("/projects/{project-name}/b") public class BController { @GetMapping("/info") public ResponseEntity<BEntity> getBInfo(@RequestAttribute("project") Project project) { BEntity entity = getBDataByProject(project); return ResponseEntity.ok(entity); } }
方案三:Webflux 适配方案
如果使用的是Spring Webflux,可采用以下两种方式:
方式1:@ControllerAdvice + @ModelAttribute
与MVC方案类似,全局通知类同样适用Webflux:
@ControllerAdvice(annotations = ProjectScoped.class) public class ProjectGlobalAdvice { @Autowired private ProjectService projectService; @ModelAttribute("project") public Mono<Project> resolveProject(@PathVariable("project-name") String projectName) { return projectService.getValidProject(projectName) .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "项目不存在"))); } }
业务Controller中直接注入:
@RestController @ProjectScoped @RequestMapping("/projects/{project-name}/c") public class CController { @GetMapping("/data") public Mono<ResponseEntity<CEntity>> getCData(@ModelAttribute("project") Mono<Project> projectMono) { return projectMono.map(project -> { CEntity entity = getCDataByProject(project); return ResponseEntity.ok(entity); }); } }
方式2:HandlerFilterFunction(Webflux拦截器)
@Component public class ProjectFilterFunction implements HandlerFilterFunction<ServerResponse, ServerResponse> { @Autowired private ProjectService projectService; @Override public Mono<ServerResponse> filter(ServerRequest request, HandlerFunction<ServerResponse> next) { String projectName = request.pathVariable("project-name"); return projectService.getValidProject(projectName) .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "项目不存在"))) .flatMap(project -> next.handle(request.attribute("project", project).build())); } }
注册过滤器:
@Configuration public class WebfluxConfig { @Autowired private ProjectFilterFunction projectFilterFunction; @Bean public RouterFunction<ServerResponse> routerFunction() { return RouterFunctions.route() .path("/projects", builder -> builder .nest(path("/{project-name}"), nestedBuilder -> nestedBuilder .add(projectFilterFunction) // 可继续配置各业务路由,或在业务Router中引用过滤器 )) .build(); } }
内容的提问来源于stack exchange,提问作者NickDelta
相关产品推荐
相关产品推荐

