it-swarm.com.de

Wie verwalte ich die REST API-Versionierung mit spring?

Ich habe gesucht, wie man eine REST API-Version mit Spring 3.2.x verwaltet, aber ich habe nichts gefunden, was einfach zu warten ist. Ich werde zuerst das Problem erläutern, das ich habe. und dann eine lösung ... aber ich frage mich, ob ich hier das rad neu erfinden werde.

Ich möchte die Version basierend auf dem Accept-Header verwalten. Wenn eine Anforderung beispielsweise den Accept-Header application/vnd.company.app-1.1+json Enthält, möchte ich, dass Spring MVC dies an die Methode weiterleitet, die diese Version verarbeitet. Und da sich nicht alle Methoden in einer API in derselben Version ändern, möchte ich nicht zu jedem meiner Controller wechseln und etwas für einen Handler ändern, der sich zwischen den Versionen nicht geändert hat. Ich möchte auch nicht die Logik haben, um herauszufinden, welche Version im Controller selbst verwendet werden soll (mithilfe von Service Locators), da Spring bereits feststellt, welche Methode aufzurufen ist.

Nehmen wir also eine API mit den Versionen 1.0 bis 1.8, in der ein Handler in Version 1.0 eingeführt und in Version 1.7 geändert wurde. Ich würde dies gerne folgendermaßen behandeln. Stellen Sie sich vor, der Code befindet sich in einem Controller und es gibt einen Code, der die Version aus dem Header extrahieren kann. (Folgendes ist im Frühjahr ungültig)

@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;
}

Dies ist im Frühjahr nicht möglich, da die beiden Methoden dieselbe Anmerkung RequestMapping haben und Spring nicht geladen werden kann. Die Idee ist, dass die Annotation VersionRange einen offenen oder geschlossenen Versionsbereich definieren kann. Die erste Methode ist ab Version 1.0 bis 1.6 gültig, die zweite ab Version 1.7 (einschließlich der neuesten Version 1.8). Ich weiß, dass dieser Ansatz abbricht, wenn sich jemand entscheidet, Version 99.99 zu bestehen, aber damit kann ich in Ordnung leben.

Da dies ohne eine gründliche Überarbeitung der Funktionsweise des Frühlings nicht möglich ist, habe ich darüber nachgedacht, die Art und Weise zu ändern, in der die Handler auf die Anforderungen abgestimmt sind, insbesondere meine eigenen ProducesRequestCondition zu schreiben und den Versionsbereich darin zu haben . Beispielsweise

Code:

@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;
}

Auf diese Weise kann ich geschlossene oder offene Versionsbereiche festlegen, die im Erzeugtenteil der Annotation definiert sind. Ich arbeite jetzt an dieser Lösung, mit dem Problem, dass ich noch einige Kern-Spring-MVC-Klassen (RequestMappingInfoHandlerMapping, RequestMappingHandlerMapping und RequestMappingInfo) ersetzen musste, die ich nicht habe. ' Ich mag es nicht, weil es zusätzliche Arbeit bedeutet, wenn ich mich für ein Upgrade auf eine neuere Version von spring entscheide.

Ich würde mich über jeden Gedanken freuen ... und insbesondere über jeden Vorschlag, dies auf eine einfachere, leichter zu pflegende Art und Weise zu tun.


Bearbeiten

Kopfgeld hinzufügen. Um das Kopfgeld zu erhalten, beantworten Sie bitte die Frage oben, ohne vorzuschlagen, diese Logik im Controller selbst zu haben. Spring hat bereits eine Menge Logik, um auszuwählen, welche Controller-Methode aufgerufen werden soll, und ich möchte darauf zurückgreifen.


Bearbeiten 2

Ich habe den ursprünglichen POC (mit einigen Verbesserungen) in Github geteilt: https://github.com/augusto/restVersioning

103
Augusto

Unabhängig davon, ob die Versionierung durch abwärtskompatible Änderungen vermieden werden kann (was möglicherweise nicht immer möglich ist, wenn Sie an Unternehmensrichtlinien gebunden sind oder Ihre API-Clients fehlerhaft implementiert sind und auch dann nicht funktionieren, wenn dies nicht der Fall ist), ist die abstrahierte Anforderung von Interesse eins:

Wie kann ich eine benutzerdefinierte Anforderungszuordnung durchführen, die willkürliche Auswertungen von Headerwerten aus der Anforderung vornimmt, ohne die Auswertung im Methodentext vorzunehmen?

Wie in diese SO Antwort beschrieben, können Sie tatsächlich das gleiche @RequestMapping und verwenden Sie eine andere Annotation, um während des tatsächlichen Routings, das zur Laufzeit stattfindet, zu unterscheiden. Dazu müssen Sie:

  1. Erstellen Sie eine neue Anmerkung VersionRange.
  2. Implementiere ein RequestCondition<VersionRange>. Da Sie so etwas wie einen Best-Match-Algorithmus haben, müssen Sie prüfen, ob mit anderen VersionRange -Werten annotierte Methoden eine bessere Übereinstimmung für die aktuelle Anforderung liefern.
  3. Implementieren Sie ein VersionRangeRequestMappingHandlerMapping basierend auf der Annotations- und Anforderungsbedingung (wie im Abschnitt nach Implementieren der benutzerdefinierten Eigenschaften von @RequestMapping beschrieben).
  4. Konfigurieren Sie spring so, dass Ihr VersionRangeRequestMappingHandlerMapping ausgewertet wird, bevor Sie den Standardwert RequestMappingHandlerMapping verwenden (z. B. indem Sie die Reihenfolge auf 0 setzen).

Dies erfordert keinen komplizierten Austausch von Spring-Komponenten, sondern verwendet die Spring-Konfigurations- und -Erweiterungsmechanismen, sodass dies auch dann funktionieren sollte, wenn Sie Ihre Spring-Version aktualisieren (sofern die neue Version diese Mechanismen unterstützt).

58
xwoker

Ich habe gerade eine benutzerdefinierte Lösung erstellt. Ich benutze die @ApiVersion Anmerkung in Kombination mit @RequestMapping Anmerkung innerhalb @Controller Klassen.

Beispiel:

@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

}

Implementierung:

ApiVersion.Java Anmerkung:

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

ApiVersionRequestMappingHandlerMapping.Java (meistens kopieren und einfügen aus 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);
    }

}

Injection in WebMvcConfigurationSupport:

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

Ich würde weiterhin die Verwendung von URLs für die Versionsverwaltung empfehlen, da @RequestMapping in URLs Muster und Pfadparameter unterstützt, deren Format mit regulären Ausdrücken angegeben werden kann.

Für Client-Upgrades (die Sie in Ihrem Kommentar erwähnt haben) können Sie Aliase wie "latest" verwenden. Oder haben Sie nicht versionierte Version von API, die die neueste Version verwendet (ja).

Durch die Verwendung von Pfadparametern können Sie auch jede komplexe Versionsbehandlungslogik implementieren. Wenn Sie bereits über Bereiche verfügen möchten, möchten Sie möglicherweise früh genug etwas anderes.

Hier sind einige Beispiele:

@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;
    }
}

Basierend auf dem letzten Ansatz können Sie tatsächlich so etwas wie das implementieren, was Sie wollen.

Beispielsweise können Sie einen Controller haben, der nur Methodenstiche mit Versionsverwaltung enthält.

In dieser Behandlung suchen Sie (unter Verwendung von Reflection/AOP/Codegenerierungsbibliotheken) in einem Spring-Service/einer Spring-Komponente oder in derselben Klasse nach Methoden mit demselben Namen/derselben Signatur und der erforderlichen @VersionRange und rufen sie auf, indem Sie alle Parameter übergeben.

16
elusive-code

Ich habe eine Lösung implementiert, die [~ # ~] perfekt behandelt [~ # ~] das Problem mit der Rest-Versionierung.

Allgemeines Es gibt drei Hauptansätze für die Versionsverwaltung:

  • Pfad - basierter Ansatz, in dem der Client die Version in URL definiert:

    http://localhost:9001/api/v1/user
    http://localhost:9001/api/v2/user
    
  • Content-Type Header, in dem der Client die Version definiert in Accept Header:

    http://localhost:9001/api/v1/user with 
    Accept: application/vnd.app-1.0+json OR application/vnd.app-2.0+json
    
  • Benutzerdefinierter Header , in dem der Client die Version in einem benutzerdefinierten Header definiert.

Das Problem mit dem ersten Ansatz ist, dass wenn Sie die Version ändern, lassen Sie uns Sagen wir von v1 -> v2, wahrscheinlich müssen Sie die Ressourcen von v1, die sich nicht in den Pfad von v2 geändert haben, kopieren und einfügen

Das Problem mit dem zweiten Ansatz ist, dass einige Tools wie http://swagger.io/ kann nicht zwischen Operationen mit demselben Pfad, aber unterschiedlichem Inhaltstyp unterscheiden. (Überprüfen Sie das Problem https://github.com/OAI/OpenAPI-Specification/issues/146 =)

Die Lösung

Da ich viel mit Restdokumentationstools arbeite, bevorzuge ich den ersten Ansatz. Meine Lösung behandelt das Problem mit dem ersten Ansatz, sodass Sie den Endpunkt nicht in die neue Version kopieren und einfügen müssen.

Angenommen, wir haben die Versionen v1 und v2 für den Benutzer-Controller:

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";
    }
 }

Die Anforderung ist, wenn ich das v1 für die Benutzerressource anfordere, die ich habe die "User V1" Antwort zu nehmen, ansonsten wenn ich die v2 , v3 und so weiter muss ich den "User V2" Antwort.

enter image description here

Um dies im Frühjahr zu implementieren, müssen wir das Standardverhalten RequestMappingHandlerMapping überschreiben:

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 ;
    }

}

Die Implementierung liest die Version in der URL und bittet ab Frühjahr, die URL aufzulösen. Falls diese URL nicht existiert (zum Beispiel der Client hat v3 ) dann versuchen wir es mit v2 und so weiter, bis wir die aktuellste Version finden für die Ressource.

Nehmen wir an, wir haben zwei Ressourcen, um die Vorteile dieser Implementierung zu sehen: Benutzer und Firma:

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

Nehmen wir an, wir haben den "Vertrag" des Unternehmens geändert, der den Kunden bricht. Also implementieren wir die http://localhost:9001/api/v2/company und wir bitten den Client, stattdessen auf v1 auf v2 zu wechseln.

Die neuen Anforderungen des Kunden lauten also:

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

anstatt:

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

Der beste Teil hier ist, dass mit dieser Lösung der Client die Benutzerinformationen von v1 und die Unternehmensinformationen von v2 erhält ohne die Notwendigkeit , einen neuen (gleichen) Endpunkt von Benutzer v2 zu erstellen!

Rest Documentation Wie ich bereits sagte, ist der Grund, warum ich den URL-basierten Versionsansatz wähle, dass einige Tools wie swagger die Endpunkte mit derselben URL nicht unterschiedlich dokumentieren aber andere inhaltstypen. Bei dieser Lösung werden beide Endpunkte angezeigt, da sie unterschiedliche URLs haben:

enter image description here

[~ # ~] git [~ # ~]

Lösungsimplementierung unter: https://github.com/mspapant/restVersioningExample/

11
mspapant

Das @RequestMapping annotation unterstützt ein headers -Element, mit dem Sie die übereinstimmenden Anforderungen eingrenzen können. Insbesondere können Sie hier den Header Accept verwenden.

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

Dies ist nicht genau das, was Sie beschreiben, da es keine Bereiche direkt behandelt, aber das Element unterstützt den Platzhalter * sowie! =. Zumindest könnten Sie sich also damit abfinden, Platzhalter zu verwenden, wenn alle Versionen den betreffenden Endpunkt unterstützen oder sogar alle Nebenversionen einer bestimmten Hauptversion (z. B. 1. *).

Ich glaube nicht, dass ich dieses Element schon einmal verwendet habe (wenn ich mich nicht erinnere), also gehe ich einfach von der Dokumentation ab

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

8
Willie Wheeler

Was ist mit der einfachen Verwendung der Vererbung zur Modellversionierung? Das ist, was ich in meinem Projekt verwende und es erfordert keine spezielle Federkonfiguration und bringt mich genau zu dem, was ich will.

@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);
    }
}

Diese Einstellung ermöglicht eine geringe Vervielfältigung des Codes und die Möglichkeit, Methoden mit geringem Aufwand in neue Versionen der API zu überschreiben. Es erspart Ihnen auch die Notwendigkeit, Ihren Quellcode mit der Versionsumschaltlogik zu komplizieren. Wenn Sie keinen Endpunkt in einer Version codieren, wird standardmäßig die vorherige Version abgerufen.

Verglichen mit dem, was andere tun, scheint dies viel einfacher zu sein. Fehlt mir etwas?

3
Ceekay

Ich habe bereits versucht, meine API mit URI Versioning zu versionieren, wie:

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

Es gibt jedoch einige Herausforderungen, wenn Sie versuchen, dies zum Laufen zu bringen: Wie organisieren Sie Ihren Code mit verschiedenen Versionen? Wie verwalte ich zwei (oder mehr) Versionen gleichzeitig? Welche Auswirkungen hat das Entfernen einer Version?

Die beste Alternative, die ich gefunden habe, war nicht die Version der gesamten API, sondern Version auf jedem Endpunkt kontrollieren. Dieses Muster heißt Versionierung mit Accept-Header oder Versionierung durch Inhaltsverhandlung :

Dieser Ansatz ermöglicht es uns, eine einzelne Ressourcendarstellung zu versionieren, anstatt die gesamte API zu versionieren, wodurch wir eine genauere Kontrolle über die Versionierung erhalten. Außerdem wird der Platzbedarf in der Codebasis verringert, da beim Erstellen einer neuen Version nicht die gesamte Anwendung aufgeteilt werden muss. Ein weiterer Vorteil dieses Ansatzes besteht darin, dass keine URI-Routingregeln implementiert werden müssen, die durch die Versionierung über den URI-Pfad eingeführt wurden.

Umsetzung im Frühjahr

Zunächst erstellen Sie einen Controller mit einem Basic Produces-Attribut, das standardmäßig für jeden Endpunkt in der Klasse gilt.

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

}

Erstellen Sie anschließend ein mögliches Szenario, in dem Sie zwei Versionen eines Endpunkts zum Erstellen einer Bestellung haben:

@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);
}

Getan! Rufen Sie einfach jeden Endpunkt mit der gewünschten Http Header Version auf:

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

Oder, um die Version zwei aufzurufen:

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

Über deine Sorgen:

Und da sich nicht alle Methoden in einer API in derselben Version ändern, möchte ich nicht zu jedem meiner Controller wechseln und etwas für einen Handler ändern, der sich zwischen den Versionen nicht geändert hat

Wie erläutert, behält diese Strategie jeden Controller und Endpunkt mit seiner aktuellen Version bei. Sie ändern nur den Endpunkt, für den Änderungen vorgenommen wurden und der eine neue Version benötigt.

Und der Prahler?

Mit dieser Strategie ist es auch sehr einfach, den Swagger mit verschiedenen Versionen einzurichten. Siehe diese Antwort zu weiteren Einzelheiten.

1
Dherik

In Erzeugnissen kann man Negation haben. Also für method1 sagen produces="!...1.7" und in method2 haben die positive.

Das Erzeugnis ist auch ein Array, also können Sie für method1 produces={"...1.6","!...1.7","...1.8"} etc (akzeptiere alle außer 1.7)

Natürlich nicht so ideal wie die Bereiche, an die Sie denken, aber ich denke, es ist einfacher zu pflegen als andere benutzerdefinierte Dinge, wenn dies in Ihrem System etwas Ungewöhnliches ist. Viel Glück!

1
codesalsa

Sie können AOP verwenden, um abzufangen

Erwägen Sie eine Anforderungszuordnung, die alle /**/public_api/* und bei dieser Methode nichts tun;

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

Nach

@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


}

Die einzige Einschränkung ist, dass sich alle in demselben Controller befinden müssen.

Informationen zur AOP-Konfiguration finden Sie unter http://www.mkyong.com/spring/spring-aop-examples-advice/

0
hevi