springboot 接口版本區(qū)分方式
springboot 接口版本區(qū)分
在進(jìn)行REST接口的開(kāi)發(fā)中,如果項(xiàng)目不斷的進(jìn)行迭代開(kāi)發(fā),需求不斷的變化,會(huì)出現(xiàn)不同的版本,一個(gè)接口版本1和版本2的業(yè)務(wù)邏輯可能完全不同,但是又需要兼容之前的版本,我們可能不能在之前的接口進(jìn)行修改,只能重新另外一個(gè)版本的接口,那該如何實(shí)現(xiàn)了?
目前有幾種方法,常見(jiàn)的有:一種是在url中加入版本號(hào),第二種是在請(qǐng)求頭中加入版本號(hào)。
下面我給出一個(gè)小demo,基于在請(qǐng)求的url中加入版本號(hào),擴(kuò)展可以根據(jù)自己的需要。
一、新建springboot項(xiàng)目
新建一個(gè)springboot項(xiàng)目,pom.xml的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jack</groupId>
<artifactId>springboot_version</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot_version</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件如下:
server: port: 9090
二、實(shí)現(xiàn)自定義版本控制的代碼
1,自定義版本控制的注解
package com.jack.springboot_version.annotation;
import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:44
* @Description:
*版本控制注解
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {
/**
* 標(biāo)識(shí)版本號(hào)
* @return
*/
int value();
}
2,自定義url匹配邏輯
package com.jack.springboot_version.config;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:57
* @Description:
*/
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
// 路徑中版本的前綴, 這里用 /v[1-9]/的形式
private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");
/**
* api的版本
*/
private int apiVersion;
public ApiVersionCondition(int apiVersion) {
this.apiVersion = apiVersion;
}
/**
* 將不同的篩選條件合并
* @param apiVersionCondition
* @return
*/
@Override
public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {
//return null;
// 采用最后定義優(yōu)先原則,則方法上的定義覆蓋類上面的定義
return new ApiVersionCondition(apiVersionCondition.getApiVersion());
}
/**
* 根據(jù)request查找匹配到的篩選條件
* @param httpServletRequest
* @return
*/
@Nullable
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
//return null;
Matcher m = VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());
if(m.find()){
Integer version = Integer.valueOf(m.group(1));
if(version >= this.apiVersion)
{
return this;
}
}
return null;
}
/**
* 不同篩選條件比較,用于排序
* @param apiVersionCondition
* @param httpServletRequest
* @return
*/
@Override
public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {
//return 0;
// 優(yōu)先匹配最新的版本號(hào)
return apiVersionCondition.getApiVersion() - this.apiVersion;
}
public int getApiVersion() {
return apiVersion;
}
}
3,自定義匹配的處理器
package com.jack.springboot_version.config;
import com.jack.springboot_version.annotation.ApiVersion;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:49
* @Description:
* 重寫RequestMappingHandlerMapping類的一些方法
*/
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
return createCondition(apiVersion);
}
@Override
protected RequestCondition<ApiVersionCondition> getCustomMethodCondition(Method method) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
return createCondition(apiVersion);
}
private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {
return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());
}
}
4,自定義WebMvcConfigurationSupport
核心代碼如下:
package com.jack.springboot_version.config;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:50
* @Description:
*/
@SpringBootConfiguration
public class WebConfig extends WebMvcConfigurationSupport {
/**
* 重寫請(qǐng)求過(guò)處理的方法
* @return
*/
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
//return super.requestMappingHandlerMapping();
RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
handlerMapping.setOrder(0);
return handlerMapping;
}
}
三、編寫測(cè)試的控制器
1,版本1的控制器:
package com.jack.springboot_version.controller.v1;
import com.jack.springboot_version.annotation.ApiVersion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:52
* @Description:
*/
@ApiVersion(1)
@RestController
@RequestMapping("{version}/hello")
public class Hello1Controller {
@RequestMapping("/world")
public String helloWorld(){
System.out.println("版本是1的接口");
return "hello,world .version is 1";
}
}
2,版本2的控制器:
package com.jack.springboot_version.controller.v2;
import com.jack.springboot_version.annotation.ApiVersion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* create by jack 2018/8/19
*
* @auther jack
* @date: 2018/8/19 10:52
* @Description:
*/
@ApiVersion(2)
@RestController
@RequestMapping("{version}/hello")
public class Hello2Controller {
@RequestMapping("/world")
public String helloWorld(){
System.out.println("版本是2的接口");
return "hello,world .version is 2";
}
}
四、測(cè)試demo
使用postman進(jìn)行測(cè)試:
1,測(cè)試版本1,:
測(cè)試url:http://localhost:9090/v1/hello/world
測(cè)試結(jié)果:

2,測(cè)試版本2,:
測(cè)試url:http://localhost:9090/v2/hello/world
測(cè)試結(jié)果:

git:https://github.com/wj903829182/springcloud5/tree/master/springboot_version
總結(jié):通過(guò)自定義springmvc的url匹配規(guī)則,實(shí)現(xiàn)接口的版本控制,url增加了版本號(hào),如果不存在高版本的版本接口則匹配代碼中版本號(hào)最高的處理邏輯。使用版本號(hào)對(duì)我們項(xiàng)目的接口的迭代開(kāi)發(fā)提供了方便。
springboot 兩個(gè)版本的差異
背景:前幾天被人問(wèn)到了SpringBoot 使用的是哪個(gè)版本的??jī)蓚€(gè)版本的差異?完全Hold不住,今天記起來(lái)去稍微了解下。
如今市面上就有SpringBoot2.X.X 和SpringBoot1.X.X 兩個(gè)新舊大版本。其中,SpringBoot1和SpringBoot2主要區(qū)別有如下兩個(gè)方面(MVC部分):
一、WebMvcConfigurerAdapter
WebMvcConfigurerAdapter該抽象類在新版的SpringBoot中有改動(dòng),部分方法過(guò)時(shí)。由于SpringBoot的2.0 及其以上版本最低已支持Java1.8,而Java1.8中有個(gè)defualt關(guān)鍵字的新特性,于是SpringBoot 2.0.0 對(duì)WebMvcConfigurerAdapter該抽象類的上層接口WebMvcConfigurer進(jìn)行了改造,將WebMvcConfigurer中的方法全部改為使用default關(guān)鍵字修飾;因此,SpringBoot2版本在使用WebMvcConfigurerAdapter抽象類時(shí)不需要再使用適配器進(jìn)行適配。
WebMvcConfigurer部分代碼如下:
public interface WebMvcConfigurer {
/**
* Helps with configuring HandlerMappings path matching options such as trailing slash match,
* suffix registration, path matcher and path helper.
* Configured path matcher and path helper instances are shared for:
* <ul>
* <li>RequestMappings</li>
* <li>ViewControllerMappings</li>
* <li>ResourcesMappings</li>
* </ul>
* @since 4.0.3
*/
void configurePathMatch(PathMatchConfigurer configurer);
/**
* Configure content negotiation options.
*/
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
/**
* Configure asynchronous request handling options.
*/
void configureAsyncSupport(AsyncSupportConfigurer configurer);
/**
* Configure a handler to delegate unhandled requests by forwarding to the
* Servlet container's "default" servlet. A common use case for this is when
* the {@link DispatcherServlet} is mapped to "/" thus overriding the
* Servlet container's default handling of static resources.
*/
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);
/**
* Add {@link Converter}s and {@link Formatter}s in addition to the ones
* registered by default.
*/
void addFormatters(FormatterRegistry registry);
更直接的說(shuō),就是WebMvcConfigurerAdapter 被WebMvcConfigurer 接口替代了,可以直接通過(guò)繼承WebMvcConfigurer接口,然后實(shí)現(xiàn)它的defalut方法來(lái)使用WebMvcConfigurerAdapter。
除此之外,WebMvcConfigurerAdapter 還可以用 WebMvcConfigurationSupport 替代,只不過(guò)使用WebMvcConfigurationSupport這個(gè)類來(lái)替換WebMvcConfigurerAdapter時(shí)會(huì)全面接管對(duì)SpringMVC的配置,即SpringBoot對(duì)SpringMVC的自動(dòng)配置全部失效,均使用用戶對(duì)SpringMVC的配置。
二、SpringMVC攔截器攔截靜態(tài)資源
SpringBoot1舊版本中配置的攔截器對(duì)靜態(tài)資源默認(rèn)是放行不攔截對(duì),而在SpringBoot 2.0.0及其以上版本的攔截器不會(huì)對(duì)靜態(tài)資源默認(rèn)放行,同樣也會(huì)進(jìn)行攔截。此時(shí),就需要為使用到的靜態(tài)資源排除排除其請(qǐng)求路徑,這樣在使用SpringBoot2新版本時(shí)攔截器才不會(huì)攔截靜態(tài)資源。
排除攔截靜態(tài)資源示例如下:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerIntercepter()).addPathPatterns("/**") .excludePathPatterns("/asserts/**","/webjars/**"); }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java實(shí)現(xiàn)富文本轉(zhuǎn)markdown
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)Java實(shí)現(xiàn)富文本轉(zhuǎn)markdown功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2023-12-12
Java調(diào)用第三方接口封裝實(shí)現(xiàn)
本文主要介紹了Java調(diào)用第三方接口封裝實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
java springboot的概述、特點(diǎn)與構(gòu)建介紹
大家好,本篇文章主要講的是springboot的概述、特點(diǎn)與構(gòu)建介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
基于Spring-Security自定義登陸錯(cuò)誤提示信息
這篇文章主要介紹了Spring-Security自定義登陸錯(cuò)誤提示信息,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
MyBatis中關(guān)于resultType和resultMap的區(qū)別介紹
MyBatis中在查詢進(jìn)行select映射的時(shí)候,返回類型可以用resultType,也可以用resultMap,那么MyBatis中關(guān)于resultType和resultMap的區(qū)別是什么呢?下面小編通過(guò)本文給大家解答下2016-09-09
SpringBoot啟動(dòng)時(shí)自動(dòng)執(zhí)行指定方法的幾種實(shí)現(xiàn)方式
在Spring Boot應(yīng)用程序中,要實(shí)現(xiàn)在應(yīng)用啟動(dòng)時(shí)自動(dòng)執(zhí)行某些代碼,本文主要介紹了SpringBoot啟動(dòng)時(shí)自動(dòng)執(zhí)行指定方法的幾種方式,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2024-03-03

