java - वसंत के साथ आरईएसटी एपीआई संस्करण का प्रबंधन कैसे करें?




spring rest (6)

आप एओपी का उपयोग, अवरोध के आसपास कर सकते हैं

एक अनुरोध मैपिंग होने पर विचार करें जो सभी /**/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


}

एकमात्र बाधा यह है कि सभी को एक ही नियंत्रक में होना चाहिए।

एओपी कॉन्फ़िगरेशन के लिए http://www.mkyong.com/spring/spring-aop-examples-advice/ पर एक नज़र डालें

मैं खोज रहा हूं कि वसंत 3.2.x का उपयोग कर एक आरईएसटी एपीआई संस्करणों का प्रबंधन कैसे करें, लेकिन मुझे कुछ भी नहीं मिला है जो बनाए रखना आसान है। मैं सबसे पहले अपनी समस्या की व्याख्या करूंगा, और फिर एक समाधान ... लेकिन मुझे आश्चर्य है कि मैं यहां पहिया का पुन: आविष्कार कर रहा हूं।

मैं स्वीकृति शीर्षलेख के आधार पर संस्करण का प्रबंधन करना चाहता हूं, और उदाहरण के लिए यदि अनुरोध में स्वीकृति हेडर application/vnd.company.app-1.1+json , तो मैं इस संस्करण को संभालने वाली विधि को आगे बढ़ाने के लिए वसंत एमवीसी चाहता हूं। और चूंकि एक ही रिलीज में एपीआई में सभी विधियों में बदलाव नहीं होता है, इसलिए मैं अपने प्रत्येक नियंत्रक पर नहीं जाना चाहता हूं और ऐसे हैंडलर के लिए कुछ भी बदलना नहीं चाहता जो संस्करणों के बीच नहीं बदला गया है। मैं यह भी समझना नहीं चाहता कि कौन सा संस्करण नियंत्रक में स्वयं (सेवा locators का उपयोग कर) का उपयोग करना है क्योंकि स्प्रिंग पहले से ही कॉल करने के लिए कौन सी विधि खोज रहा है।

तो संस्करण 1.0 के साथ एक एपीआई ले लिया, 1.8 जहां एक हैंडलर संस्करण 1.0 में पेश किया गया था और v1.7 में संशोधित किया गया था, मैं इसे निम्न तरीके से संभालना चाहता हूं। कल्पना करें कि कोड एक नियंत्रक के अंदर है, और यह कि कुछ कोड है जो हेडर से संस्करण निकालने में सक्षम है। (वसंत में निम्नलिखित अमान्य है)

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

वसंत में यह संभव नहीं है क्योंकि 2 विधियों में एक ही RequestMapping एनोटेशन है और वसंत लोड करने में विफल रहता है। विचार यह है कि संस्करण श्रेणी एनोटेशन एक खुली या बंद संस्करण सीमा को परिभाषित कर सकता है। पहली विधि संस्करण 1.0 से 1.6 तक मान्य है, जबकि संस्करण 1.7 के बाद दूसरा (नवीनतम संस्करण 1.8 सहित)। मुझे पता है कि अगर कोई व्यक्ति संस्करण 99.99 पास करने का फैसला करता है तो यह दृष्टिकोण टूट जाता है, लेकिन ऐसा कुछ है जिसके साथ मैं रहना ठीक हूं।

अब, चूंकि वसंत कैसे काम करता है, इसके बारे में गंभीर पुनर्विक्रय के बिना उपरोक्त संभव नहीं है, इसलिए मैं अनुरोधों से मेल खाने वाले हैंडलरों के साथ झुकाव करने की सोच रहा था, विशेष रूप से अपने स्वयं के उत्पाद लिखने के लिए, और वहां संस्करण संस्करण है। उदाहरण के लिए

कोड:

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

इस तरह, मैं एनोटेशन के हिस्से में परिभाषित बंद या खुली संस्करण श्रेणियां रख सकता हूं। मैं अब इस समाधान पर काम कर रहा हूं, इस समस्या के साथ कि मुझे अभी भी कुछ मूल स्प्रिंग एमवीसी कक्षाओं ( RequestMappingInfoHandlerMapping , RequestMappingHandlerMapping और RequestMappingInfo ) को प्रतिस्थापित करना है, जो मुझे पसंद नहीं है, क्योंकि जब भी मैं अपग्रेड करने का निर्णय लेता हूं तो इसका अतिरिक्त काम होता है वसंत का नया संस्करण।

मैं किसी भी विचार की सराहना करता हूं ... और विशेष रूप से, इसे सरल, आसान बनाए रखने के तरीके में ऐसा करने का कोई सुझाव।

संपादित करें

एक बक्षीस जोड़ना बक्षीस प्राप्त करने के लिए, कृपया इस तर्क को नियंत्रक में स्वयं रखने का सुझाव दिए बिना ऊपर दिए गए प्रश्न का उत्तर दें। स्प्रिंग में पहले से ही बहुत सारे तर्क हैं कि कौन से नियंत्रक विधि को कॉल करना है, और मैं उस पर पिगबैक करना चाहता हूं।

2 संपादित करें

मैंने github में मूल पीओसी (कुछ सुधारों के साथ) साझा किया है: https://github.com/augusto/restVersioning


उत्पादन में आप निषेध कर सकते हैं। तो विधि 1 के लिए कहता produces="!...1.7" और विधि 2 में सकारात्मक है।

उत्पादन भी एक सरणी है ताकि आप विधि 1 के लिए कह सकें produces={"...1.6","!...1.7","...1.8"} आदि (सभी को छोड़कर 1.7 को स्वीकार करें)

आपके मन में श्रेणियों के रूप में आदर्श नहीं है, लेकिन मुझे लगता है कि यह आपके कस्टम सिस्टम में कुछ असामान्य है, तो अन्य कस्टम सामानों की तुलना में बनाए रखना आसान लगता है। सौभाग्य!


मैं अभी भी वर्जनिंग के लिए यूआरएल का उपयोग करने की सिफारिश करता हूं क्योंकि URL में @RequestMapping पैटर्न और पथ पैरामीटर का समर्थन करता है, जिसे प्रारूप regexp के साथ निर्दिष्ट किया जा सकता है।

और क्लाइंट अपग्रेड (जिसे आपने टिप्पणी में उल्लेख किया है) को संभालने के लिए आप 'नवीनतम' जैसे उपनाम का उपयोग कर सकते हैं। या एपीआई का विचलित संस्करण है जो नवीनतम संस्करण (हाँ) का उपयोग करता है।

पथ पैरामीटर का उपयोग करके आप किसी भी जटिल संस्करण को तर्कसंगत तर्क को कार्यान्वित कर सकते हैं, और यदि आप पहले से ही श्रेणियां चाहते हैं, तो आप बहुत जल्द कुछ और चाहते हैं।

यहां कुछ उदाहरण दिए गए हैं:

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

आखिरी दृष्टिकोण के आधार पर आप वास्तव में कुछ जो आप चाहते हैं उसे लागू कर सकते हैं।

उदाहरण के लिए आपके पास एक नियंत्रक हो सकता है जिसमें संस्करण हैंडलिंग के साथ केवल विधि स्टैब्स हों।

उस हैंडलिंग में आप कुछ वसंत सेवा / घटक में या उसी नाम / हस्ताक्षर के साथ विधि के लिए उसी वर्ग में प्रतिबिंब / एओपी / कोड जनरेशन पुस्तकालयों का उपयोग करके देखें और आवश्यक @VersionRange और इसे सभी मानकों को पारित करने का आह्वान करें।


मैंने अभी एक कस्टम समाधान बनाया है। मैं @ApiVersion कक्षाओं के अंदर @ApiVersion एनोटेशन के साथ संयोजन में @ApiVersion एनोटेशन का उपयोग कर रहा हूं।

उदाहरण:

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

}

WebMvc कॉन्फ़िगरेशन में इंजेक्शन समर्थन:

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

मॉडल संस्करण के लिए विरासत का उपयोग करने के बारे में क्या? यही वह है जो मैं अपने प्रोजेक्ट में उपयोग कर रहा हूं और इसके लिए कोई विशेष वसंत कॉन्फ़िगरेशन की आवश्यकता नहीं है और मुझे वही चाहिए जो मैं चाहता हूं।

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

यह सेट अप कोड के कम डुप्लिकेशंस और छोटे काम के साथ एपीआई के नए संस्करणों में विधियों को ओवरराइट करने की क्षमता देता है। यह संस्करण स्विचिंग तर्क के साथ अपने स्रोत कोड को जटिल करने की आवश्यकता को भी बचाता है। यदि आप किसी संस्करण में एंडपॉइंट कोड नहीं करते हैं तो यह डिफ़ॉल्ट रूप से पिछले संस्करण को पकड़ लेगा।

दूसरों के साथ ऐसा करने की तुलना में यह आसान तरीका लगता है। क्या मुझे कुछ याद आ रही है?


@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





versioning