概要
通过自定义注解 @ApiMethod 结合 Spring 拦截器 ApiHandlerMapping,实现对 /api/** 路径 POST 请求的动态路由拦截,将请求映射到指定业务服务的对应方法,无需编写大量 Controller 层代码,提升接口开发灵活性。
核心组件说明
1. 自定义注解 @ApiMethod
用于标记业务服务中需要对外暴露的 API 方法,通过注解值绑定 API 路径,支持运行时反射获取注解信息。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ApiMethod { String value() default ""; }
|
2. 动态路由处理器 ApiHandlerMapping
核心拦截器,实现 Spring 的 HandlerMapping 接口,专门处理 /api/** 路径的 POST 请求,通过反射+注解匹配,将请求转发到对应业务服务方法,整体流程清晰、扩展性强。
核心处理流程
当 POST 请求 http://域名/api/模块名/方法名 到达时:
ApiHandlerMapping.getHandler() 匹配 /api/ 前缀 + POST 方法,返回自定义处理器 ApiHandler
ApiHandler.handleRequest() 执行核心逻辑:
解析 URI:/api/user/create → 模块名 user、方法名 create
读取 JSON 请求体:通过 Jackson 解析为通用 JsonNode 结构,兼容任意 JSON 格式入参
调用 invokeService():根据模块名匹配 Spring 容器中的业务服务,根据注解匹配对应方法并执行
统一封装响应结果(成功/失败)
读取请求体为 JsonNode
1
| JsonNode requestBody = objectMapper.readTree(request.getInputStream());
|
- 使用 Jackson 将整个 POST body 解析为通用 JSON 树结构
- 无论传 {}、{“name”:”A”} 还是 [] 都能解析(非法 JSON 会抛 IOException)
invokeService 业务处理方式
根据模块名和方法名,从 Spring 容器中找到对应 Service,并调用带 @ApiMethod 注解的方法。
1
| invokeService(String moduleName, String methodName, JsonNode args)
|
构造 Service Bean 名称
1 2
| String serviceName = moduleName + "ApiService"; Object service = applicationContext.getBean(serviceName);
|
查找 @ApiMethod 注解, 没有注解时寻找内部方法名相同的接口
1
| ApiMethod ann = getApiMethodAnnotation(method, serviceClass);
|
writeError 统一封装 错误提醒,往外丢出
1
| private void writeError(HttpServletResponse response, String message, int status) throws IOException
|
完整实现代码(优化排版+注释)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
| import 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;
@Order(1) public class ApiHandlerMapping implements HandlerMapping, ApplicationContextAware {
private ApplicationContext applicationContext; private final ObjectMapper objectMapper = new ObjectMapper();
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
@Override public HandlerExecutionChain getHandler(HttpServletRequest request) { String uri = request.getRequestURI(); String method = request.getMethod(); System.out.println("📝 接收到请求:URI=" + uri + ", Method=" + method); 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 { 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]; String methodName = pathParts[1];
JsonNode requestBody = objectMapper.readTree(request.getInputStream());
Object businessResult = invokeService(moduleName, methodName, requestBody);
response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_OK); objectMapper.writeValue( response.getWriter(), Map.of("code", 200, "data", businessResult, "msg", "操作成功") );
} catch (Exception e) { writeError(response, "接口调用失败:" + e.getMessage(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } }
private Object invokeService(String moduleName, String methodName, JsonNode args) throws Exception { String serviceBeanName = moduleName + "ApiService"; Object serviceBean = applicationContext.getBean(serviceBeanName);
if (serviceBean == null) { throw new IllegalArgumentException("未找到指定的业务服务:" + serviceBeanName); }
Class<?> serviceClass = serviceBean.getClass(); 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 + "\")的方法"); }
private ApiMethod getApiMethodAnnotation(Method method, Class<?> targetClass) { ApiMethod annotation = method.getAnnotation(ApiMethod.class); if (annotation != null) { return annotation; }
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; } }
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; }
private void writeError(HttpServletResponse response, String message, int status) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(status); objectMapper.writeValue( response.getWriter(), Map.of("code", status, "error", message, "msg", "操作失败") ); } } }
|
使用方式
在业务服务接口/实现类中添加 @ApiMethod 注解,注解值与 API 路径中的方法名对应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import com.fasterxml.jackson.databind.JsonNode;
public interface McpApiService {
@ApiMethod("create") Object createUser(JsonNode args);
@ApiMethod("getuser") Object selectUser(JsonNode args); }
|
调用示例
请求地址:POST /api/mcp/create
请求体:
1 2 3 4 5
| { "username": "test", "password": "123456" }
|
响应示例:
1 2 3 4 5 6 7 8 9
| { "code": 200, "data": { "userId": 1001, "username": "test" }, "msg": "操作成功" }
|
无侵入式路由:无需编写 Controller,通过注解直接绑定业务方法与 API 路径,减少冗余代码;
通用参数解析:基于 JsonNode 接收任意 JSON 格式参数,适配不同业务场景;
注解穿透获取:支持接口、父类的注解继承,兼容不同编码风格的服务实现;
统一响应封装:成功/失败响应格式标准化,异常信息解包暴露真实原因,便于排查问题;
灵活扩展:可通过扩展 getApiMethodAnnotation 方法,支持更多注解匹配规则,或增加参数校验、权限控制等逻辑。