概要
通过自定义注解 @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/模块名/方法名 到达时:
apihandlermapping.gethandler()匹配/api/前缀 + post 方法,返回自定义处理器apihandlerapihandler.handlerequest()执行核心逻辑:- 解析 uri:
/api/user/create→ 模块名user、方法名create - 读取 json 请求体:通过 jackson 解析为通用
jsonnode结构,兼容任意 json 格式入参 - 调用
invokeservice():根据模块名匹配 spring 容器中的业务服务,根据注解匹配对应方法并执行 - 统一封装响应结果(成功/失败)
- 解析 uri:
读取请求体为 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路由的资料请关注代码网其它相关文章!
发表评论