diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc index bd88310a..324741e2 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc @@ -260,8 +260,8 @@ public class NacosConsumerApp { @GetMapping("/echo/app-name") public String echoAppName(){ //使用 LoadBalanceClient 和 RestTemolate 结合的方式来访问 - ServiceInstance serviceInstance = loadBalancerClient.choose("nacos-provider"); - String url = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName); + ServiceInstance delegate = loadBalancerClient.choose("nacos-provider"); + String url = String.format("http://%s:%s/echo/%s",delegate.getHost(),delegate.getPort(),appName); System.out.println("request url:"+url); return restTemplate.getForObject(url,String.class); } diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc index 44f2222f..03f1c3f4 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc/nacos-discovery.adoc @@ -260,8 +260,8 @@ public class NacosConsumerApp { @GetMapping("/echo/app-name") public String echoAppName(){ //Access through the combination of LoadBalanceClient and RestTemolate - ServiceInstance serviceInstance = loadBalancerClient.choose("nacos-provider"); - String url = String.format("http://%s:%s/echo/%s",serviceInstance.getHost(),serviceInstance.getPort(),appName); + ServiceInstance delegate = loadBalancerClient.choose("nacos-provider"); + String url = String.format("http://%s:%s/echo/%s",delegate.getHost(),delegate.getPort(),appName); System.out.println("request url:" +url); return restTemplate.getForObject(url,String.class); } diff --git a/spring-cloud-alibaba-dubbo/pom.xml b/spring-cloud-alibaba-dubbo/pom.xml index 1a869a3e..2de8ada5 100644 --- a/spring-cloud-alibaba-dubbo/pom.xml +++ b/spring-cloud-alibaba-dubbo/pom.xml @@ -59,6 +59,17 @@ true + + org.springframework.cloud + spring-cloud-alibaba-nacos-config + + + + + + + + org.springframework.cloud spring-cloud-starter-openfeign @@ -126,6 +137,11 @@ 9.7.0 + + org.apache.commons + commons-lang3 + + junit @@ -159,11 +175,11 @@ - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - test - + + + + + \ No newline at end of file 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 7af8faeb..90ee3079 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 @@ -17,17 +17,13 @@ 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.AutoConfigureOrder; 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.cloud.alibaba.dubbo.util.MetadataConfigUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.web.bind.annotation.RequestMapping; - -import javax.ws.rs.Path; +import org.springframework.core.Ordered; /** * Spring Boot Auto-Configuration class for Dubbo REST @@ -35,34 +31,22 @@ import javax.ws.rs.Path; * @author Mercy */ @Configuration +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 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); } + + @Bean + public MetadataConfigUtils metadataConfigUtils() { + return new MetadataConfigUtils(); + } } \ 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 index 93fdc338..8cf682f7 100644 --- 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 @@ -16,31 +16,36 @@ */ package org.springframework.cloud.alibaba.dubbo.autoconfigure; +import com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration; import com.alibaba.dubbo.config.ApplicationConfig; import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.spring.ReferenceBean; import feign.Client; import feign.Request; +import feign.RequestInterceptor; import feign.Response; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.beans.BeansException; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.alibaba.dubbo.rest.feign.RestMetadataResolver; +import org.springframework.cloud.alibaba.dubbo.rest.metadata.ServiceRestMetadata; +import org.springframework.cloud.alibaba.dubbo.util.MetadataConfigUtils; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; +import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration; +import org.springframework.cloud.client.serviceregistry.Registration; 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 org.springframework.scheduling.annotation.Scheduled; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import java.io.IOException; @@ -51,17 +56,18 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; /** * The Auto-Configuration class for Dubbo REST Discovery * * @author Mercy */ -@Configuration +@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = true) @AutoConfigureAfter(value = { + DubboAutoConfiguration.class, DubboRestAutoConfiguration.class, DubboRestMetadataRegistrationAutoConfiguration.class}) +@Configuration public class DubboRestDiscoveryAutoConfiguration { @Autowired @@ -81,29 +87,66 @@ public class DubboRestDiscoveryAutoConfiguration { @Autowired private ApplicationConfig applicationConfig; + @Value("${spring.application.name}") + private String applicationName; + @Value("${spring.cloud.nacos.discovery.server-addr}") private String nacosServerAddress; - - private volatile boolean initialized = false; - @Autowired private ListableBeanFactory beanFactory; - @Scheduled(initialDelay = 10 * 1000, fixedRate = 5000) - public void init() { + @Autowired + private MetadataConfigUtils metadataConfigUtils; - if (initialized) { + /** + * Handle on self instance registered. + * + * @param event {@link InstanceRegisteredEvent} + */ + @EventListener(InstanceRegisteredEvent.class) + public void onSelfInstanceRegistered(InstanceRegisteredEvent event) throws Exception { + + Class targetClass = AbstractAutoServiceRegistration.class; + + Object source = event.getSource(); + + if (!targetClass.isInstance(source)) { return; } + // getRegistration() is a protected method + Method method = targetClass.getDeclaredMethod("getRegistration"); + + method.setAccessible(true); + + Registration registration = (Registration) ReflectionUtils.invokeMethod(method, source); + + String serviceRestMetaData = + metadataConfigUtils.getServiceRestMetadata(registration.getServiceId()); + + Set metadata = objectMapper.readValue(serviceRestMetaData, Set.class); + + System.out.println(serviceRestMetaData); + + } + + @Bean + public RequestInterceptor requestInterceptor() { + return template -> { + System.out.println(template); + }; + } + + private void noop() { Map specifications = beanFactory.getBeansOfType(NamedContextFactory.Specification.class); - ServiceAnnotationBeanPostProcessor // 1. Get all service names from Spring beans that was annotated by @FeignClient List serviceNames = new LinkedList<>(); - specifications.forEach((beanName, specification) -> { + specifications.forEach((beanName, specification) -> + + { String serviceName = beanName.substring(0, beanName.indexOf(".")); serviceNames.add(serviceName); @@ -139,11 +182,32 @@ public class DubboRestDiscoveryAutoConfiguration { // } }); - - initialized = true; - } + private ReferenceBean buildReferenceBean(ServiceInstance serviceInstance) { + + ReferenceBean referenceBean = new ReferenceBean(); + Map metadata = serviceInstance.getMetadata(); + // 4. Resolve REST metadata from the @FeignClient instance + String restMetadataJson = metadata.get("restMetadata"); + + try { + Map> restMetadata = objectMapper.readValue(restMetadataJson, Map.class); + + restMetadata.forEach((dubboServiceName, restJsons) -> { + restJsons.stream().map(restMetadataResolver::resolveRequest).forEach(request -> { + referenceBeanCache.put(request.toString(), buildReferenceBean(dubboServiceName)); + }); + }); + + } catch (IOException e) { + throw new RuntimeException(e); + } + + return referenceBean; + } + + private ReferenceBean buildReferenceBean(String dubboServiceName) { ReferenceBean referenceBean = new ReferenceBean(); applicationConfig.setName("service-consumer"); @@ -204,6 +268,7 @@ public class DubboRestDiscoveryAutoConfiguration { return delegate.execute(request, options); } + } @EventListener(ContextRefreshedEvent.class) 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 index 6d48f369..8c225b0e 100644 --- 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 @@ -16,36 +16,26 @@ */ 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 feign.jaxrs2.JAXRS2Contract; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.alibaba.dubbo.rest.feign.RestMetadataResolver; +import org.springframework.cloud.alibaba.dubbo.rest.metadata.ServiceRestMetadata; +import org.springframework.cloud.alibaba.dubbo.util.MetadataConfigUtils; import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent; import org.springframework.cloud.client.serviceregistry.Registration; -import org.springframework.cloud.openfeign.support.SpringMvcContract; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; -import org.springframework.util.ClassUtils; -import javax.annotation.PostConstruct; -import java.util.Collection; -import java.util.Collections; import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; +import java.util.LinkedHashSet; 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, @@ -54,21 +44,19 @@ import static org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegist * * @author Mercy */ -@Configuration +@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) @AutoConfigureAfter(value = { DubboRestAutoConfiguration.class, DubboServiceRegistrationAutoConfiguration.class}) +@Configuration public class DubboRestMetadataRegistrationAutoConfiguration implements BeanClassLoaderAware { /** * 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<>(); + private final Map>> restMetadata = new LinkedHashMap<>(); - /** - * Feign Contracts - */ - private Collection contracts = Collections.emptyList(); + private final Set serviceRestMetadata = new LinkedHashSet<>(); private ClassLoader classLoader; @@ -78,42 +66,16 @@ public class DubboRestMetadataRegistrationAutoConfiguration implements BeanClass @Autowired private RestMetadataResolver restMetadataResolver; - @PostConstruct - public void init() { - contracts = initFeignContracts(); - } - - private Collection initFeignContracts() { - Collection contracts = new LinkedList<>(); - - if (ClassUtils.isPresent("javax.ws.rs.Path", classLoader)) { - contracts.add(new JAXRS2Contract()); - } - - if (ClassUtils.isPresent("org.springframework.web.bind.annotation.RequestMapping", classLoader)) { - contracts.add(new SpringMvcContract()); - } - - return contracts; - } + @Autowired + private MetadataConfigUtils metadataConfigUtils; @EventListener(ServiceBeanExportedEvent.class) - public void recordRestMetadata(ServiceBeanExportedEvent event) { + public void recordRestMetadata(ServiceBeanExportedEvent event) throws JsonProcessingException { 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); - }); +// Map>> metadata = restMetadataResolver.resolve(serviceBean); +// restMetadata.putAll(metadata); + serviceRestMetadata.addAll(restMetadataResolver.resolve(serviceBean)); } /** @@ -126,15 +88,20 @@ public class DubboRestMetadataRegistrationAutoConfiguration implements BeanClass * @param event {@link InstancePreRegisteredEvent} instance */ @EventListener(InstancePreRegisteredEvent.class) - public void registerRestMetadata(InstancePreRegisteredEvent event) throws JsonProcessingException { + public void registerRestMetadata(InstancePreRegisteredEvent event) throws Exception { Registration registration = event.getRegistration(); - Map serviceInstanceMetadata = registration.getMetadata(); - String restMetadataJson = objectMapper.writeValueAsString(restMetadata); - serviceInstanceMetadata.put("restMetadata", restMetadataJson); + + String restMetadataJson = objectMapper.writeValueAsString(serviceRestMetadata); + + metadataConfigUtils.publishServiceRestMetadata(registration.getServiceId(), restMetadataJson); + } + @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } + + } 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 index f9127c70..994d0a98 100644 --- 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 @@ -16,21 +16,39 @@ */ package org.springframework.cloud.alibaba.dubbo.rest.feign; +import com.alibaba.dubbo.common.URL; +import com.alibaba.dubbo.config.spring.ServiceBean; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import feign.Contract; import feign.MethodMetadata; import feign.Request; import feign.RequestTemplate; +import feign.jaxrs2.JAXRS2Contract; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry; +import org.springframework.cloud.alibaba.dubbo.rest.metadata.MethodRestMetadata; +import org.springframework.cloud.alibaba.dubbo.rest.metadata.ServiceRestMetadata; +import org.springframework.cloud.openfeign.support.SpringMvcContract; +import org.springframework.util.ClassUtils; import java.io.IOException; import java.util.Collection; -import java.util.LinkedHashMap; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.Set; /** * The JSON resolver for {@link MethodMetadata} */ -public class RestMetadataResolver { +public class RestMetadataResolver implements BeanClassLoaderAware, SmartInitializingSingleton { private static final String METHOD_PROPERTY_NAME = "method"; private static final String URL_PROPERTY_NAME = "url"; @@ -38,26 +56,108 @@ public class RestMetadataResolver { private final ObjectMapper objectMapper; + /** + * Feign Contracts + */ + private Collection contracts; + + private ClassLoader classLoader; + + @Autowired + private ObjectProvider contractObjectProvider; + public RestMetadataResolver(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } - public String resolve(MethodMetadata methodMetadata) { + @Override + public void afterSingletonsInstantiated() { + + Collection contracts = new LinkedList<>(); + + // Add injected Contract , for example SpringMvcContract Bean under Spring Cloud Open Feign + Contract contract = contractObjectProvider.getIfAvailable(); + if (contract != null) { + contracts.add(contract); + } else { + if (ClassUtils.isPresent("org.springframework.cloud.openfeign.support.SpringMvcContract", classLoader)) { + contracts.add(new SpringMvcContract()); + } + } + + // Add JAXRS2Contract if it's present in Class Path + if (ClassUtils.isPresent("javax.ws.rs.Path", classLoader)) { + contracts.add(new JAXRS2Contract()); + } + + this.contracts = Collections.unmodifiableCollection(contracts); + } + + public Set resolve(ServiceBean serviceBean) { + + Object bean = serviceBean.getRef(); + + Class beanType = bean.getClass(); + + Class interfaceClass = serviceBean.getInterfaceClass(); + + Set serviceRestMetadata = new LinkedHashSet<>(); + + Set methodRestMetadata = new LinkedHashSet<>(); + + contracts.stream() + .map(contract -> contract.parseAndValidatateMetadata(bean.getClass())) + .flatMap(v -> v.stream()) + .forEach(methodMetadata -> { + methodRestMetadata.add(resolveMethodRestMetadata(methodMetadata, beanType, interfaceClass)); + }); + + List urls = serviceBean.getExportedUrls(); + + urls.stream() + .map(SpringCloudRegistry::getServiceName) + .forEach(serviceName -> { + ServiceRestMetadata metadata = new ServiceRestMetadata(); + metadata.setName(serviceName); + metadata.setMeta(methodRestMetadata); + serviceRestMetadata.add(metadata); + }); + + return serviceRestMetadata; + } + + private String toJson(Object object) { 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); + jsonContent = objectMapper.writeValueAsString(object); } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } return jsonContent; } + private String regenerateConfigKey(String configKey, Class beanType, Class interfaceClass) { + return StringUtils.replace(configKey, beanType.getSimpleName(), interfaceClass.getSimpleName()); + } + + protected MethodRestMetadata resolveMethodRestMetadata(MethodMetadata methodMetadata, Class beanType, + Class interfaceClass) { + RequestTemplate requestTemplate = methodMetadata.template(); + Request request = requestTemplate.request(); + + String configKey = methodMetadata.configKey(); + String newConfigKey = regenerateConfigKey(configKey, beanType, interfaceClass); + + MethodRestMetadata methodRestMetadata = new MethodRestMetadata(); + methodRestMetadata.setConfigKey(newConfigKey); + methodRestMetadata.setMethod(request.method()); + methodRestMetadata.setUrl(request.url()); + methodRestMetadata.setHeaders(request.headers()); + methodRestMetadata.setIndexToName(methodMetadata.indexToName()); + + return methodRestMetadata; + } + public Request resolveRequest(String json) { Request request = null; try { @@ -71,4 +171,10 @@ public class RestMetadataResolver { } return request; } + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + } \ No newline at end of file diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/rest/metadata/MethodRestMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/rest/metadata/MethodRestMetadata.java new file mode 100644 index 00000000..ad8d9db9 --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/rest/metadata/MethodRestMetadata.java @@ -0,0 +1,100 @@ +/* + * 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.metadata; + +import java.util.Collection; +import java.util.Map; + +/** + * TODO + */ +public class MethodRestMetadata { + + private String configKey; + + private String method; + + private String url; + + private Map> headers; + + private Map> indexToName; + + public String getConfigKey() { + return configKey; + } + + public void setConfigKey(String configKey) { + this.configKey = configKey; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Map> getHeaders() { + return headers; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } + + public Map> getIndexToName() { + return indexToName; + } + + public void setIndexToName(Map> indexToName) { + this.indexToName = indexToName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MethodRestMetadata that = (MethodRestMetadata) o; + + if (configKey != null ? !configKey.equals(that.configKey) : that.configKey != null) return false; + if (method != null ? !method.equals(that.method) : that.method != null) return false; + if (url != null ? !url.equals(that.url) : that.url != null) return false; + if (headers != null ? !headers.equals(that.headers) : that.headers != null) return false; + return indexToName != null ? indexToName.equals(that.indexToName) : that.indexToName == null; + } + + @Override + public int hashCode() { + int result = configKey != null ? configKey.hashCode() : 0; + result = 31 * result + (method != null ? method.hashCode() : 0); + result = 31 * result + (url != null ? url.hashCode() : 0); + result = 31 * result + (headers != null ? headers.hashCode() : 0); + result = 31 * result + (indexToName != null ? indexToName.hashCode() : 0); + return result; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/rest/metadata/ServiceRestMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/rest/metadata/ServiceRestMetadata.java new file mode 100644 index 00000000..6b93302f --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/rest/metadata/ServiceRestMetadata.java @@ -0,0 +1,63 @@ +/* + * 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.metadata; + +import java.util.Set; + +/** + * TODO + */ +public class ServiceRestMetadata { + + private String name; + + private Set meta; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getMeta() { + return meta; + } + + public void setMeta(Set meta) { + this.meta = meta; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ServiceRestMetadata that = (ServiceRestMetadata) o; + + if (name != null ? !name.equals(that.name) : that.name != null) return false; + return meta != null ? meta.equals(that.meta) : that.meta == null; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + (meta != null ? meta.hashCode() : 0); + return result; + } +} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/util/MetadataConfigUtils.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/util/MetadataConfigUtils.java new file mode 100644 index 00000000..5fe71b3a --- /dev/null +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/util/MetadataConfigUtils.java @@ -0,0 +1,61 @@ +/* + * 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.util; + +import com.alibaba.nacos.api.config.ConfigService; +import com.alibaba.nacos.api.exception.NacosException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.alibaba.nacos.NacosConfigProperties; + +import javax.annotation.PostConstruct; + +import static com.alibaba.nacos.api.common.Constants.DEFAULT_GROUP; + +/** + * TODO + */ +public class MetadataConfigUtils { + + @Autowired + private NacosConfigProperties nacosConfigProperties; + + private ConfigService configService; + + @PostConstruct + public void init() { + this.configService = nacosConfigProperties.configServiceInstance(); + } + + /** + * Get the data Id of service rest metadata + * TODO JavaDoc + */ + private static String getServiceRestMetadataDataId(String serviceName) { + return serviceName + "-rest-metadata.json"; + } + + public void publishServiceRestMetadata(String serviceName, String restMetadataJSON) + throws NacosException { + String dataId = getServiceRestMetadataDataId(serviceName); + configService.publishConfig(dataId, DEFAULT_GROUP, restMetadataJSON); + } + + public String getServiceRestMetadata(String serviceName) throws NacosException { + String dataId = getServiceRestMetadataDataId(serviceName); + return configService.getConfig(dataId, DEFAULT_GROUP, 1000 * 3); + } +} diff --git a/spring-cloud-alibaba-dubbo/src/test/resources/bootstrap.yaml b/spring-cloud-alibaba-dubbo/src/test/resources/bootstrap.yaml index c760349d..c3799608 100644 --- a/spring-cloud-alibaba-dubbo/src/test/resources/bootstrap.yaml +++ b/spring-cloud-alibaba-dubbo/src/test/resources/bootstrap.yaml @@ -5,6 +5,9 @@ spring: nacos: discovery: server-addr: 127.0.0.1:8848 + config: + server-addr: 127.0.0.1:8848 + eureka: client: enabled: false diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/EchoController.java b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/EchoController.java new file mode 100644 index 00000000..1a571b26 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/EchoController.java @@ -0,0 +1,40 @@ +/* + * 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.cloud.examples; + +import com.alibaba.dubbo.config.annotation.Service; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Service(version = "1.0.0") +public class EchoController implements EchoService { + @Override + @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET) + public String echo(@PathVariable String string) { + return "hello Nacos Discovery " + string; + } + + @Override + @RequestMapping(value = "/divide", method = RequestMethod.GET) + public String divide(@RequestParam Integer a, @RequestParam Integer b) { + return String.valueOf(a / b); + } +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/EchoService.java b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/EchoService.java new file mode 100644 index 00000000..b60affbc --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/EchoService.java @@ -0,0 +1,33 @@ +/* + * 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.cloud.examples; + +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * TODO + */ +public interface EchoService { + @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET) + String echo(@PathVariable String string); + + @RequestMapping(value = "/divide", method = RequestMethod.GET) + String divide(@RequestParam Integer a, @RequestParam Integer b); +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java index 843f5cbe..2519096a 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java @@ -1,13 +1,30 @@ package org.springframework.cloud.alibaba.cloud.examples; +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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.context.event.EventListener; + +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URL; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.springframework.util.ClassUtils.isPrimitiveOrWrapper; /** * @author xiaojing @@ -16,20 +33,118 @@ import org.springframework.web.bind.annotation.RestController; @EnableDiscoveryClient public class ProviderApplication { - public static void main(String[] args) { - SpringApplication.run(ProviderApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(ProviderApplication.class, args); + } - @RestController - class EchoController { - @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET) - public String echo(@PathVariable String string) { - return "hello Nacos Discovery " + string; - } + @Autowired + private ConfigurableListableBeanFactory beanFactory; - @RequestMapping(value = "/divide", method = RequestMethod.GET) - public String divide(@RequestParam Integer a, @RequestParam Integer b) { - return String.valueOf(a / b); - } - } + @Autowired + private ObjectMapper objectMapper; + + @EventListener(ServiceBeanExportedEvent.class) + public void onServiceBeanExportedEvent(ServiceBeanExportedEvent event) { + ServiceBean serviceBean = event.getServiceBean(); + } + + @EventListener(InstancePreRegisteredEvent.class) + public void onInstancePreRegisteredEvent(InstancePreRegisteredEvent event) throws JsonProcessingException { + Registration registration = event.getRegistration(); + Map serviceBeansMap = beanFactory.getBeansOfType(ServiceBean.class); + Map metaData = registration.getMetadata(); + String serviceBeansJson = objectMapper.writeValueAsString(services(serviceBeansMap)); + metaData.put("serviceBeans", serviceBeansJson); + } + + public Map> services(Map serviceBeansMap) { + + Map> servicesMetadata = new LinkedHashMap<>(serviceBeansMap.size()); + + for (Map.Entry entry : serviceBeansMap.entrySet()) { + + String serviceBeanName = entry.getKey(); + + ServiceBean serviceBean = entry.getValue(); + + Map serviceBeanMetadata = resolveBeanMetadata(serviceBean); + + Object service = resolveServiceBean(serviceBeanName, serviceBean); + + if (service != null) { + // Add Service implementation class + serviceBeanMetadata.put("serviceClass", service.getClass().getName()); + } + + servicesMetadata.put(serviceBeanName, serviceBeanMetadata); + + } + + return servicesMetadata; + + } + + protected Map resolveBeanMetadata(final Object bean) { + + final Map beanMetadata = new LinkedHashMap<>(); + + try { + + BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); + PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); + + for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { + + Method readMethod = propertyDescriptor.getReadMethod(); + + if (readMethod != null && isSimpleType(propertyDescriptor.getPropertyType())) { + + String name = Introspector.decapitalize(propertyDescriptor.getName()); + Object value = readMethod.invoke(bean); + + if (value != null) { + beanMetadata.put(name, value); + } + } + + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + + return beanMetadata; + + } + + private Object resolveServiceBean(String serviceBeanName, ServiceBean serviceBean) { + + int index = serviceBeanName.indexOf("#"); + + if (index > -1) { + + Class interfaceClass = serviceBean.getInterfaceClass(); + + String serviceName = serviceBeanName.substring(index + 1); + + if (beanFactory.containsBean(serviceName)) { + return beanFactory.getBean(serviceName, interfaceClass); + } + + } + + return null; + + } + + private static boolean isSimpleType(Class type) { + return isPrimitiveOrWrapper(type) + || type == String.class + || type == BigDecimal.class + || type == BigInteger.class + || type == Date.class + || type == URL.class + || type == Class.class + ; + } } diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/resources/application.properties index 66100d79..81cea717 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/resources/application.properties @@ -1,4 +1,12 @@ server.port=18082 spring.application.name=service-provider spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 -management.endpoints.web.exposure.include=* \ No newline at end of file +management.endpoints.web.exposure.include=* + + +dubbo.scan.base-packages=org.springframework.cloud.alibaba.cloud.examples + +dubbo.registry.address=nacos://127.0.0.1:8848 + +dubbo.protocol.name=dubbo +dubbo.protocol.port=-1 \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/pom.xml index ac62d9a3..5d37f883 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/pom.xml +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/pom.xml @@ -15,11 +15,47 @@ pom Example demonstrating how to use nacos discovery + + 2.6.5 + nacos-discovery-consumer-example nacos-discovery-provider-example + + + + + com.alibaba.boot + dubbo-spring-boot-starter + + + + com.alibaba + dubbo + ${dubbo.version} + + + + + io.netty + netty-all + + + + + com.alibaba + dubbo-registry-nacos + + + + + com.alibaba.nacos + nacos-client + + + diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/endpoint/NacosDiscoveryEndpointAutoConfiguration.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/endpoint/NacosDiscoveryEndpointAutoConfiguration.java index 2356ad0a..0bbee4c2 100644 --- a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/endpoint/NacosDiscoveryEndpointAutoConfiguration.java +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/endpoint/NacosDiscoveryEndpointAutoConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.condition.Conditi import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.alibaba.nacos.ConditionalOnNacosDiscoveryEnabled; import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -29,6 +30,7 @@ import org.springframework.context.annotation.Configuration; */ @Configuration @ConditionalOnClass(Endpoint.class) +@ConditionalOnNacosDiscoveryEnabled public class NacosDiscoveryEndpointAutoConfiguration { @Bean