diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml index 8c40df1b80c..76bdd1c70eb 100644 --- a/dubbo-dependencies-bom/pom.xml +++ b/dubbo-dependencies-bom/pom.xml @@ -194,6 +194,7 @@ 1.5.0 1.23.0 3.3.0-beta.1-SNAPSHOT + 3.1.5 @@ -949,6 +950,11 @@ rxjava ${rxjava.version} + + org.springframework.cloud + spring-cloud-openfeign-core + ${open_feign_version} + diff --git a/dubbo-metadata/dubbo-metadata-rest/pom.xml b/dubbo-metadata/dubbo-metadata-rest/pom.xml index 65893ee29e2..3179070537a 100644 --- a/dubbo-metadata/dubbo-metadata-rest/pom.xml +++ b/dubbo-metadata/dubbo-metadata-rest/pom.xml @@ -58,6 +58,12 @@ spring-context test + + + org.springframework.cloud + spring-cloud-openfeign-core + test + diff --git a/dubbo-metadata/dubbo-metadata-rest/src/main/java/org/apache/dubbo/metadata/rest/RestMetadataConstants.java b/dubbo-metadata/dubbo-metadata-rest/src/main/java/org/apache/dubbo/metadata/rest/RestMetadataConstants.java index b96234d764b..e43b5b557d8 100644 --- a/dubbo-metadata/dubbo-metadata-rest/src/main/java/org/apache/dubbo/metadata/rest/RestMetadataConstants.java +++ b/dubbo-metadata/dubbo-metadata-rest/src/main/java/org/apache/dubbo/metadata/rest/RestMetadataConstants.java @@ -109,6 +109,11 @@ interface SPRING_MVC { */ String CONTROLLER_ANNOTATION_CLASS_NAME = "org.springframework.stereotype.Controller"; + /** + * The annotation class name of @FeignClient + */ + String FEIGN_CLIENT_CLASS_NAME = "org.springframework.cloud.openfeign.FeignClient"; + /** * The annotation class name of @RequestMapping */ @@ -161,5 +166,8 @@ interface SPRING_MVC { * @since 2.7.9 */ Class ANNOTATED_ELEMENT_UTILS_CLASS = resolveClass(ANNOTATED_ELEMENT_UTILS_CLASS_NAME, getClassLoader()); + + Class FEIGN_CLIENT_CLASS = (Class) resolveClass(FEIGN_CLIENT_CLASS_NAME, getClassLoader()); + } } diff --git a/dubbo-metadata/dubbo-metadata-rest/src/main/java/org/apache/dubbo/metadata/rest/springmvc/SpringMvcServiceRestMetadataResolver.java b/dubbo-metadata/dubbo-metadata-rest/src/main/java/org/apache/dubbo/metadata/rest/springmvc/SpringMvcServiceRestMetadataResolver.java index 765b2308554..da8035915cd 100644 --- a/dubbo-metadata/dubbo-metadata-rest/src/main/java/org/apache/dubbo/metadata/rest/springmvc/SpringMvcServiceRestMetadataResolver.java +++ b/dubbo-metadata/dubbo-metadata-rest/src/main/java/org/apache/dubbo/metadata/rest/springmvc/SpringMvcServiceRestMetadataResolver.java @@ -39,6 +39,7 @@ import static org.apache.dubbo.common.utils.PathUtils.buildPath; import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.ANNOTATED_ELEMENT_UTILS_CLASS; import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.CONTROLLER_ANNOTATION_CLASS; +import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.FEIGN_CLIENT_CLASS; import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.REQUEST_MAPPING_ANNOTATION_CLASS; /** @@ -58,7 +59,9 @@ public SpringMvcServiceRestMetadataResolver(ApplicationModel applicationModel) { @Override protected boolean supports0(Class serviceType) { // class @Controller or @RequestMapping - return isAnnotationPresent(serviceType, CONTROLLER_ANNOTATION_CLASS) || isAnnotationPresent(serviceType, REQUEST_MAPPING_ANNOTATION_CLASS); + return isAnnotationPresent(serviceType, CONTROLLER_ANNOTATION_CLASS) + || isAnnotationPresent(serviceType, FEIGN_CLIENT_CLASS) + || isAnnotationPresent(serviceType, REQUEST_MAPPING_ANNOTATION_CLASS); } @Override @@ -84,9 +87,10 @@ protected String resolveRequestMethod(Method serviceMethod, Class serviceType @Override protected String resolveRequestPath(Method serviceMethod, Class serviceType, Class serviceInterfaceClass) { - String requestBasePath = resolveRequestPath(serviceType); + String feignClientBasePath = resolveFeignClientBaseRequestPath(serviceType); + String requestMappingBasePath = resolveRequestPath(serviceType); String requestRelativePath = resolveRequestPath(serviceMethod); - return buildPath(requestBasePath, requestRelativePath); + return buildPath(feignClientBasePath, buildPath(requestMappingBasePath, requestRelativePath)); } @Override @@ -103,6 +107,18 @@ protected void processConsumes(Method serviceMethod, Class serviceType, Class addMediaTypes(serviceInterfaceClass, "consumes", consumes); } + private String resolveFeignClientBaseRequestPath(AnnotatedElement annotatedElement) { + Annotation feignClient = findAnnotation(annotatedElement, FEIGN_CLIENT_CLASS); + + String path = getAttribute(feignClient, "path"); + + if (path == null) { + return ""; + } + + return path; + } + private String resolveRequestPath(AnnotatedElement annotatedElement) { Annotation mappingAnnotation = getRequestMapping(annotatedElement); diff --git a/dubbo-metadata/dubbo-metadata-rest/src/test/java/org/apache/dubbo/metadata/rest/feign/FeignClientController.java b/dubbo-metadata/dubbo-metadata-rest/src/test/java/org/apache/dubbo/metadata/rest/feign/FeignClientController.java new file mode 100644 index 00000000000..fc1fe0fdbd7 --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-rest/src/test/java/org/apache/dubbo/metadata/rest/feign/FeignClientController.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.metadata.rest.feign; + + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@FeignClient(path = "/feign") +@RequestMapping("/context") +public interface FeignClientController { + + @RequestMapping(value = "/hello", method = RequestMethod.GET) + String hello(); +} diff --git a/dubbo-metadata/dubbo-metadata-rest/src/test/java/org/apache/dubbo/metadata/rest/feign/FeignClientControllerImpl.java b/dubbo-metadata/dubbo-metadata-rest/src/test/java/org/apache/dubbo/metadata/rest/feign/FeignClientControllerImpl.java new file mode 100644 index 00000000000..43333e15476 --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-rest/src/test/java/org/apache/dubbo/metadata/rest/feign/FeignClientControllerImpl.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.metadata.rest.feign; + + + +public class FeignClientControllerImpl implements FeignClientController{ + + @Override + public String hello() { + return "hello, feign"; + } +} diff --git a/dubbo-metadata/dubbo-metadata-rest/src/test/java/org/apache/dubbo/metadata/rest/springmvc/FeignClientAnnotationTest.java b/dubbo-metadata/dubbo-metadata-rest/src/test/java/org/apache/dubbo/metadata/rest/springmvc/FeignClientAnnotationTest.java new file mode 100644 index 00000000000..8a819db801e --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-rest/src/test/java/org/apache/dubbo/metadata/rest/springmvc/FeignClientAnnotationTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.metadata.rest.springmvc; + +import org.apache.dubbo.metadata.rest.PathMatcher; +import org.apache.dubbo.metadata.rest.RestMethodMetadata; +import org.apache.dubbo.metadata.rest.ServiceRestMetadata; +import org.apache.dubbo.metadata.rest.feign.FeignClientController; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +public class FeignClientAnnotationTest { + + private SpringMvcServiceRestMetadataResolver instance = new SpringMvcServiceRestMetadataResolver(ApplicationModel.defaultModel()); + + @Test + void testFeignClientAnnotationResolve() { + + Assertions.assertEquals(true, instance.supports(FeignClientController.class)); + Class service = FeignClientController.class; + ServiceRestMetadata serviceRestMetadata = new ServiceRestMetadata(); + serviceRestMetadata.setServiceInterface(service.getName()); + + ServiceRestMetadata resolve = instance.resolve(service, serviceRestMetadata); + + Map unContainPathVariableToServiceMap = resolve.getPathUnContainPathVariableToServiceMap(); + RestMethodMetadata restMethodMetadata = unContainPathVariableToServiceMap.get(PathMatcher.getInvokeCreatePathMatcher("/feign/context/hello", null, null, null, "GET")); + Assertions.assertNotNull(restMethodMetadata); + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/pom.xml b/dubbo-rpc/dubbo-rpc-rest/pom.xml index 44b3214b1f9..e9fdd48a5ba 100644 --- a/dubbo-rpc/dubbo-rpc-rest/pom.xml +++ b/dubbo-rpc/dubbo-rpc-rest/pom.xml @@ -149,5 +149,11 @@ spring-context test + + + org.springframework.cloud + spring-cloud-openfeign-core + test + diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/FeignClientRestProtocolTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/FeignClientRestProtocolTest.java new file mode 100644 index 00000000000..2b44a3e96f8 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/FeignClientRestProtocolTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.rest; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.rpc.Exporter; +import org.apache.dubbo.rpc.Protocol; +import org.apache.dubbo.rpc.ProxyFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.model.ModuleServiceRepository; +import org.apache.dubbo.rpc.model.ProviderModel; +import org.apache.dubbo.rpc.model.ServiceDescriptor; +import org.apache.dubbo.rpc.protocol.rest.mvc.feign.FeignClientController; +import org.apache.dubbo.rpc.protocol.rest.mvc.feign.FeignClientControllerImpl; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +public class FeignClientRestProtocolTest { + private Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("rest"); + private ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); + private final ModuleServiceRepository repository = ApplicationModel.defaultModel().getDefaultModule().getServiceRepository(); + + + @AfterEach + public void tearDown() { + protocol.destroy(); + FrameworkModel.destroyAll(); + } + + + @Test + void testRestProtocol() { + URL url = URL.valueOf("rest://127.0.0.1:" + NetUtils.getAvailablePort() + "/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.mvc.feign.FeignClientController"); + + FeignClientController server = new FeignClientControllerImpl(); + + url = this.registerProvider(url, server, DemoService.class); + + Exporter exporter = protocol.export(proxy.getInvoker(server, FeignClientController.class, url)); + + FeignClientController feignClientController = this.proxy.getProxy(protocol.refer(FeignClientController.class, url)); + + Assertions.assertEquals("hello, feign",feignClientController.hello()); + exporter.unexport(); + } + + + private URL registerProvider(URL url, Object impl, Class interfaceClass) { + ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass); + ProviderModel providerModel = new ProviderModel( + url.getServiceKey(), + impl, + serviceDescriptor, + null, + null); + repository.registerProvider(providerModel); + return url.setServiceModel(providerModel); + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/feign/FeignClientController.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/feign/FeignClientController.java new file mode 100644 index 00000000000..ff4baa1acbb --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/feign/FeignClientController.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.rest.mvc.feign; + + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@FeignClient(path = "/feign") +@RequestMapping("/context") +public interface FeignClientController { + + @RequestMapping(value = "/hello", method = RequestMethod.GET) + String hello(); +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/feign/FeignClientControllerImpl.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/feign/FeignClientControllerImpl.java new file mode 100644 index 00000000000..591694f6077 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/feign/FeignClientControllerImpl.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.rest.mvc.feign; + + + +public class FeignClientControllerImpl implements FeignClientController{ + + @Override + public String hello() { + return "hello, feign"; + } +}