当前位置: 代码网 > it编程>编程语言>Java > 基于Spring注解+拦截器的动态API路由实现方案

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

2026年07月22日 Java 我要评论
概要通过自定义注解 @apimethod 结合 spring 拦截器 apihandlermapping,实现对 /api/** 路径 post 请求的动态路由拦截,将请求映射到指定业务服务的对应方法

概要

通过自定义注解 @apimethod 结合 spring 拦截器 apihandlermapping,实现对 /api/** 路径 post 请求的动态路由拦截,将请求映射到指定业务服务的对应方法,无需编写大量 controller 层代码,提升接口开发灵活性。

核心组件说明

1. 自定义注解 @apimethod

用于标记业务服务中需要对外暴露的 api 方法,通过注解值绑定 api 路径,支持运行时反射获取注解信息。

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

jsonnode requestbody = objectmapper.readtree(request.getinputstream());
  • 使用 jackson 将整个 post body 解析为通用 json 树结构
  • 无论传 {}、{“name”:“a”} 还是 [] 都能解析(非法 json 会抛 ioexception)

invokeservice 业务处理方式
根据模块名和方法名,从 spring 容器中找到对应 service,并调用带 @apimethod 注解的方法。

invokeservice(string modulename, string methodname, jsonnode args)

构造 service bean 名称

string servicename = modulename + "apiservice";
object service = applicationcontext.getbean(servicename);

查找 @apimethod 注解, 没有注解时寻找内部方法名相同的接口

apimethod ann = getapimethodannotation(method, serviceclass);

writeerror 统一封装 错误提醒,往外丢出

private void writeerror(httpservletresponse response, string message, int status) throws ioexception 

完整实现代码(优化排版+注释)

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 路径中的方法名对应:

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

请求体:

{
  "username": "test",
  "password": "123456"
}

响应示例:

{
  "code": 200,
  "data": {
    "userid": 1001,
    "username": "test"
  },
  "msg": "操作成功"
}
  • 无侵入式路由:无需编写 controller,通过注解直接绑定业务方法与 api 路径,减少冗余代码;
  • 通用参数解析:基于 jsonnode 接收任意 json 格式参数,适配不同业务场景;
  • 注解穿透获取:支持接口、父类的注解继承,兼容不同编码风格的服务实现;
  • 统一响应封装:成功/失败响应格式标准化,异常信息解包暴露真实原因,便于排查问题;
  • 灵活扩展:可通过扩展 getapimethodannotation 方法,支持更多注解匹配规则,或增加参数校验、权限控制等逻辑。

以上就是基于spring注解+拦截器的动态api路由实现方案的详细内容,更多关于spring拦截器的动态api路由的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com