SpringBoot項(xiàng)目啟動(dòng)過程動(dòng)態(tài)修改接口請求路徑的解決方案
背景
最近遇到一個(gè)技術(shù)需求,需要對(duì)其他多個(gè)已有的服務(wù)進(jìn)行整合打包為一個(gè)整體的服務(wù),項(xiàng)目啟動(dòng)過程發(fā)現(xiàn)一個(gè)問題,在controller層多個(gè)服務(wù)之間存在相同的RequestMapping接口請求路徑,導(dǎo)致服務(wù)無法啟動(dòng)。
目前的接口定義規(guī)范為:/服務(wù)名(context-path)/接口版本號(hào)/模塊名/接口名
例如通過用戶Id查詢用戶信息的接口,在統(tǒng)一認(rèn)證服務(wù)和用戶管理服務(wù)有如下接口定義
統(tǒng)一認(rèn)證服務(wù):/sso/v1.0/user/get/{id}
用戶管理服務(wù):/user/v1.0/user/get/{id}
當(dāng)我們把統(tǒng)一認(rèn)證服務(wù)和用戶管理服務(wù)進(jìn)行整合為一個(gè)服務(wù)時(shí),/v1.0/user/get/{id}請求路徑發(fā)生重復(fù),導(dǎo)致服務(wù)無法啟動(dòng)。
解決方案
為了解決服務(wù)之間接口定義沖突的問題,我們準(zhǔn)備對(duì)接口的請求路徑進(jìn)行動(dòng)態(tài)修改,主要是通過controller類所在的包路徑進(jìn)行服務(wù)名的識(shí)別,并加入到接口請求路徑的最前面。
舉個(gè)栗子:
統(tǒng)一認(rèn)證服務(wù)controller層偽代碼如下:
package cn.codest.sso.web;
@RestController
@Api(tags = "統(tǒng)一認(rèn)證服務(wù)")
@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方法,實(shí)現(xiàn)在項(xiàng)目啟動(dòng)過程中注冊/v1.0/user/get/{id}接口時(shí),識(shí)別package的路徑cn.codest.sso.web提取業(yè)務(wù)標(biāo)識(shí)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";
/**
* 包名對(duì)應(yīng)的服務(wù)名緩存類
*/
private final LinkedHashMap<String, String> services = new LinkedHashMap<>(8);
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 判斷當(dāng)前注冊的controller接口屬于業(yè)務(wù)層controller,部分中間件例如swagger也會(huì)進(jìn)行接口注冊
if (!StringUtils.startsWith(handlerType.getName(), PACKAGE_PREFIX)
|| !AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class)
|| AnnotatedElementUtils.hasAnnotation(handlerType, FeignClient.class)) {
return super.getMappingForMethod(method, handlerType);
}
try {
// 構(gòu)造RequestMapping對(duì)象
RequestMappingInfo mapping = super.getMappingForMethod(method, handlerType);
// 根據(jù)包路徑獲取服務(wù)名
String serviceName = getServiceName(handlerType.getName());
if (StringUtils.isBlank(serviceName)) {
return mapping;
}
// 增加服務(wù)名前綴
return RequestMappingInfo.paths(serviceName).build().combine(mapping);
} catch (Exception e) {
log.error("重寫RequestMapping請求路徑時(shí)發(fā)生錯(cuò)誤[class: {}, method: {}]", handlerType.getName(), method.getName(), e);
throw e;
}
}
protected String getServiceName(String className) {
// 分割類限定名
String[] packages = className.split("\\.");
// 判斷包路徑長度
if (packages.length > 3) {
// 獲取子產(chǎn)品包名
String serviceName = packages[2];
// 讀取緩存
if (services.containsKey(serviceName)) {
return services.get(serviceName);
} else if (StringUtils.startsWith(serviceName, SERVICE_PREFIX)) { // service服務(wù)
services.put(serviceName, serviceName.replace(SERVICE_PREFIX, StringUtils.EMPTY));
} else if (StringUtils.startsWith(serviceName, PROVIDER_PREFIX.toLowerCase())) { // provider服務(wù)
services.put(serviceName, serviceName.replace(PROVIDER_PREFIX.toLowerCase(), PROVIDER_PREFIX));
}
return services.get(serviceName);
}
return StringUtils.EMPTY;
}
}注冊自定義RequestMappingHandler,項(xiàng)目使用的SpringBoot版本為2.0.x,不同版本注冊方式不同,可以自行查閱官方文檔。
public class CustomWebMvcConfig implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = new PackagePathRequestMappingHandler();
handlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
return handlerMapping;
}
}到此這篇關(guān)于SpringBoot項(xiàng)目啟動(dòng)過程動(dòng)態(tài)修改接口請求路徑的文章就介紹到這了,更多相關(guān)SpringBoot動(dòng)態(tài)修改接口請求路徑內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實(shí)現(xiàn)自定義啟動(dòng)器的示例詳解
雖然Spring官方給我們提供了很多的啟動(dòng)器供我們使用,但有時(shí)候我們也會(huì)遇到某些特殊場景,這些啟動(dòng)器滿足不了。這個(gè)時(shí)候就需要自定義一個(gè)啟動(dòng)器供我們使用,本文為大家介紹了SpringBoot實(shí)現(xiàn)自定義啟動(dòng)器的方法,希望對(duì)大家有所幫助2023-01-01
java并發(fā)編程專題(一)----線程基礎(chǔ)知識(shí)
這篇文章主要介紹了java并發(fā)編程線程的基礎(chǔ)知識(shí),文中講解非常詳細(xì),幫助大家更好的學(xué)習(xí)JAVA并發(fā)編程,感興趣想學(xué)習(xí)JAVA的可以了解下2020-06-06
SpringBoot實(shí)現(xiàn)分布式驗(yàn)證碼登錄方案小結(jié)
驗(yàn)證碼登錄作為一種有效的防護(hù)手段,可以防止惡意gongji、暴力pojie等,本文主要介紹了SpringBoot實(shí)現(xiàn)分布式驗(yàn)證碼登錄方案小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2024-12-12
Maven中exec插件執(zhí)行Java程序的實(shí)現(xiàn)
在Maven項(xiàng)目中,可以使用Maven的插件來執(zhí)行Java程序,本文主要介紹了Maven中exec插件執(zhí)行Java程序的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
Java中基于推、拉模式的sentinel規(guī)則持久化詳解
這篇文章主要介紹了Java中基于推、拉模式的sentinel規(guī)則持久化詳解,推模式是sentinelDashboard?把規(guī)則推給Nacos,Nacos監(jiān)聽規(guī)則的變化推給微服務(wù),拉模式是sentinelDashboard?把規(guī)則直接給微服務(wù),?Nacos定時(shí)的同步微服務(wù)端的規(guī)則,需要的朋友可以參考下2023-09-09
Spring自動(dòng)掃描無法掃描jar包中bean的解決方法
在日常開發(fā)中往往會(huì)對(duì)公共的模塊打包發(fā)布,然后調(diào)用公共包的內(nèi)容。然而,最近對(duì)公司的公共模塊進(jìn)行整理發(fā)布后。spring卻無法掃描到相應(yīng)的bean,下面這篇文章主要給大家介紹了關(guān)于Spring自動(dòng)掃描時(shí)無法掃描jar包中bean的解決方法,需要的朋友可以參考下。2017-06-06
SpringBoot Maven打包如何根據(jù)環(huán)境排除文件
文章介紹了在SpringBoot項(xiàng)目中,根據(jù)不同的環(huán)境(開發(fā)、測試、生產(chǎn))進(jìn)行JSP文件打包處理的方法,通過配置`pom.xml`文件中的``標(biāo)簽,可以實(shí)現(xiàn)開發(fā)環(huán)境保留`index.jsp`文件,測試環(huán)境和生產(chǎn)環(huán)境排除該文件2024-12-12

