diff --git a/spring-cloud-alibaba-dubbo/pom.xml b/spring-cloud-alibaba-dubbo/pom.xml index 1a7d27ac..1a869a3e 100644 --- a/spring-cloud-alibaba-dubbo/pom.xml +++ b/spring-cloud-alibaba-dubbo/pom.xml @@ -82,31 +82,42 @@ netty-all - - - - - + + + org.jboss.resteasy + resteasy-jaxrs + - - - - + + org.jboss.resteasy + resteasy-client + - - - - + + org.jboss.resteasy + resteasy-netty4 + - - - - + + javax.validation + validation-api + - - - - + + org.jboss.resteasy + resteasy-jackson-provider + + + + org.jboss.resteasy + resteasy-jaxb-provider + + + + javax.servlet + javax.servlet-api + provided + diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestAutoConfiguration.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestAutoConfiguration.java index 7108cbbf..58528da7 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestAutoConfiguration.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestAutoConfiguration.java @@ -16,7 +16,18 @@ */ package org.springframework.cloud.alibaba.dubbo.autoconfigure; +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.Contract; +import feign.jaxrs2.JAXRS2Contract; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.alibaba.dubbo.rest.feign.RestMetadataResolver; +import org.springframework.cloud.openfeign.support.SpringMvcContract; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.ws.rs.Path; /** * Spring Boot Auto-Configuration class for Dubbo REST @@ -26,5 +37,32 @@ import org.springframework.context.annotation.Configuration; @Configuration public class DubboRestAutoConfiguration { + /** + * A Feign Contract bean for JAX-RS if available + */ + @ConditionalOnClass(Path.class) + @Bean + public Contract jaxrs2Contract() { + return new JAXRS2Contract(); + } -} + @Bean + @ConditionalOnMissingBean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + /** + * A Feign Contract bean for Spring MVC if available + */ + @ConditionalOnClass(RequestMapping.class) + @Bean + public Contract springMvcContract() { + return new SpringMvcContract(); + } + + @Bean + public RestMetadataResolver metadataJsonResolver(ObjectMapper objectMapper) { + return new RestMetadataResolver(objectMapper); + } +} \ No newline at end of file diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestDiscoveryAutoConfiguration.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestDiscoveryAutoConfiguration.java new file mode 100644 index 00000000..ddb2fd2b --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestDiscoveryAutoConfiguration.java @@ -0,0 +1,72 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.autoconfigure; + +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.context.named.NamedContextFactory; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; + +import java.util.Map; + +/** + * The Auto-Configuration class for Dubbo REST Discovery + * + * @author Mercy + */ +@Configuration +@AutoConfigureAfter(value = { + DubboRestAutoConfiguration.class, + DubboRestMetadataRegistrationAutoConfiguration.class}) +public class DubboRestDiscoveryAutoConfiguration { + + + @Autowired + private DiscoveryClient discoveryClient; + + // 1. Get all service names from Spring beans that was annotated by @FeignClient + // 2. Get all service instances by echo specified service name + // 3. Get Rest metadata from service instance + // 4. Resolve REST metadata from the @FeignClient instance + + @Bean + public SmartInitializingSingleton onBeansInitialized(ListableBeanFactory beanFactory) { + return () -> { + Map feignClientBeans = beanFactory.getBeansWithAnnotation(FeignClient.class); + feignClientBeans.forEach((beanName, bean) -> { + if (bean instanceof NamedContextFactory.Specification) { + NamedContextFactory.Specification specification = (NamedContextFactory.Specification) bean; + String serviceName = specification.getName(); + } + }); + }; + } + + @EventListener(ContextRefreshedEvent.class) + public void onContextRefreshed(ContextRefreshedEvent event) { + + } + + +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestMetadataRegistrationAutoConfiguration.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestMetadataRegistrationAutoConfiguration.java new file mode 100644 index 00000000..ea9af9a3 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboRestMetadataRegistrationAutoConfiguration.java @@ -0,0 +1,108 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.autoconfigure; + +import com.alibaba.dubbo.common.URL; +import com.alibaba.dubbo.config.spring.ServiceBean; +import com.alibaba.dubbo.config.spring.context.event.ServiceBeanExportedEvent; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.Contract; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.cloud.alibaba.dubbo.rest.feign.RestMetadataResolver; +import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry.getServiceName; + +/** + * The Auto-Configuration class for Dubbo REST metadata registration, + * REST metadata that is a part of {@link Registration#getMetadata() Spring Cloud service instances' metadata} + * will be registered Spring Cloud registry. + * + * @author Mercy + */ +@Configuration +@AutoConfigureAfter(value = { + DubboRestAutoConfiguration.class, DubboServiceRegistrationAutoConfiguration.class}) +public class DubboRestMetadataRegistrationAutoConfiguration { + + /** + * A Map to store REST metadata temporary, its' key is the special service name for a Dubbo service, + * the value is a JSON content of JAX-RS or Spring MVC REST metadata from the annotated methods. + */ + private final Map> restMetadata = new LinkedHashMap<>(); + + /** + * Autowire Feign Contract Beans + */ + @Autowired(required = false) + private Collection contracts = Collections.emptyList(); + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private RestMetadataResolver restMetadataResolver; + + @EventListener(ServiceBeanExportedEvent.class) + public void recordRestMetadata(ServiceBeanExportedEvent event) { + ServiceBean serviceBean = event.getServiceBean(); + List urls = serviceBean.getExportedUrls(); + Object bean = serviceBean.getRef(); + + Set metadata = contracts.stream() + .map(contract -> contract.parseAndValidatateMetadata(bean.getClass())) + .flatMap(v -> v.stream()) + .map(restMetadataResolver::resolve) + .collect(Collectors.toSet()); + + urls.forEach(url -> { + String serviceName = getServiceName(url); + restMetadata.put(serviceName, metadata); + }); + } + + /** + * Pre-handle Spring Cloud application service registered: + *

+ * Put restMetadata with the JSON format into + * {@link Registration#getMetadata() service instances' metadata} + *

+ * + * @param event {@link InstancePreRegisteredEvent} instance + */ + @EventListener(InstancePreRegisteredEvent.class) + public void registerRestMetadata(InstancePreRegisteredEvent event) throws JsonProcessingException { + Registration registration = event.getRegistration(); + Map serviceInstanceMetadata = registration.getMetadata(); + String restMetadataJson = objectMapper.writeValueAsString(restMetadata); + serviceInstanceMetadata.put("restMetadata", restMetadataJson); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/registry/SpringCloudRegistry.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/registry/SpringCloudRegistry.java index aa93c7e5..f9d71340 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/registry/SpringCloudRegistry.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/registry/SpringCloudRegistry.java @@ -160,12 +160,12 @@ public class SpringCloudRegistry extends FailbackRegistry { return serviceInstance; } - private String getServiceName(URL url) { + public static String getServiceName(URL url) { String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); return getServiceName(url, category); } - private String getServiceName(URL url, String category) { + private static String getServiceName(URL url, String category) { StringBuilder serviceNameBuilder = new StringBuilder(category); appendIfPresent(serviceNameBuilder, url, Constants.INTERFACE_KEY); appendIfPresent(serviceNameBuilder, url, Constants.VERSION_KEY); @@ -173,7 +173,7 @@ public class SpringCloudRegistry extends FailbackRegistry { return serviceNameBuilder.toString(); } - private void appendIfPresent(StringBuilder target, URL url, String parameterName) { + private static void appendIfPresent(StringBuilder target, URL url, String parameterName) { String parameterValue = url.getParameter(parameterName); if (StringUtils.hasText(parameterValue)) { target.append(SERVICE_NAME_SEPARATOR).append(parameterValue); diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/rest/feign/RestMetadataResolver.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/rest/feign/RestMetadataResolver.java new file mode 100644 index 00000000..f9127c70 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/rest/feign/RestMetadataResolver.java @@ -0,0 +1,74 @@ +/* + * 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.springframework.cloud.alibaba.dubbo.rest.feign; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.MethodMetadata; +import feign.Request; +import feign.RequestTemplate; + +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * The JSON resolver for {@link MethodMetadata} + */ +public class RestMetadataResolver { + + private static final String METHOD_PROPERTY_NAME = "method"; + private static final String URL_PROPERTY_NAME = "url"; + private static final String HEADERS_PROPERTY_NAME = "headers"; + + private final ObjectMapper objectMapper; + + public RestMetadataResolver(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public String resolve(MethodMetadata methodMetadata) { + String jsonContent = null; + Map metadata = new LinkedHashMap<>(); + RequestTemplate requestTemplate = methodMetadata.template(); + Request request = requestTemplate.request(); + metadata.put(METHOD_PROPERTY_NAME, request.method()); + metadata.put(URL_PROPERTY_NAME, request.url()); + metadata.put(HEADERS_PROPERTY_NAME, request.headers()); + try { + jsonContent = objectMapper.writeValueAsString(metadata); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException(e); + } + return jsonContent; + } + + public Request resolveRequest(String json) { + Request request = null; + try { + Map data = objectMapper.readValue(json, Map.class); + String method = (String) data.get(METHOD_PROPERTY_NAME); + String url = (String) data.get(URL_PROPERTY_NAME); + Map> headers = (Map) data.get(HEADERS_PROPERTY_NAME); + request = Request.create(method, url, headers, null, null); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + return request; + } +} \ No newline at end of file diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java index 72bd95cf..8b32a4b9 100644 --- a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java @@ -16,14 +16,23 @@ */ package org.springframework.cloud.alibaba.dubbo.bootstrap; +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.annotation.Reference; +import com.alibaba.dubbo.config.spring.ReferenceBean; +import com.alibaba.dubbo.config.spring.ServiceBean; +import com.alibaba.dubbo.config.spring.context.event.ServiceBeanExportedEvent; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.alibaba.dubbo.service.EchoService; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.Bean; +import org.springframework.context.event.EventListener; + +import java.util.List; /** * Dubbo Spring Cloud Bootstrap @@ -35,6 +44,9 @@ public class DubboSpringCloudBootstrap { @Reference(version = "1.0.0") private EchoService echoService; + @Reference(version = "1.0.0") + private EchoService echoServiceForRest; + @Bean public ApplicationRunner applicationRunner() { return arguments -> { @@ -42,6 +54,28 @@ public class DubboSpringCloudBootstrap { }; } + + @Autowired + private ApplicationConfig applicationConfig; + + @Autowired + private List registries; + + @EventListener(ServiceBeanExportedEvent.class) + public void onServiceBeanExportedEvent(ServiceBeanExportedEvent event) { + ServiceBean serviceBean = event.getServiceBean(); + ReferenceBean referenceBean = new ReferenceBean(); + referenceBean.setApplication(applicationConfig); + referenceBean.setRegistries(registries); + referenceBean.setInterface(serviceBean.getInterfaceClass()); + referenceBean.setInterface(serviceBean.getInterface()); + referenceBean.setVersion(serviceBean.getVersion()); + referenceBean.setGroup(serviceBean.getGroup()); + Object object = referenceBean.get(); + System.out.println(object); + } + + public static void main(String[] args) { new SpringApplicationBuilder(DubboSpringCloudBootstrap.class) .run(args); diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/DefaultEchoService.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/DefaultEchoService.java index f0fc0ac0..abc2d641 100644 --- a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/DefaultEchoService.java +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/DefaultEchoService.java @@ -17,23 +17,48 @@ package org.springframework.cloud.alibaba.dubbo.service; import com.alibaba.dubbo.config.annotation.Service; +import com.alibaba.dubbo.rpc.RpcContext; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; + /** * Default {@link EchoService} * * @author Mercy */ -@Service(version = "1.0.0") +@Service(version = "1.0.0", protocol = {"dubbo", "rest"}) @RestController +@Path("/") public class DefaultEchoService implements EchoService { @Override - @GetMapping("/echo") - public String echo(@RequestParam String message) { - return "[echo] : " + message; + @GetMapping(value = "/echo", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + @Path("/echo") + @GET + @Consumes("application/json") + @Produces("application/json;charset=UTF-8") + public String echo(@RequestParam @QueryParam("message") String message) { + return RpcContext.getContext().getUrl() + " [echo] : " + message; + } + + @Override + @PostMapping("/plus") + @Path("/plus") + @POST + public String plus(@RequestParam @QueryParam("a") int a, @RequestParam @QueryParam("b") int b) { + return null; } } diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/EchoService.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/EchoService.java index 7bc249f4..10ace8ec 100644 --- a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/EchoService.java +++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/service/EchoService.java @@ -24,4 +24,7 @@ package org.springframework.cloud.alibaba.dubbo.service; public interface EchoService { String echo(String message); + + String plus(int a, int b); + } diff --git a/spring-cloud-alibaba-dubbo/src/test/resources/application.yaml b/spring-cloud-alibaba-dubbo/src/test/resources/application.yaml index 65249ca9..113a3aa5 100644 --- a/spring-cloud-alibaba-dubbo/src/test/resources/application.yaml +++ b/spring-cloud-alibaba-dubbo/src/test/resources/application.yaml @@ -1,9 +1,13 @@ - - dubbo: scan: base-packages: org.springframework.cloud.alibaba.dubbo.service - protocol: - port: 12345 + protocols: + dubbo: + name: dubbo + port: 12345 + rest: + name: rest + port: 9090 + server: netty registry: address: spring-cloud://dummy \ No newline at end of file