ChatGPT解决这个技术问题 Extra ChatGPT

如何使用 spring 管理 REST API 版本控制?

我一直在寻找如何使用 Spring 3.2.x 管理 REST API 版本,但我没有找到任何易于维护的东西。我将首先解释我遇到的问题,然后是解决方案......但我想知道我是否在这里重新发明了轮子。

我想根据 Accept 标头管理版本,例如,如果请求具有 Accept 标头 application/vnd.company.app-1.1+json,我希望 Spring MVC 将其转发给处理此版本的方法。而且由于并非 API 中的所有方法都在同一个版本中更改,因此我不想转到我的每个控制器并更改版本之间未更改的处理程序的任何内容。我也不希望有逻辑来确定在控制器本身中使用哪个版本(使用服务定位器),因为 Spring 已经在发现要调用的方法。

因此,采用 1.0 到 1.8 版本的 API,其中在 1.0 版本中引入了处理程序并在 v1.7 中进行了修改,我想通过以下方式处理这个问题。想象一下代码在控制器内部,并且有一些代码能够从标头中提取版本。 (以下在Spring中无效)

@RequestMapping(...)
@VersionRange(1.0,1.6)
@ResponseBody
public Object method1() {
   // so something
   return object;
}

@RequestMapping(...) //same Request mapping annotation
@VersionRange(1.7)
@ResponseBody
public Object method2() {
   // so something
   return object;
}

这在 spring 中是不可能的,因为 2 个方法具有相同的 RequestMapping 注释并且 Spring 无法加载。这个想法是 VersionRange 注释可以定义一个开放或封闭的版本范围。第一种方法从 1.0 到 1.6 版本有效,而第二种方法适用于 1.7 及以上版本(包括最新版本 1.8)。我知道如果有人决定通过 99.99 版本,这种方法就会失效,但我可以接受。

现在,由于没有对 spring 的工作方式进行认真的返工,上述内容是不可能的,我正在考虑修改处理程序与请求匹配的方式,特别是编写我自己的 ProducesRequestCondition,并在其中包含版本范围。例如

代码:

@RequestMapping(..., produces = "application/vnd.company.app-[1.0-1.6]+json)
@ResponseBody
public Object method1() {
   // so something
   return object;
}

@RequestMapping(..., produces = "application/vnd.company.app-[1.7-]+json)
@ResponseBody
public Object method2() {
   // so something
   return object;
}

通过这种方式,我可以在注释的产生部分中定义封闭或开放的版本范围。我现在正在研究这个解决方案,问题是我仍然需要替换一些我不喜欢的核心 Spring MVC 类(RequestMappingInfoHandlerMappingRequestMappingHandlerMappingRequestMappingInfo),因为这意味着无论何时都需要额外的工作我决定升级到更新版本的spring。

我会很感激任何想法......尤其是任何以更简单、更易于维护的方式执行此操作的建议。

编辑

添加赏金。要获得赏金,请回答上面的问题,而不建议在控制器本身中使用此逻辑。 Spring 已经有很多逻辑来选择调用哪个控制器方法,我想捎带它。

编辑 2

我在 github 中分享了原始 POC(经过一些改进):https://github.com/augusto/restVersioning

@flup 我不明白你的评论。这只是说您可以使用标头,正如我所说,spring 提供的开箱即用的功能不足以支持不断更新的 API。更糟糕的是,该答案上的链接使用了 URL 中的版本。
我们需要支持我们的 API 的多个版本,这些差异通常是微小的更改,会使某些客户端的某些调用不兼容(如果我们需要支持 4 个小版本,其中某些端点不兼容,这并不奇怪)。我很欣赏将它放在 url 中的建议,但我们知道这是朝着错误方向迈出的一步,因为我们有几个应用程序的版本在 URL 中,并且每次我们需要提升时都会涉及很多工作版本。
@Augusto,实际上您还没有。只需以不破坏向后兼容性的方式设计您的 API 更改。只需给我一个破坏兼容性的更改示例,我将向您展示如何以非破坏方式进行这些更改。
您是否看过 stackoverflow.com/a/10336769/2615437,这似乎暗示您的陈述“这在 spring 中是不可能的,因为这两种方法具有相同的 RequestMapping 注释并且 Spring 无法加载。”是不是完全正确?

T
TechEnthusiast

不管是否可以通过向后兼容的更改来避免版本控制(当您受到某些公司准则的约束或您的 API 客户端以错误的方式实现并且即使不应该中断也会中断),抽象的需求是一个有趣的一:

如何进行自定义请求映射,对请求中的标头值进行任意评估,而无需在方法主体中进行评估?

this SO answer 中所述,您实际上可以拥有相同的 @RequestMapping 并使用不同的注释来区分运行时发生的实际路由。为此,您必须:

创建一个新的注解 VersionRange。实现一个 RequestCondition。由于您将拥有类似最佳匹配算法的东西,因此您必须检查使用其他 VersionRange 值注释的方法是否为当前请求提供了更好的匹配。根据注解和请求条件实现 VersionRangeRequestMappingHandlerMapping(如如何实现 @RequestMapping 自定义属性一文中所述)。在使用默认的 RequestMappingHandlerMapping 之前配置 spring 以评估您的 VersionRangeRequestMappingHandlerMapping(例如,通过将其 order 设置为 0)。

这不需要对 Spring 组件进行任何 hacky 替换,而是使用 Spring 配置和扩展机制,因此即使您更新 Spring 版本(只要新版本支持这些机制)它也应该工作。


感谢您添加您的评论作为答案 xwoker。到目前为止是最好的一个。我已经根据您提到的链接实施了解决方案,而且还不错。升级到新版本的 Spring 时会出现最大的问题,因为它需要检查对 mvc:annotation-driven 背后的逻辑的任何更改。希望 Spring 将提供一个可以定义自定义条件的 mvc:annotation-driven 版本。
@Augusto,半年后,这对您来说效果如何?另外,我很好奇,您真的是按方法进行版本控制吗?在这一点上,我想知道在每个类/每个控制器级别粒度上的版本是否会更清晰?
@SanderVerhagen 它正在工作,但我们对整个 API 进行版本控制,而不是每个方法或控制器(API 非常小,因为它专注于业务的一个方面)。我们确实有一个更大的项目,他们选择为每个资源使用不同的版本并在 URL 上指定(这样你就可以在 /v1/sessions 上拥有一个端点,在完全不同的版本上拥有另一个资源,例如 /v4/orders) ...它更灵活一点,但它给客户带来了更大的压力,让他们知道要调用每个端点的哪个版本。
不幸的是,这对 Swagger 来说并不好用,因为在扩展 WebMvcConfigurationSupport 时会关闭很多自动配置。
我尝试了这个解决方案,但它实际上不适用于 2.3.2.RELEASE。你有一些示例项目要展示吗?
J
Jens

我刚刚创建了一个自定义解决方案。我将 @ApiVersion 注释与 @Controller 类中的 @RequestMapping 注释结合使用。

例子:

@Controller
@RequestMapping("x")
@ApiVersion(1)
class MyController {

    @RequestMapping("a")
    void a() {}         // maps to /v1/x/a

    @RequestMapping("b")
    @ApiVersion(2)
    void b() {}         // maps to /v2/x/b

    @RequestMapping("c")
    @ApiVersion({1,3})
    void c() {}         // maps to /v1/x/c
                        //  and to /v3/x/c

}

执行:

ApiVersion.java 注释:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
    int[] value();
}

ApiVersionRequestMappingHandlerMapping.java(这主要是从 RequestMappingHandlerMapping 复制和粘贴):

public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    private final String prefix;

    public ApiVersionRequestMappingHandlerMapping(String prefix) {
        this.prefix = prefix;
    }

    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
        if(info == null) return null;

        ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class);
        if(methodAnnotation != null) {
            RequestCondition<?> methodCondition = getCustomMethodCondition(method);
            // Concatenate our ApiVersion with the usual request mapping
            info = createApiVersionInfo(methodAnnotation, methodCondition).combine(info);
        } else {
            ApiVersion typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
            if(typeAnnotation != null) {
                RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
                // Concatenate our ApiVersion with the usual request mapping
                info = createApiVersionInfo(typeAnnotation, typeCondition).combine(info);
            }
        }

        return info;
    }

    private RequestMappingInfo createApiVersionInfo(ApiVersion annotation, RequestCondition<?> customCondition) {
        int[] values = annotation.value();
        String[] patterns = new String[values.length];
        for(int i=0; i<values.length; i++) {
            // Build the URL prefix
            patterns[i] = prefix+values[i]; 
        }

        return new RequestMappingInfo(
                new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch(), useTrailingSlashMatch(), getFileExtensions()),
                new RequestMethodsRequestCondition(),
                new ParamsRequestCondition(),
                new HeadersRequestCondition(),
                new ConsumesRequestCondition(),
                new ProducesRequestCondition(),
                customCondition);
    }

}

注入 WebMvcConfigurationSupport:

public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Override
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new ApiVersionRequestMappingHandlerMapping("v");
    }
}

我将 int[] 更改为 String[] 以允许像“1.2”这样的版本,所以我可以处理像“latest”这样的关键字
是的,这很合理。对于未来的项目,出于某些原因,我会采用不同的方式:1. URL 代表资源。 /v1/aResource/v2/aResource 看起来像不同的资源,它只是同一资源的不同表示! 2. 使用 HTTP 标头看起来更好,但是你不能给别人一个 URL,因为 URL 不包含标头。 3. 使用 URL 参数,即 /aResource?v=2.1(顺便说一句:这是 Google 进行版本控制的方式)。 ... 我仍然不确定我会选择 2 还是 3 选项,但出于某种原因我再也不会使用 1上文提到的。
如果您想将自己的 RequestMappingHandlerMapping 注入到您的 WebMvcConfiguration 中,您应该覆盖 createRequestMappingHandlerMapping 而不是 requestMappingHandlerMapping!否则你会遇到奇怪的问题(由于会话关闭,我突然遇到了 Hibernates 延迟初始化的问题)
该方法看起来不错,但不知何故似乎不适用于junti测试用例(SpringRunner)。您有任何机会使用测试用例的方法
有一种方法可以完成这项工作,不要扩展 WebMvcConfigurationSupport ,而是扩展 DelegatingWebMvcConfiguration。这对我有用(见 stackoverflow.com/questions/22267191/…
m
mspapant

我已经实现了一个解决方案,可以完美地处理剩余版本控制的问题。

一般而言,REST 版本控制有 3 种主要方法:

基于路径的方法,其中客户端在 URL 中定义版本: http://localhost:9001/api/v1/user http://localhost:9001/api/v2/user

Content-Type 标头,其中客户端在 Accept 标头中定义版本:http://localhost:9001/api/v1/user 和 Accept: application/vnd.app-1.0+json OR application/vnd.app-2.0+ json

自定义标头,其中客户端在自定义标头中定义版本。

第一种方法的问题是,如果您更改版本,比如说从 v1 -> v2,可能您需要将未更改的 v1 资源复制粘贴到 v2 路径

第二种方法的问题是像http://swagger.io/这样的一些工具无法区分具有相同路径但不同Content-Type的操作(检查问题https://github.com/OAI/OpenAPI-Specification/issues/146

解决方案

由于我经常使用其他文档工具,因此我更喜欢使用第一种方法。我的解决方案使用第一种方法处理问题,因此您无需将端点复制粘贴到新版本。

假设我们有用户控制器的 v1 和 v2 版本:

package com.mspapant.example.restVersion.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * The user controller.
 *
 * @author : Manos Papantonakos on 19/8/2016.
 */
@Controller
@Api(value = "user", description = "Operations about users")
public class UserController {

    /**
     * Return the user.
     *
     * @return the user
     */
    @ResponseBody
    @RequestMapping(method = RequestMethod.GET, value = "/api/v1/user")
    @ApiOperation(value = "Returns user", notes = "Returns the user", tags = {"GET", "User"})
    public String getUserV1() {
         return "User V1";
    }

    /**
     * Return the user.
     *
     * @return the user
     */
    @ResponseBody
    @RequestMapping(method = RequestMethod.GET, value = "/api/v2/user")
    @ApiOperation(value = "Returns user", notes = "Returns the user", tags = {"GET", "User"})
    public String getUserV2() {
         return "User V2";
    }
 }

要求是,如果我为用户资源请求 v1,我必须接受“用户 V1”响应,否则如果我请求 v2、v3 等,我必须接受“用户 V2”响应。

https://i.stack.imgur.com/pCP7v.png

为了在 spring 中实现这一点,我们需要覆盖默认的 RequestMappingHandlerMapping 行为:

package com.mspapant.example.restVersion.conf.mapping;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class VersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    @Value("${server.apiContext}")
    private String apiContext;

    @Value("${server.versionContext}")
    private String versionContext;

    @Override
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        HandlerMethod method = super.lookupHandlerMethod(lookupPath, request);
        if (method == null && lookupPath.contains(getApiAndVersionContext())) {
            String afterAPIURL = lookupPath.substring(lookupPath.indexOf(getApiAndVersionContext()) + getApiAndVersionContext().length());
            String version = afterAPIURL.substring(0, afterAPIURL.indexOf("/"));
            String path = afterAPIURL.substring(version.length() + 1);

            int previousVersion = getPreviousVersion(version);
            if (previousVersion != 0) {
                lookupPath = getApiAndVersionContext() + previousVersion + "/" + path;
                final String lookupFinal = lookupPath;
                return lookupHandlerMethod(lookupPath, new HttpServletRequestWrapper(request) {
                    @Override
                    public String getRequestURI() {
                        return lookupFinal;
                    }

                    @Override
                    public String getServletPath() {
                        return lookupFinal;
                    }});
            }
        }
        return method;
    }

    private String getApiAndVersionContext() {
        return "/" + apiContext + "/" + versionContext;
    }

    private int getPreviousVersion(final String version) {
        return new Integer(version) - 1 ;
    }

}

实现读取 URL 中的版本,并从 spring 请求解析 URL。如果这个 URL 不存在(例如客户端请求 v3),那么我们尝试使用 v2 等等,直到我们找到资源的最新版本.

为了看到这个实现的好处,假设我们有两个资源:用户和公司:

http://localhost:9001/api/v{version}/user
http://localhost:9001/api/v{version}/company

假设我们对破坏客户的公司“合同”进行了更改。因此,我们实现了 http://localhost:9001/api/v2/company,并要求客户在 v1 上更改为 v2。

所以来自客户的新请求是:

http://localhost:9001/api/v2/user
http://localhost:9001/api/v2/company

代替:

http://localhost:9001/api/v1/user
http://localhost:9001/api/v1/company

这里最好的部分是,使用此解决方案,客户端将从 v1 获取用户信息并从 v2 获取公司信息,而无需从用户 v2 创建新的(相同的)端点!

休息文档 正如我之前所说,我选择基于 URL 的版本控制方法的原因是,像 swagger 这样的一些工具不会以不同的方式记录具有相同 URL 但不同内容类型的端点。使用此解决方案,两个端点都会显示,因为它们的 URL 不同:

https://i.stack.imgur.com/Hbeln.png

胃肠道

解决方案实施:https://github.com/mspapant/restVersioningExample/


我希望得到尝试过此解决方案的人的反馈。 :)
注意:如果您使用 /v0 或 /v-1 发送请求,则会发生无限循环。要修复它,您需要将 if (previousVersion != 0) { 设置为 >0,并且您需要在 getPreviousVersion() 中捕获异常并返回 -1
对于最新的 Spring 版本 2.4.2,您需要调整创建新请求以:gist.github.com/eisenreich/6ab40616a9e694bc2220c68ec3a01455
github.com/OAI/OpenAPI-Specification/issues/146 在 OpenAPI 3.x 规范中得到解决
e
elusive-code

我仍然建议使用 URL 进行版本控制,因为在 URL 中 @RequestMapping 支持模式和路径参数,可以使用正则表达式指定格式。

并且要处理客户端升级(您在评论中提到),您可以使用“最新”之类的别名。或者有使用最新版本的未版本化的 api(是的)。

同样使用路径参数,您可以实现任何复杂的版本处理逻辑,如果您已经想要范围,那么您很可能想要更快的东西。

这里有几个例子:

@RequestMapping({
    "/**/public_api/1.1/method",
    "/**/public_api/1.2/method",
})
public void method1(){
}

@RequestMapping({
    "/**/public_api/1.3/method"
    "/**/public_api/latest/method"
    "/**/public_api/method" 
})
public void method2(){
}

@RequestMapping({
    "/**/public_api/1.4/method"
    "/**/public_api/beta/method"
})
public void method2(){
}

//handles all 1.* requests
@RequestMapping({
    "/**/public_api/{version:1\\.\\d+}/method"
})
public void methodManual1(@PathVariable("version") String version){
}

//handles 1.0-1.6 range, but somewhat ugly
@RequestMapping({
    "/**/public_api/{version:1\\.[0123456]?}/method"
})
public void methodManual1(@PathVariable("version") String version){
}

//fully manual version handling
@RequestMapping({
    "/**/public_api/{version}/method"
})
public void methodManual2(@PathVariable("version") String version){
    int[] versionParts = getVersionParts(version);
    //manual handling of versions
}

public int[] getVersionParts(String version){
    try{
        String[] versionParts = version.split("\\.");
        int[] result = new int[versionParts.length];
        for(int i=0;i<versionParts.length;i++){
            result[i] = Integer.parseInt(versionParts[i]);
        }
        return result;
    }catch (Exception ex) {
        return null;
    }
}

基于最后一种方法,您实际上可以实现您想要的东西。

例如,您可以拥有一个仅包含带有版本处理的方法 stab 的控制器。

在该处理中,您查看(使用反射/AOP/代码生成库)在某些 spring 服务/组件或同一类中具有相同名称/签名和需要 @VersionRange 的方法并调用它并传递所有参数。


佚名

@RequestMapping 注释支持允许您缩小匹配请求的 headers 元素。特别是,您可以在此处使用 Accept 标头。

@RequestMapping(headers = {
    "Accept=application/vnd.company.app-1.0+json",
    "Accept=application/vnd.company.app-1.1+json"
})

这与您所描述的不完全一样,因为它不直接处理范围,但该元素确实支持 * 通配符和 !=。因此,对于所有版本都支持相关端点的情况,甚至是给定主要版本的所有次要版本(例如 1.*),至少您可以避免使用通配符。

我认为我之前实际上没有使用过这个元素(如果我不记得的话),所以我只是离开了文档

http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html


我知道这一点,但正如您所指出的,在每个版本上,我都需要访问我的所有控制器并添加一个版本,即使它们没有更改。您提到的范围仅适用于完整类型,例如 application/* 而不是类型的一部分。例如以下在 Spring "Accept=application/vnd.company.app-1.*+json" 中是无效的。这与弹簧类 MediaType 的工作方式有关
@Augusto 您不一定需要这样做。使用这种方法,您不是对“API”而是“端点”进行版本控制。每个端点可以有不同的版本。对我来说,这是对 API compared with API version 进行版本控制的最简单方法。招摇也更simple to setup。这种策略称为通过内容协商进行版本控制。
D
Dherik

我已经尝试使用 URI 版本控制对我的 API 进行版本控制,例如:

/api/v1/orders
/api/v2/orders

但是在尝试完成这项工作时存在一些挑战:如何用不同的版本组织代码?如何同时管理两个(或更多)版本?删除某些版本有什么影响?

我发现最好的替代方案不是对整个 API 进行版本控制,而是控制每个端点上的版本。此模式称为 Versioning using Accept headerVersioning through content negotiation

这种方法允许我们对单个资源表示进行版本控制,而不是对整个 API 进行版本控制,这使我们能够更精细地控制版本控制。它还在代码库中创建了更小的占用空间,因为我们在创建新版本时不必分叉整个应用程序。这种方法的另一个优点是它不需要实现通过 URI 路径进行版本控制引入的 URI 路由规则。

在 Spring 上实现

首先,您创建一个具有 produces 属性的控制器,该属性将默认应用于同一类内的每个端点。

@RestController
@RequestMapping(value = "/api/orders/", produces = "application/vnd.company.etc.v1+json")
public class OrderController {

}

之后,我们可以想象一个可能的场景,您有两个版本(v1 和 v2)用于“创建订单”的端点:

@Deprecated
@PostMapping
public ResponseEntity<OrderResponse> createV1(
        @RequestBody OrderRequest orderRequest) {

    OrderResponse response = createOrderService.createOrder(orderRequest);
    return new ResponseEntity<>(response, HttpStatus.CREATED);
}

@PostMapping(
        produces = "application/vnd.company.etc.v2+json",
        consumes = "application/vnd.company.etc.v2+json")
public ResponseEntity<OrderResponseV2> createV2(
        @RequestBody OrderRequestV2 orderRequest) {

    OrderResponse response = createOrderService.createOrder(orderRequest);
    return new ResponseEntity<>(response, HttpStatus.CREATED);
}

完毕!只需使用所需的 Http Header 版本调用每个端点:

Content-Type: application/vnd.company.etc.v1+json

或者,调用 v2:

Content-Type: application/vnd.company.etc.v2+json

关于您的担忧:

而且由于并非 API 中的所有方法都在同一个版本中更改,因此我不想转到我的每个控制器并更改版本之间未更改的处理程序的任何内容

如前所述,此策略使用其实际版本维护每个控制器和端点。您只需修改已修改并需要新版本的端点。

和招摇?

使用这种策略设置不同版本的 Swagger 也很容易。 See this answer 了解更多详情。


T
Tim Schimandle

仅使用继承来建模版本控制怎么样?这就是我在我的项目中使用的,它不需要特殊的弹簧配置,并且可以得到我想要的。

@RestController
@RequestMapping(value = "/test/1")
@Deprecated
public class Test1 {
...Fields Getters Setters...
    @RequestMapping(method = RequestMethod.GET)
    @Deprecated
    public Test getTest(Long id) {
        return serviceClass.getTestById(id);
    }
    @RequestMapping(method = RequestMethod.PUT)
    public Test getTest(Test test) {
        return serviceClass.updateTest(test);
    }

}

@RestController
@RequestMapping(value = "/test/2")
public class Test2 extends Test1 {
...Fields Getters Setters...
    @Override
    @RequestMapping(method = RequestMethod.GET)
    public Test getTest(Long id) {
        return serviceClass.getAUpdated(id);
    }

    @RequestMapping(method = RequestMethod.DELETE)
    public Test deleteTest(Long id) {
        return serviceClass.deleteTestById(id);
    }
}

这种设置允许很少的代码重复,并且能够以很少的工作将方法覆盖到新版本的 api 中。它还省去了使用版本切换逻辑使源代码复杂化的需要。如果您不在某个版本中编写端点代码,它将默认获取以前的版本。

与其他人所做的相比,这似乎更容易。有什么我想念的吗?


+1 用于共享代码。然而,继承将它紧紧地耦合在一起。反而。控制器(Test1 和 Test2)应该只是通过......没有逻辑实现。一切逻辑都应该在服务类 someService 中。在这种情况下,只需使用简单的组合,永远不要从其他控制器继承
@dan-hunex 似乎 Ceekay 使用继承来管理不同版本的 api。如果删除继承,解决方案是什么?为什么在这个例子中紧耦合是一个问题?在我看来,Test2 扩展了 Test1,因为它是对它的改进(具有相同的角色和相同的职责),不是吗?
c
codesalsa

在产生你可以有否定。所以对于 method1 来说 produces="!...1.7" 并且在 method2 中是肯定的。

产品也是一个数组,因此对于方法 1,您可以说 produces={"...1.6","!...1.7","...1.8"} 等(接受除 1.7 之外的所有内容)

当然不像你想象的范围那么理想,但我认为如果这在你的系统中不常见的话,它比其他自定义的东西更容易维护。祝你好运!


感谢codesalsa,我正在尝试找到一种易于维护的方法,并且每次我们需要升级版本时都不需要更新每个端点。
h
hevi

可以使用AOP,左右拦截

考虑有一个接收所有 /**/public_api/* 的请求映射,并且在此方法中什么都不做;

@RequestMapping({
    "/**/public_api/*"
})
public void method2(Model model){
}

@Override
public void around(Method method, Object[] args, Object target)
    throws Throwable {
       // look for the requested version from model parameter, call it desired range
       // check the target object for @VersionRange annotation with reflection and acquire version ranges, call the function if it is in the desired range


}

唯一的限制是所有的都必须在同一个控制器中。

对于 AOP 配置,请查看 http://www.mkyong.com/spring/spring-aop-examples-advice/


谢谢hevi,我正在寻找一种更“弹簧”友好的方式来做到这一点,因为Spring已经选择了不使用AOP来调用的方法。我认为 AOP 增加了我想避免的新级别的代码复杂性。
@Augusto,Spring 有很好的 AOP 支持。你应该试试看。 :)