网站首页 > 技术文章 正文
前言
目前在这家公司遇到一个比较有意思的需求,特此记录
需求是这样的,当前系统需要对外提供 OpenApi 接口以供第三方调用,类似提供给第三方直接调用的 SDK。最常见的实现可能就是定义专门对外提供服务的 Controller,指定不同的访问路径来访问我们的 OpenApi(类比普通 SpringBoot 接口)。但目前我们想要的效果是让所有的接口走一个统一的方法,再由这个方法分发到具体的实现上。那这个有意思的需求要如何实现呢?
实现思路
我们肯定需要一个统一的处理器,让所有的第三方都调用这个处理器。那处理器内部怎么知道不同的客户要调用哪些接口呢?首先想到的肯定是需要用户除了传递业务参数外,多传递一个 method 属性。通过这个 method 方法名属性去调用对应的方法
第一个难点在于我们如何通过一个字符串的方法名 #技术分享找到对应的方法呢?
- 我想到的是通过一个 Map<String, Method> 。key 对应方法名,value 对应 Method 方法、
第二个难点在于我们怎么知道有哪些方法需要存入 Map 中?前面说了 OpenApi 的方法都是在不同的类中的。难道我们要硬编码,写死具体的方法名吗?
- 肯定是不行的,这样就降低了复用性。我的思路是使用自定义注解,标记哪些类是供 OpenApi 使用。然后通过 Spring 的上下文对象解析自定义注解,找到所有被自定义注解注释的类
第二个难点在于我们如何初始化这个 Map 呢?应该怎么把这些信息封装进去呢?
- 这就需要了解 Spring 的 Bean 生命周期及相关知识
- 我们可以定义一个配置类,把我们的 Map 定义成一个 Bean,让 Spring 在启动的时候去加载并且初始化这个 Bean。这样 Spring 项目启动成功后,这个 Bean 里面也就有了数据
三大难点我们分析出来了,就一起来实现代码试试吧
代码实现
- 定义自定义注解,作用是所有被此注解标记的类都是对外提供 OpenApi 的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ScannableService {
String value() default ""; }
- 定义 Config 配置文件,解析并向 Map 中存放 Method 数据
@Slf4j
@Configuration
public class ScannableConfig {
@Bean public Map<String, Method> methodMap(ApplicationContext context) { HashMap<String, Method> methodMap = new HashMap<>(); this.init(context, methodMap); return methodMap; }
private void init(ApplicationContext context, Map<String, Method> methodMap) { log.info("========= 初始化上下文方法映射 ========="); Map<String, Object> annotationBeanMap = context.getBeansWithAnnotation(ScannableService.class);
annotationBeanMap.forEach((beanName, bean) -> { Class<?> clazz = bean.getClass(); Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) { methodMap.put(method.getName(), method); } });
log.info("========= 初始化上下文方法映射完成 ========="); }
}
这里有两个需要明确的知识点:
@Bean 注解标记的方法必须要有返回值
@PostConstruct 注解标记的方法会在依赖注入完成后执行,适合初始化操作,那此处为什么不直接在 init() 方法上使用次注解初始化呢?因为 @PostConstruct 注解必须用在没有形参的方法上,不然会报错无法执行
- 编写统一请求处理器
@Slf4j
@RestController
@RequestMapping("/api/v1/openapi")
@Api(tags = "统一请求类")
public class ConsistentController {
@Resource private Map<String, Method> methodMap; @Resource private ApplicationContext context;
@PostMapping("/execute") @ApiOperation(value = "统一请求处理方法") public Result<?> execute(@RequestHeader(value = "method") String methodName, @RequestBody Map<String, Object> paramMap) {
Method method = methodMap.getOrDefault(methodName, null); if (method == null) { return Result.error(new BizException(ConsistentError.METHOD_NOT_FOUND), methodName); }
Object arg = this.resolveMethodParameters(method, paramMap); Object targetBean = context.getBean(method.getDeclaringClass());
Object result; try { result = method.invoke(targetBean, arg); return Result.ok(result); } catch (IllegalArgumentException e) { return Result.error(ConsistentError.PARAM_TYPE_MISMATCH, e); } catch (Exception e) { return Result.error(ConsistentError.SYSTEM_ERROR, e); }
}
private Object resolveMethodParameters(Method method, Map<String, Object> paramMap) { Class<?> parameterType = method.getParameterTypes()[0];
try { ObjectMapper mapper = new ObjectMapper(); return mapper.convertValue(paramMap, parameterType); } catch (IllegalArgumentException e) { throw new BizException(ConsistentError.PARAM_TYPE_MISMATCH, e); } } }
此处需要注意,Post 请求具体方法的参数我是使用 Map 来接受,然后 execute() 方法内部自己转成了对应的数据类型。而额外的 methodName 参数则是让第三方调用的时候填入请求头中,通过@RequestHeader 注解获取
这样,一个统一请求处理类就实现好了~
注意事项
这样统一请求路径的实现有什么好处呢?首先第三方用户不用去查看成百上千的 OpenApi 路径。只需要记住这一个统一请求方法的路径就好了,在一定程度上方便了第三方用户其次,有了统一请求处理器就能很方便的实现个性化需求。例如:我们可以方便的统计出第三方那个客户的调用频率、IP 地址等信息,方便我们进行数据分析等
还是那句话,不要为了用而用,不要炫技。这个方式不一定就比普通的通过 Controller 层的请求路径调用好。要根据需求及应用场景来进行选择!
本文由博客一文多发平台 OpenWrite 发布!
猜你喜欢
- 2025-07-28 如何优雅的实现 Spring Boot 接口参数加密解密?
- 2025-07-28 SpringBoot注解最全详解(9大常用注解)
- 2025-07-28 SpringBoot注解全攻略:这些注解让你的代码更专业!
- 2025-07-28 答应我,不要再用 Map 做出入参了好吗
- 2025-07-28 关于远程调用feign的优雅写法(feign远程调用怎么用restful)
- 2025-07-28 自研分布式高性能RPC框架及服务注册中心ApiRegistry实践笔记
- 2025-07-28 spring基础面试题整理(2)(spring基本面试题)
- 2025-07-28 Spring Boot异常处理太难搞,这样实现让你轻松应对!
- 2025-07-28 独立开发:高效集成大模型,看这篇就够了
- 2025-07-28 Spring&SpringBoot常用注解总结
- 1518℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 600℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 521℃MySQL service启动脚本浅析(r12笔记第59天)
- 490℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 489℃启用MySQL查询缓存(mysql8.0查询缓存)
- 477℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 457℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 454℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- windowsscripthost (69)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)