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

概要

通过自定义注解 @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/模块名/方法名 到达时:

  1. ApiHandlerMapping.getHandler() 匹配 /api/ 前缀 + POST 方法,返回自定义处理器 ApiHandler

  2. 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 方法,支持更多注解匹配规则,或增加参数校验、权限控制等逻辑。

相关推荐
None3212 小时前
【NestJs】基于Redlock装饰器分布式锁设计与实现
后端·node.js
初次攀爬者2 小时前
Kafka + KRaft模式架构基础介绍
后端·kafka
洛森唛2 小时前
Elasticsearch DSL 查询语法大全:从入门到精通
后端·elasticsearch
拳打南山敬老院3 小时前
Context 不是压缩出来的,而是设计出来的
前端·后端·aigc
初次攀爬者3 小时前
Kafka + ZooKeeper架构基础介绍
后端·zookeeper·kafka
LucianaiB3 小时前
Openclaw 安装使用保姆级教程(最新版)
后端
华仔啊3 小时前
Stream 代码越写越难看?JDFrame 让 Java 逻辑回归优雅
java·后端
ray_liang3 小时前
用六边形架构与整洁架构对比是伪命题?
java·架构
哈密瓜的眉毛美3 小时前
零基础学Java|第五篇:进制转换与位运算、原码反码补码
后端