背景

最近遇到一个技术需求,需要对其他多个已有的服务进行整合打包为一个整体的服务,项目启动过程发现一个问题,在controller层多个服务之间存在相同的RequestMapping接口请求路径,导致服务无法启动。

目前的接口定义规范为:/服务名(context-path)/接口版本号/模块名/接口名

例如通过用户Id查询用户信息的接口,在统一认证服务和用户管理服务有如下接口定义

统一认证服务:/sso/v1.0/user/get/{id}

用户管理服务:/user/v1.0/user/get/{id}

当我们把统一认证服务和用户管理服务进行整合为一个服务时,/v1.0/user/get/{id}请求路径发生重复,导致服务无法启动。

解决方案

为了解决服务之间接口定义冲突的问题,我们准备对接口的请求路径进行动态修改,主要是通过controller类所在的包路径进行服务名的识别,并加入到接口请求路径的最前面。

举个栗子:

统一认证服务controller层伪代码如下:

package cn.codest.sso.web;

@RestController
@Api(tags = "统一认证服务")
@RequestMapping("/v1.0/user")
@RequiredArgsConstructor
public class SSOController { private final UserService userService; @GetMapping("/get/{id}")
@ApiOperation(value = "用户查询", httpMethod = "GET", notes = "用户查询")
public SwaggerResultUtil getById(String id){
return SwaggerResultUtil.resultSuccess(userService.get(id));
} }

通过重载RequestMappingHandlerMapping类的getMappingForMethod方法,实现在项目启动过程中注册/v1.0/user/get/{id}接口时,识别package的路径cn.codest.sso.web提取业务标识sso,修改接口注册地址为/sso/v1.0/user/get/{id},具体代码如下:

@Slf4j
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class PackagePathRequestMappingHandler extends RequestMappingHandlerMapping { private static final String PACKAGE_PREFIX = "cn.codest"; private static final String SERVICE_PREFIX = "service"; private static final String PROVIDER_PREFIX = "providerD"; /**
* 包名对应的服务名缓存类
*/
private final LinkedHashMap<String, String> services = new LinkedHashMap<>(8); @Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { // 判断当前注册的controller接口属于业务层controller,部分中间件例如swagger也会进行接口注册
if (!StringUtils.startsWith(handlerType.getName(), PACKAGE_PREFIX)
|| !AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class)
|| AnnotatedElementUtils.hasAnnotation(handlerType, FeignClient.class)) {
return super.getMappingForMethod(method, handlerType);
} try {
// 构造RequestMapping对象
RequestMappingInfo mapping = super.getMappingForMethod(method, handlerType);
// 根据包路径获取服务名
String serviceName = getServiceName(handlerType.getName());
if (StringUtils.isBlank(serviceName)) {
return mapping;
}
// 增加服务名前缀
return RequestMappingInfo.paths(serviceName).build().combine(mapping);
} catch (Exception e) {
log.error("重写RequestMapping请求路径时发生错误[class: {}, method: {}]", handlerType.getName(), method.getName(), e);
throw e;
}
} protected String getServiceName(String className) {
// 分割类限定名
String[] packages = className.split("\\."); // 判断包路径长度
if (packages.length > 3) {
// 获取子产品包名
String serviceName = packages[2]; // 读取缓存
if (services.containsKey(serviceName)) {
return services.get(serviceName);
} else if (StringUtils.startsWith(serviceName, SERVICE_PREFIX)) { // service服务
services.put(serviceName, serviceName.replace(SERVICE_PREFIX, StringUtils.EMPTY));
} else if (StringUtils.startsWith(serviceName, PROVIDER_PREFIX.toLowerCase())) { // provider服务
services.put(serviceName, serviceName.replace(PROVIDER_PREFIX.toLowerCase(), PROVIDER_PREFIX));
} return services.get(serviceName);
} return StringUtils.EMPTY;
} }

注册自定义RequestMappingHandler,项目使用的SpringBoot版本为2.0.x,不同版本注册方式不同,可以自行查阅官方文档。

public class CustomWebMvcConfig implements WebMvcRegistrations {

    @Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = new PackagePathRequestMappingHandler();
handlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
return handlerMapping;
} }

SpringBoot项目启动过程动态修改接口请求路径的更多相关文章

  1. springboot 项目启动后访问不论什么请求的是spring的注册页面Please sign in Username || springboot禁用security

    解决方法: 1.在启动类上添加注解@EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class}) 2.或者:@SpringB ...

  2. SpringBoot项目启动后再请求远程接口的实现方式

    场景 有一个SpringBoot项目需要在启动后请求另一个远程服务拿取配置,而不是加载过程中去请求,可能会出现类没有实例化的场景,因此需要实现项目完全启动后再进行请求的场景. 解决 一般会有两种实现方 ...

  3. Spring Boot启动过程及回调接口汇总

    Spring Boot启动过程及回调接口汇总 链接: https://www.itcodemonkey.com/article/1431.html 来自:chanjarster (Daniel Qia ...

  4. ssm框架中,项目启动过程以及web.xml配置详解

    原文:https://blog.csdn.net/qq_35571554/article/details/82385838 本篇主要在基于SSM的框架,深入讲解web.xml的配置 web.xml   ...

  5. Springboot 项目启动后执行某些自定义代码

    Springboot 项目启动后执行某些自定义代码 Springboot给我们提供了两种"开机启动"某些方法的方式:ApplicationRunner和CommandLineRun ...

  6. SpringBoot源码分析之SpringBoot的启动过程

    SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30   |   分类于 springboot  |   0 Comments  |   阅读次数 SpringB ...

  7. springboot项目启动成功后执行一段代码的两种方式

    springboot项目启动成功后执行一段代码的两种方式 实现ApplicationRunner接口 package com.lnjecit.lifecycle; import org.springf ...

  8. springboot项目启动之后初始化自定义配置类

    前言 今天在写项目的时候,需要再springboot项目启动之后,加载我自定义的配置类的一些方法,百度了之后特此记录下. 正文 方法有两种: 1. 创建自定义类实现 CommandLineRunner ...

  9. springBoot项目启动类启动无法访问

    springBoot项目启动类启动无法访问. 网上也查了一些资料,我这里总结.下不来虚的,也不废话. 解决办法: 1.若是maven项目,则找到右边Maven Projects --->Plug ...

  10. SpringBoot项目启动时链接数据库很慢

    SpringBoot项目启动时链接数据库很慢 springboot项目在启动时候,如下图所示,链接数据库很慢 解决方法:在mysql 的配置文件中 配置 skip-name-resolve

随机推荐

  1. 我愿称之为"温水煮青蛙"

    前言:作为开发在工作中如何将自己一点一点放弃. 事情是这样的,来新公司已经差不多三个多月了,公司的主要技术栈大部分还是jquer 这让我非常的头疼,不是说做不了这个技术,其实用过jquer 都知道这玩 ...

  2. Hugging News #0821: Hugging Face 完成 2.35 亿美元 D 轮融资

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  3. 【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅

    前言 缘由 博友的需求就是我最大的动力 博友一说话,本狗笑哈哈.博友要我写啥,我就写啥. 特来一篇关于SpringBoot接口返回结果及异常统一处理,虽说封不封装都能用,但咱后端也得给前端小姐姐留个好 ...

  4. LUA的一些工具备份

    table.unpack遇到的问题 做了个中转的服务, socket+json 传递数据, 通过 {...} 封装不定参数然后 json.encode 传递到其他服务器, 然后其他服务器 json.d ...

  5. Vue 中的 Ajax

    1.1 使用代理服务器 1.1.1 方式一 在 vue.config.js 中添加如下配置: devServer:{ proxy:"http://localhost:5000" } ...

  6. 解决CentOS 7出现docker-compose: command not found

    解决CentOS 7出现docker-compose: command not found 1. 安装docker-compose 既然使用了docker-compose那自然得安装了 在GitHub ...

  7. 如何vue3中使用全局变量,与Vue2的区别

    对比: 在vue2.x中我们挂载全局变量或方法是通过是使用Vue.prototype.$xxxx=xxx的形式来挂载,然后通过this.$xxx来获取挂载到全局的变量或者方法 但是 在vue3.x中显 ...

  8. Asp-Net-Core开发笔记:EFCore统一实体和属性命名风格

    前言 C# 编码规范中,类和属性都是大写驼峰命名风格(PascalCase / UpperCamelCase),而在数据库中我们往往使用小写蛇形命名(snake_case),在默认情况下,EFCore ...

  9. Java虚拟机(JVM):第六幕:自动内存管理 - 选择合适的垃圾收集器

    前言:在虚拟机的世界里面,内置了很多的垃圾收集器,但并不是说最先进的就是最好的.有一句话说的好"因地制宜": 一.Epsilon收集器 是一个无操作的收集器,但是贴切的来说是&qu ...

  10. 【高手训练】【RMQ】奶牛排队

    第一题由于过水,就没写awa 大概就是这样了.题意就是求一个最长的区间使得区间的左边是它的最小值,区间右边是他的最大值第一个想法肯定是暴力枚举啦awa但是这个是O(n^3)的,绝对的不可过awa 思考 ...