基于注解+拦截器的API动态路由实现方案

📅 发布时间:2026/7/5 14:42:08 👁️ 浏览次数:
基于注解+拦截器的API动态路由实现方案
概要通过自定义注解 ApiMethod 结合 Spring 拦截器 ApiHandlerMapping实现对 /api/** 路径 POST 请求的动态路由拦截将请求映射到指定业务服务的对应方法无需编写大量 Controller 层代码提升接口开发灵活性。核心组件说明1. 自定义注解 ApiMethod用于标记业务服务中需要对外暴露的 API 方法通过注解值绑定 API 路径支持运行时反射获取注解信息。javaimport java.lang.annotation.*; /** * API方法绑定注解 * 用于标记业务服务中可被/api/**路径调用的方法 */ Retention(RetentionPolicy.RUNTIME) // 运行时保留支持反射获取 Target(ElementType.METHOD) // 仅作用于方法 public interface ApiMethod { /** 绑定的API子路径如create、getuser */ String value() default ; }2. 动态路由处理器 ApiHandlerMapping核心拦截器实现 Spring 的 HandlerMapping 接口专门处理 /api/** 路径的 POST 请求通过反射注解匹配将请求转发到对应业务服务方法整体流程清晰、扩展性强。核心处理流程当 POST 请求 http://域名/api/模块名/方法名 到达时ApiHandlerMapping.getHandler() 匹配 /api/ 前缀 POST 方法返回自定义处理器 ApiHandlerApiHandler.handleRequest() 执行核心逻辑解析 URI/api/user/create → 模块名 user、方法名 create读取 JSON 请求体通过 Jackson 解析为通用 JsonNode 结构兼容任意 JSON 格式入参调用 invokeService()根据模块名匹配 Spring 容器中的业务服务根据注解匹配对应方法并执行统一封装响应结果成功/失败读取请求体为 JsonNodejavaJsonNode requestBody objectMapper.readTree(request.getInputStream());使用 Jackson 将整个 POST body 解析为通用 JSON 树结构无论传 {}、{name:A} 还是 [] 都能解析非法 JSON 会抛 IOExceptioninvokeService 业务处理方式根据模块名和方法名从 Spring 容器中找到对应 Service并调用带 ApiMethod 注解的方法。javainvokeService(String moduleName, String methodName, JsonNode args)构造 Service Bean 名称javaString serviceName moduleName ApiService; Object service applicationContext.getBean(serviceName);查找 ApiMethod 注解, 没有注解时寻找内部方法名相同的接口javaApiMethod ann getApiMethodAnnotation(method, serviceClass);writeError 统一封装 错误提醒往外丢出javaprivate void writeError(HttpServletResponse response, String message, int status) throws IOException完整实现代码优化排版注释javaimport com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.HttpRequestHandler; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; /** * API动态路由处理器 * 拦截/api/**的POST请求动态路由到对应业务服务的ApiMethod注解方法 * Order(1) 保证优先于默认HandlerMapping执行 */ Order(1) public class ApiHandlerMapping implements HandlerMapping, ApplicationContextAware { // Spring容器上下文用于获取业务服务Bean private ApplicationContext applicationContext; // Jackson JSON解析工具全局单例 private final ObjectMapper objectMapper new ObjectMapper(); Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext applicationContext; } /** * 核心拦截逻辑匹配/api/**的POST请求 */ Override public HandlerExecutionChain getHandler(HttpServletRequest request) { String uri request.getRequestURI(); String method request.getMethod(); System.out.println( 接收到请求URI uri , Method method); // 仅处理/api/前缀的POST请求其他请求交给Spring默认处理器 if (uri.startsWith(/api/) POST.equalsIgnoreCase(method)) { return new HandlerExecutionChain(new ApiHandler()); } return null; } /** * 内部请求处理器 * 负责解析请求、调用业务方法、封装响应 */ class ApiHandler implements HttpRequestHandler { Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { try { // 1. 解析API路径/api/模块名/方法名 → 拆分模块和方法 String pathInfo request.getRequestURI().substring(/api/.length()); String[] pathParts pathInfo.split(/, 2); if (pathParts.length ! 2) { writeError(response, 无效的API路径格式正确格式/api/模块名/方法名, 400); return; } String moduleName pathParts[0]; // 模块名如user String methodName pathParts[1]; // 方法名如create // 2. 读取JSON请求体兼容任意JSON格式非法JSON会抛出IOException JsonNode requestBody objectMapper.readTree(request.getInputStream()); // 3. 调用业务服务方法 Object businessResult invokeService(moduleName, methodName, requestBody); // 4. 封装成功响应 response.setContentType(application/json;charsetUTF-8); response.setStatus(HttpServletResponse.SC_OK); objectMapper.writeValue( response.getWriter(), Map.of(code, 200, data, businessResult, msg, 操作成功) ); } catch (Exception e) { // 5. 统一异常处理封装错误响应 writeError(response, 接口调用失败 e.getMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } /** * 调用业务服务方法 * param moduleName 模块名对应服务Bean名模块名 ApiService * param methodName API方法名对应ApiMethod注解值 * param args JSON请求参数 * return 业务方法执行结果 * throws Exception 执行异常 */ private Object invokeService(String moduleName, String methodName, JsonNode args) throws Exception { // 构造服务Bean名称如user → userApiService String serviceBeanName moduleName ApiService; Object serviceBean applicationContext.getBean(serviceBeanName); // 防御性检查服务Bean不存在则抛出异常 if (serviceBean null) { throw new IllegalArgumentException(未找到指定的业务服务 serviceBeanName); } Class? serviceClass serviceBean.getClass(); // 遍历服务类所有方法匹配ApiMethod注解 for (Method method : serviceClass.getMethods()) { // 穿透获取注解当前方法→接口方法→父类方法 ApiMethod apiMethodAnn getApiMethodAnnotation(method, serviceClass); // 注解值匹配则执行方法 if (apiMethodAnn ! null methodName.equals(apiMethodAnn.value())) { method.setAccessible(true); // 允许访问私有/保护方法 try { return method.invoke(serviceBean, args); // 执行方法并返回结果 } catch (InvocationTargetException e) { // 解包目标异常暴露真实业务异常信息 throw new Exception(方法执行失败 e.getTargetException().getMessage(), e.getTargetException()); } catch (IllegalAccessException e) { throw new Exception(方法访问权限不足 method.getName(), e); } } } throw new IllegalArgumentException(服务[ serviceBeanName ]中未找到绑定ApiMethod(\ methodName \)的方法); } /** * 穿透获取ApiMethod注解支持接口/父类注解继承 * param method 目标方法 * param targetClass 目标类 * return ApiMethod注解无则返回null */ private ApiMethod getApiMethodAnnotation(Method method, Class? targetClass) { // 1. 优先获取当前方法的注解 ApiMethod annotation method.getAnnotation(ApiMethod.class); if (annotation ! null) { return annotation; } // 2. 获取实现接口中方法的注解 for (Class? interfaceClass : targetClass.getInterfaces()) { try { Method interfaceMethod interfaceClass.getMethod(method.getName(), method.getParameterTypes()); annotation interfaceMethod.getAnnotation(ApiMethod.class); if (annotation ! null) { return annotation; } } catch (NoSuchMethodException e) { continue; // 接口无此方法跳过 } } // 3. 获取父类方法的注解可选 if (targetClass.getSuperclass() ! null targetClass.getSuperclass() ! Object.class) { try { Method superClassMethod targetClass.getSuperclass().getMethod(method.getName(), method.getParameterTypes()); annotation superClassMethod.getAnnotation(ApiMethod.class); if (annotation ! null) { return annotation; } } catch (NoSuchMethodException e) { // 父类无此方法忽略 } } return null; } /** * 统一错误响应封装 * param response 响应对象 * param message 错误信息 * param status HTTP状态码 * throws IOException 写入响应异常 */ private void writeError(HttpServletResponse response, String message, int status) throws IOException { response.setContentType(application/json;charsetUTF-8); response.setStatus(status); objectMapper.writeValue( response.getWriter(), Map.of(code, status, error, message, msg, 操作失败) ); } } }使用方式在业务服务接口/实现类中添加 ApiMethod 注解注解值与 API 路径中的方法名对应javaimport com.fasterxml.jackson.databind.JsonNode; /** * 用户业务API服务接口 * Bean名称mcpApiService模块名mcp ApiService */ public interface McpApiService { /** * 创建用户接口 * 对应API路径/api/mcp/create */ ApiMethod(create) Object createUser(JsonNode args); /** * 查询用户接口 * 对应API路径/api/mcp/getuser */ ApiMethod(getuser) Object selectUser(JsonNode args); }调用示例请求地址POST /api/mcp/create请求体JSON{ username: test, password: 123456 }响应示例JSON{ code: 200, data: { userId: 1001, username: test }, msg: 操作成功 }无侵入式路由无需编写 Controller通过注解直接绑定业务方法与 API 路径减少冗余代码通用参数解析基于 JsonNode 接收任意 JSON 格式参数适配不同业务场景注解穿透获取支持接口、父类的注解继承兼容不同编码风格的服务实现统一响应封装成功/失败响应格式标准化异常信息解包暴露真实原因便于排查问题灵活扩展可通过扩展 getApiMethodAnnotation 方法支持更多注解匹配规则或增加参数校验、权限控制等逻辑。