
概要
通过自定义注解 @ApiMethod 结合 Spring 拦截器 ApiHandlerMapping,实现对 /api/** 路径 POST 请求的动态路由拦截,将请求映射到指定业务服务的对应方法,无需编写大量 Controller 层代码,提升接口开发灵活性。
核心组件说明
1. 自定义注解 @ApiMethod
用于标记业务服务中需要对外暴露的 API 方法,通过注解值绑定 API 路径,支持运行时反射获取注解信息。
java
import 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 方法,返回自定义处理器ApiHandler -
ApiHandler.handleRequest()执行核心逻辑:-
解析 URI:
/api/user/create→ 模块名user、方法名create -
读取 JSON 请求体:通过 Jackson 解析为通用
JsonNode结构,兼容任意 JSON 格式入参 -
调用
invokeService():根据模块名匹配 Spring 容器中的业务服务,根据注解匹配对应方法并执行 -
统一封装响应结果(成功/失败)
-
读取请求体为 JsonNode
java
JsonNode requestBody = objectMapper.readTree(request.getInputStream());
- 使用 Jackson 将整个 POST body 解析为通用 JSON 树结构
- 无论传 {}、{"name":"A"} 还是 [] 都能解析(非法 JSON 会抛 IOException)
invokeService 业务处理方式 根据模块名和方法名,从 Spring 容器中找到对应 Service,并调用带 @ApiMethod 注解的方法。
java
invokeService(String moduleName, String methodName, JsonNode args)
构造 Service Bean 名称
java
String serviceName = moduleName + "ApiService";
Object service = applicationContext.getBean(serviceName);
查找 @ApiMethod 注解, 没有注解时寻找内部方法名相同的接口
java
ApiMethod ann = getApiMethodAnnotation(method, serviceClass);
writeError 统一封装 错误提醒,往外丢出
java
private void writeError(HttpServletResponse response, String message, int status) throws IOException
完整实现代码(优化排版+注释)
java
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;
/**
* 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;charset=UTF-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;charset=UTF-8");
response.setStatus(status);
objectMapper.writeValue(
response.getWriter(),
Map.of("code", status, "error", message, "msg", "操作失败")
);
}
}
}
使用方式
在业务服务接口/实现类中添加 @ApiMethod 注解,注解值与 API 路径中的方法名对应:
java
import 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方法,支持更多注解匹配规则,或增加参数校验、权限控制等逻辑。