Merge pull request #562 from mercyblitz/master

[Refactor] add Introspective DubboMetadataService for Dubbo Spring Cloud
pull/579/head
Mercy Ma 6 years ago committed by GitHub
commit d535c6f74f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -32,7 +32,7 @@ import org.springframework.cloud.alibaba.dubbo.metadata.resolver.MetadataResolve
import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceFactory; import org.springframework.cloud.alibaba.dubbo.service.DubboGenericServiceFactory;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceExporter; import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceExporter;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceProxy; import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceProxy;
import org.springframework.cloud.alibaba.dubbo.service.PublishingDubboMetadataService; import org.springframework.cloud.alibaba.dubbo.service.IntrospectiveDubboMetadataService;
import org.springframework.cloud.alibaba.dubbo.util.JSONUtils; import org.springframework.cloud.alibaba.dubbo.util.JSONUtils;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -50,13 +50,13 @@ import java.util.function.Supplier;
*/ */
@Configuration @Configuration
@Import({DubboServiceMetadataRepository.class, @Import({DubboServiceMetadataRepository.class,
PublishingDubboMetadataService.class, IntrospectiveDubboMetadataService.class,
DubboMetadataServiceExporter.class, DubboMetadataServiceExporter.class,
JSONUtils.class}) JSONUtils.class})
public class DubboMetadataAutoConfiguration { public class DubboMetadataAutoConfiguration {
@Autowired @Autowired
private PublishingDubboMetadataService dubboMetadataService; private ObjectProvider<DubboServiceMetadataRepository> dubboServiceMetadataRepository;
@Autowired @Autowired
private MetadataResolver metadataResolver; private MetadataResolver metadataResolver;
@ -87,7 +87,6 @@ public class DubboMetadataAutoConfiguration {
public void onServiceBeanExported(ServiceBeanExportedEvent event) { public void onServiceBeanExported(ServiceBeanExportedEvent event) {
ServiceBean serviceBean = event.getServiceBean(); ServiceBean serviceBean = event.getServiceBean();
publishServiceRestMetadata(serviceBean); publishServiceRestMetadata(serviceBean);
exportDubboMetadataConfigService();
} }
@EventListener(ApplicationFailedEvent.class) @EventListener(ApplicationFailedEvent.class)
@ -97,15 +96,11 @@ public class DubboMetadataAutoConfiguration {
@EventListener(ContextClosedEvent.class) @EventListener(ContextClosedEvent.class)
public void onContextClosed() { public void onContextClosed() {
dubboMetadataConfigServiceExporter.unexport(); unExportDubboMetadataConfigService();
} }
private void publishServiceRestMetadata(ServiceBean serviceBean) { private void publishServiceRestMetadata(ServiceBean serviceBean) {
dubboMetadataService.publishServiceRestMetadata(metadataResolver.resolveServiceRestMetadata(serviceBean)); dubboServiceMetadataRepository.getIfAvailable().publishServiceRestMetadata(metadataResolver.resolveServiceRestMetadata(serviceBean));
}
private void exportDubboMetadataConfigService() {
dubboMetadataConfigServiceExporter.export();
} }
private void unExportDubboMetadataConfigService() { private void unExportDubboMetadataConfigService() {

@ -16,7 +16,6 @@
*/ */
package org.springframework.cloud.alibaba.dubbo.autoconfigure; package org.springframework.cloud.alibaba.dubbo.autoconfigure;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.spring.ServiceBean; import org.apache.dubbo.config.spring.ServiceBean;
import com.ecwid.consul.v1.agent.model.NewService; import com.ecwid.consul.v1.agent.model.NewService;
@ -35,7 +34,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository; import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.registry.DubboServiceRegistrationEventPublishingAspect; import org.springframework.cloud.alibaba.dubbo.registry.DubboServiceRegistrationEventPublishingAspect;
import org.springframework.cloud.alibaba.dubbo.registry.event.ServiceInstancePreRegisteredEvent; import org.springframework.cloud.alibaba.dubbo.registry.event.ServiceInstancePreRegisteredEvent;
import org.springframework.cloud.alibaba.dubbo.util.JSONUtils;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.consul.serviceregistry.ConsulRegistration; import org.springframework.cloud.consul.serviceregistry.ConsulRegistration;
@ -46,17 +44,13 @@ import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import static org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.CONSUL_AUTO_CONFIGURATION_CLASS_NAME; import static org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.CONSUL_AUTO_CONFIGURATION_CLASS_NAME;
import static org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.EUREKA_AUTO_CONFIGURATION_CLASS_NAME; import static org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.EUREKA_AUTO_CONFIGURATION_CLASS_NAME;
import static org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository.DUBBO_URLS_METADATA_PROPERTY_NAME;
import static org.springframework.util.ObjectUtils.isEmpty; import static org.springframework.util.ObjectUtils.isEmpty;
/** /**
@ -93,13 +87,10 @@ public class DubboServiceRegistrationAutoConfiguration {
@Autowired @Autowired
private DubboServiceMetadataRepository dubboServiceMetadataRepository; private DubboServiceMetadataRepository dubboServiceMetadataRepository;
@Autowired
private JSONUtils jsonUtils;
@EventListener(ServiceInstancePreRegisteredEvent.class) @EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) { public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) {
Registration registration = event.getSource(); Registration registration = event.getSource();
attachURLsIntoMetadata(registration); attachDubboMetadataServiceMetadata(registration);
} }
@Configuration @Configuration
@ -115,7 +106,7 @@ public class DubboServiceRegistrationAutoConfiguration {
Registration registration = event.getSource(); Registration registration = event.getSource();
EurekaRegistration eurekaRegistration = EurekaRegistration.class.cast(registration); EurekaRegistration eurekaRegistration = EurekaRegistration.class.cast(registration);
InstanceInfo instanceInfo = eurekaRegistration.getApplicationInfoManager().getInfo(); InstanceInfo instanceInfo = eurekaRegistration.getApplicationInfoManager().getInfo();
attachURLsIntoMetadata(instanceInfo.getMetadata()); attachDubboMetadataServiceMetadata(instanceInfo.getMetadata());
} }
/** /**
@ -155,39 +146,30 @@ public class DubboServiceRegistrationAutoConfiguration {
private void attachURLsIntoMetadata(ConsulRegistration consulRegistration) { private void attachURLsIntoMetadata(ConsulRegistration consulRegistration) {
NewService newService = consulRegistration.getService(); NewService newService = consulRegistration.getService();
String dubboURLsJson = getDubboURLsJSON(); Map<String, String> serviceMetadata = dubboServiceMetadataRepository.getDubboMetadataServiceMetadata();
if (StringUtils.hasText(dubboURLsJson)) { if (!isEmpty(serviceMetadata)) {
List<String> tags = newService.getTags(); List<String> tags = newService.getTags();
tags.add(DUBBO_URLS_METADATA_PROPERTY_NAME + "=" + dubboURLsJson); for (Map.Entry<String, String> entry : serviceMetadata.entrySet()) {
tags.add(entry.getKey() + "=" + entry.getValue());
}
} }
} }
} }
private void attachURLsIntoMetadata(Registration registration) { private void attachDubboMetadataServiceMetadata(Registration registration) {
if (registration == null) { if (registration == null) {
return; return;
} }
synchronized (registration) { synchronized (registration) {
Map<String, String> metadata = registration.getMetadata(); Map<String, String> metadata = registration.getMetadata();
attachURLsIntoMetadata(metadata); attachDubboMetadataServiceMetadata(metadata);
}
}
private void attachURLsIntoMetadata(Map<String, String> metadata) {
String dubboURLsJson = getDubboURLsJSON();
if (StringUtils.hasText(dubboURLsJson)) {
metadata.put(DUBBO_URLS_METADATA_PROPERTY_NAME, dubboURLsJson);
} }
} }
private String getDubboURLsJSON() { private void attachDubboMetadataServiceMetadata(Map<String, String> metadata) {
Collection<URL> urls = dubboServiceMetadataRepository.getRegisteredUrls(); Map<String, String> serviceMetadata = dubboServiceMetadataRepository.getDubboMetadataServiceMetadata();
if (CollectionUtils.isEmpty(urls)) { if (!isEmpty(serviceMetadata)) {
if (logger.isDebugEnabled()) { metadata.putAll(serviceMetadata);
logger.debug("There is no registered URL to attach into metadata.");
}
return null;
} }
return jsonUtils.toJSON(urls.stream().map(URL::toFullString).collect(Collectors.toList()));
} }
} }

@ -41,7 +41,7 @@ import org.springframework.cloud.zookeeper.serviceregistry.ServiceInstanceRegist
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import java.util.Collection; import java.util.List;
import static org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.CONSUL_AUTO_CONFIGURATION_CLASS_NAME; import static org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.CONSUL_AUTO_CONFIGURATION_CLASS_NAME;
import static org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.ZOOKEEPER_AUTO_CONFIGURATION_CLASS_NAME; import static org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.ZOOKEEPER_AUTO_CONFIGURATION_CLASS_NAME;
@ -97,20 +97,21 @@ public class DubboServiceRegistrationNonWebApplicationAutoConfiguration {
*/ */
private void setServerPort() { private void setServerPort() {
if (serverPort == null) { if (serverPort == null) {
Collection<URL> urls = repository.getRegisteredUrls(); for (List<URL> urls : repository.getAllExportedUrls().values()) {
urls.stream() urls.stream()
.filter(url -> REST_PROTOCOL.equalsIgnoreCase(url.getProtocol())) .filter(url -> REST_PROTOCOL.equalsIgnoreCase(url.getProtocol()))
.findFirst() .findFirst()
.ifPresent(url -> { .ifPresent(url -> {
serverPort = url.getPort();
});
// If REST protocol is not present, use any applied port.
if (serverPort == null) {
urls.stream()
.findAny().ifPresent(url -> {
serverPort = url.getPort(); serverPort = url.getPort();
}); });
}
// If REST protocol is not present, use any applied port.
if (serverPort == null) {
urls.stream()
.findAny().ifPresent(url -> {
serverPort = url.getPort();
});
} }
} }
} }

@ -25,6 +25,7 @@ import java.util.Iterator;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.apache.dubbo.common.Constants.DEFAULT_PROTOCOL; import static org.apache.dubbo.common.Constants.DEFAULT_PROTOCOL;
import static org.springframework.util.CollectionUtils.isEmpty;
/** /**
* Dubbo's {@link ProtocolConfig} {@link Supplier} * Dubbo's {@link ProtocolConfig} {@link Supplier}
@ -43,23 +44,26 @@ public class DubboProtocolConfigSupplier implements Supplier<ProtocolConfig> {
public ProtocolConfig get() { public ProtocolConfig get() {
ProtocolConfig protocolConfig = null; ProtocolConfig protocolConfig = null;
Collection<ProtocolConfig> protocols = this.protocols.getIfAvailable(); Collection<ProtocolConfig> protocols = this.protocols.getIfAvailable();
for (ProtocolConfig protocol : protocols) {
String protocolName = protocol.getName(); if (!isEmpty(protocols)) {
if (DEFAULT_PROTOCOL.equals(protocolName)) { for (ProtocolConfig protocol : protocols) {
protocolConfig = protocol; String protocolName = protocol.getName();
break; if (DEFAULT_PROTOCOL.equals(protocolName)) {
protocolConfig = protocol;
break;
}
} }
}
if (protocolConfig == null) { // If The ProtocolConfig bean named "dubbo" is absent, take first one of them if (protocolConfig == null) { // If The ProtocolConfig bean named "dubbo" is absent, take first one of them
Iterator<ProtocolConfig> iterator = protocols.iterator(); Iterator<ProtocolConfig> iterator = protocols.iterator();
protocolConfig = iterator.hasNext() ? iterator.next() : null; protocolConfig = iterator.hasNext() ? iterator.next() : null;
}
} }
if (protocolConfig == null) { if (protocolConfig == null) {
protocolConfig = new ProtocolConfig(); protocolConfig = new ProtocolConfig();
protocolConfig.setName(DEFAULT_PROTOCOL); protocolConfig.setName(DEFAULT_PROTOCOL);
protocolConfig.setPort(20880); protocolConfig.setPort(-1);
} }
return protocolConfig; return protocolConfig;

@ -30,6 +30,7 @@ import org.springframework.cloud.alibaba.dubbo.metadata.DubboRestServiceMetadata
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataService; import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataService;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceExporter;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceProxy; import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceProxy;
import org.springframework.cloud.alibaba.dubbo.util.JSONUtils; import org.springframework.cloud.alibaba.dubbo.util.JSONUtils;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
@ -37,12 +38,12 @@ import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpRequest; import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -51,10 +52,19 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.lang.String.format;
import static java.lang.String.valueOf;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
import static org.apache.dubbo.common.Constants.APPLICATION_KEY; import static org.apache.dubbo.common.Constants.APPLICATION_KEY;
import static org.apache.dubbo.common.Constants.VERSION_KEY;
import static org.springframework.cloud.alibaba.dubbo.env.DubboCloudProperties.ALL_DUBBO_SERVICES; import static org.springframework.cloud.alibaba.dubbo.env.DubboCloudProperties.ALL_DUBBO_SERVICES;
import static org.springframework.cloud.alibaba.dubbo.http.DefaultHttpRequest.builder; import static org.springframework.cloud.alibaba.dubbo.http.DefaultHttpRequest.builder;
import static org.springframework.util.CollectionUtils.isEmpty; import static org.springframework.util.CollectionUtils.isEmpty;
import static org.springframework.util.StringUtils.hasText;
/** /**
* Dubbo Service Metadata {@link Repository} * Dubbo Service Metadata {@link Repository}
@ -65,17 +75,56 @@ import static org.springframework.util.CollectionUtils.isEmpty;
public class DubboServiceMetadataRepository { public class DubboServiceMetadataRepository {
/** /**
* The property name of Dubbo {@link URL URLs} metadata * The prefix of {@link DubboMetadataService} : "dubbo.metadata-service."
*/ */
public static final String DUBBO_URLS_METADATA_PROPERTY_NAME = "dubbo.urls"; public static final String DUBBO_METADATA_SERVICE_PREFIX = "dubbo.metadata-service.";
/**
* The {@link URL URLs} property name of {@link DubboMetadataService} : "dubbo.metadata-service.urls"
*/
public static final String DUBBO_METADATA_SERVICE_URLS_PROPERTY_NAME = DUBBO_METADATA_SERVICE_PREFIX + "urls";
/**
* The {@link String#format(String, Object...) pattern} of dubbo protocols port
*/
public static final String DUBBO_PROTOCOLS_PORT_PROPERTY_NAME_PATTERN = "dubbo.protocols.%s.port";
private final Logger logger = LoggerFactory.getLogger(getClass()); private final Logger logger = LoggerFactory.getLogger(getClass());
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();
private final Set<URL> registeredURLs = new LinkedHashSet<>(); // =================================== Registration =================================== //
/**
* All exported {@link URL urls} {@link Map} whose key is the return value of {@link URL#getServiceKey()} method
* and value is the {@link List} of {@link URL URLs}
*/
private final MultiValueMap<String, URL> allExportedURLs = new LinkedMultiValueMap<>();
// ==================================================================================== //
// =================================== Subscription =================================== //
private Set<String> subscribedServices;
/**
* The subscribed {@link URL urls} {@link Map} of {@link DubboMetadataService},
* whose key is the return value of {@link URL#getServiceKey()} method and value is the {@link List} of
* {@link URL URLs}
*/
private final MultiValueMap<String, URL> subscribedDubboMetadataServiceURLs = new LinkedMultiValueMap<>();
// ==================================================================================== //
private final Map<String, String> dubboServiceKeysRepository = new HashMap<>();
// =================================== REST Metadata ================================== //
/**
* 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 Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>();
/** /**
* Key is application name * Key is application name
@ -83,7 +132,10 @@ public class DubboServiceMetadataRepository {
*/ */
private Map<String, Map<RequestMetadataMatcher, DubboRestServiceMetadata>> dubboRestServiceMetadataRepository = newHashMap(); private Map<String, Map<RequestMetadataMatcher, DubboRestServiceMetadata>> dubboRestServiceMetadataRepository = newHashMap();
private Set<String> subscribedServices; // ==================================================================================== //
// =================================== Dependencies =================================== //
@Autowired @Autowired
private DubboCloudProperties dubboCloudProperties; private DubboCloudProperties dubboCloudProperties;
@ -100,67 +152,179 @@ public class DubboServiceMetadataRepository {
@Value("${spring.application.name}") @Value("${spring.application.name}")
private String currentApplicationName; private String currentApplicationName;
@Autowired
private DubboMetadataServiceExporter dubboMetadataServiceExporter;
// ==================================================================================== //
@PostConstruct @PostConstruct
public void init() { public void init() {
// Keep the order in following invocations
initSubscribedServices(); initSubscribedServices();
initDubboServiceKeysRepository(); initSubscribedDubboMetadataServices();
retainAvailableSubscribedServices();
initDubboRestServiceMetadataRepository(); initDubboRestServiceMetadataRepository();
} }
/** /**
* The specified service is subscribe or not * Get the metadata {@link Map} of {@link DubboMetadataService}
* *
* @param serviceName the service name * @return non-null read-only {@link Map}
* @return
*/ */
public boolean isSubscribedService(String serviceName) { public Map<String, String> getDubboMetadataServiceMetadata() {
return subscribedServices.contains(serviceName);
List<URL> dubboMetadataServiceURLs = dubboMetadataServiceExporter.export();
// remove the exported URLs of DubboMetadataService
removeDubboMetadataServiceURLs(dubboMetadataServiceURLs);
Map<String, String> metadata = newHashMap();
addDubboMetadataServiceURLsMetadata(metadata, dubboMetadataServiceURLs);
addDubboProtocolsPortMetadata(metadata);
return Collections.unmodifiableMap(metadata);
}
private void removeDubboMetadataServiceURLs(List<URL> dubboMetadataServiceURLs) {
dubboMetadataServiceURLs.forEach(this::unexportURL);
}
private void addDubboMetadataServiceURLsMetadata(Map<String, String> metadata, List<URL> dubboMetadataServiceURLs) {
String dubboMetadataServiceURLsJSON = jsonUtils.toJSON(dubboMetadataServiceURLs);
metadata.put(DUBBO_METADATA_SERVICE_URLS_PROPERTY_NAME, dubboMetadataServiceURLsJSON);
}
private void addDubboProtocolsPortMetadata(Map<String, String> metadata) {
allExportedURLs.values()
.stream()
.flatMap(v -> v.stream())
.forEach(url -> {
String protocol = url.getProtocol();
String propertyName = getDubboProtocolPropertyName(protocol);
String propertyValue = valueOf(url.getPort());
metadata.put(propertyName, propertyValue);
});
} }
/** /**
* Get the service name by the {@link URL#getServiceKey() service key} * Get the property name of Dubbo Protocol
* *
* @param url {@link URL} * @param protocol Dubbo Protocol
* @return the service name if found * @return non-null
*/ */
public String getServiceName(URL url) { public String getDubboProtocolPropertyName(String protocol) {
return getServiceName(url.getServiceKey()); return format(DUBBO_PROTOCOLS_PORT_PROPERTY_NAME_PATTERN, protocol);
} }
/** /**
* Get the service name by the {@link URL#getServiceKey() service key} * Publish the {@link Set} of {@link ServiceRestMetadata}
* *
* @param serviceKey the {@link URL#getServiceKey() service key} * @param serviceRestMetadataSet the {@link Set} of {@link ServiceRestMetadata}
* @return the service name if found
*/ */
public String getServiceName(String serviceKey) { public void publishServiceRestMetadata(Set<ServiceRestMetadata> serviceRestMetadataSet) {
return dubboServiceKeysRepository.get(serviceKey); for (ServiceRestMetadata serviceRestMetadata : serviceRestMetadataSet) {
if (!isEmpty(serviceRestMetadata.getMeta())) {
this.serviceRestMetadata.add(serviceRestMetadata);
}
}
}
/**
* Get the {@link Set} of {@link ServiceRestMetadata}
*
* @return non-null read-only {@link Set}
*/
public Set<ServiceRestMetadata> getServiceRestMetadata() {
return unmodifiableSet(serviceRestMetadata);
}
// /**
// * Get The subscribed {@link DubboMetadataService}'s {@link URL URLs}
// *
// * @return non-null read-only {@link List}
// */
// public List<URL> getSubscribedDubboMetadataServiceURLs() {
// return Collections.unmodifiableList(subscribedDubboMetadataServiceURLs);
// }
public List<URL> findSubscribedDubboMetadataServiceURLs(String serviceName, String group, String version,
String protocol) {
String serviceKey = URL.buildKey(serviceName, group, version);
List<URL> urls = subscribedDubboMetadataServiceURLs.get(serviceKey);
if (isEmpty(urls)) {
return emptyList();
}
return hasText(protocol) ?
urls.stream().filter(url -> url.getProtocol().equalsIgnoreCase(protocol)).collect(Collectors.toList()) :
unmodifiableList(urls)
;
}
/**
* The specified service is subscribe or not
*
* @param serviceName the service name
* @return
*/
public boolean isSubscribedService(String serviceName) {
return subscribedServices.contains(serviceName);
} }
public void registerURL(URL url) { public void exportURL(URL url) {
this.registeredURLs.add(url); this.allExportedURLs.add(url.getServiceKey(), url);
} }
public void unregisterURL(URL url) { public void unexportURL(URL url) {
this.registeredURLs.remove(url); String key = url.getServiceKey();
List<URL> urls = allExportedURLs.get(key);
urls.remove(url);
this.allExportedURLs.addAll(key, urls);
} }
public Collection<URL> getRegisteredUrls() { /**
return Collections.unmodifiableSet(registeredURLs); * Get all exported {@link URL urls}.
*
* @return non-null read-only
*/
public Map<String, List<URL>> getAllExportedUrls() {
return unmodifiableMap(allExportedURLs);
} }
/** /**
* Build the {@link URL urls} by the specified {@link ServiceInstance} * Get all exported {@link URL#getServiceKey() service keys}
*
* @return non-null read-only
*/
public Set<String> getAllServiceKeys() {
return allExportedURLs.keySet();
}
/**
* Get the {@link URL urls} that {@link DubboMetadataService} exported by the specified {@link ServiceInstance}
* *
* @param serviceInstance {@link ServiceInstance} * @param serviceInstance {@link ServiceInstance}
* @return the mutable {@link URL urls} * @return the mutable {@link URL urls}
*/ */
public List<URL> buildURLs(ServiceInstance serviceInstance) { public List<URL> getDubboMetadataServiceURLs(ServiceInstance serviceInstance) {
Map<String, String> metadata = serviceInstance.getMetadata();
String dubboURLsJSON = metadata.get(DUBBO_METADATA_SERVICE_URLS_PROPERTY_NAME);
return jsonUtils.toURLs(dubboURLsJSON);
}
public Integer getDubboProtocolPort(ServiceInstance serviceInstance, String protocol) {
String protocolProperty = getDubboProtocolPropertyName(protocol);
Map<String, String> metadata = serviceInstance.getMetadata(); Map<String, String> metadata = serviceInstance.getMetadata();
String dubboURLsJSON = metadata.get(DUBBO_URLS_METADATA_PROPERTY_NAME); String protocolPort = metadata.get(protocolProperty);
List<String> urlValues = jsonUtils.toList(dubboURLsJSON); return hasText(protocolPort) ? Integer.valueOf(protocolPort) : null;
return urlValues.stream().map(URL::valueOf).collect(Collectors.toList()); }
public List<URL> getExportedURLs(String serviceInterface, String group, String version) {
String serviceKey = URL.buildKey(serviceInterface, group, version);
return allExportedURLs.getOrDefault(serviceKey, Collections.emptyList());
} }
/** /**
@ -212,6 +376,10 @@ public class DubboServiceMetadataRepository {
return match(dubboRestServiceMetadataRepository, serviceName, requestMetadata); return match(dubboRestServiceMetadataRepository, serviceName, requestMetadata);
} }
public Set<String> getSubscribedServices() {
return Collections.unmodifiableSet(subscribedServices);
}
private <T> T match(Map<String, Map<RequestMetadataMatcher, T>> repository, String serviceName, private <T> T match(Map<String, Map<RequestMetadataMatcher, T>> repository, String serviceName,
RequestMetadata requestMetadata) { RequestMetadata requestMetadata) {
@ -256,18 +424,22 @@ public class DubboServiceMetadataRepository {
} }
private Set<ServiceRestMetadata> getServiceRestMetadataSet(String serviceName) { private Set<ServiceRestMetadata> getServiceRestMetadataSet(String serviceName) {
DubboMetadataService dubboMetadataService = dubboMetadataConfigServiceProxy.newProxy(serviceName);
Set<ServiceRestMetadata> metadata = emptySet();
Set<ServiceRestMetadata> metadata = Collections.emptySet();
try { DubboMetadataService dubboMetadataService = dubboMetadataConfigServiceProxy.getProxy(serviceName);
String serviceRestMetadataJsonConfig = dubboMetadataService.getServiceRestMetadata();
if(StringUtils.hasText(serviceRestMetadataJsonConfig)) { if (dubboMetadataService != null) {
metadata = objectMapper.readValue(serviceRestMetadataJsonConfig, try {
TypeFactory.defaultInstance().constructCollectionType(LinkedHashSet.class, ServiceRestMetadata.class)); String serviceRestMetadataJsonConfig = dubboMetadataService.getServiceRestMetadata();
} if (hasText(serviceRestMetadataJsonConfig)) {
} catch (Exception e) { metadata = objectMapper.readValue(serviceRestMetadataJsonConfig,
if (logger.isErrorEnabled()) { TypeFactory.defaultInstance().constructCollectionType(LinkedHashSet.class, ServiceRestMetadata.class));
logger.error(e.getMessage(), e); }
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
} }
} }
return metadata; return metadata;
@ -292,8 +464,16 @@ public class DubboServiceMetadataRepository {
private void initSubscribedServices() { private void initSubscribedServices() {
// If subscribes all services // If subscribes all services
if (ALL_DUBBO_SERVICES.equalsIgnoreCase(dubboCloudProperties.getSubscribedServices())) { if (ALL_DUBBO_SERVICES.equals(dubboCloudProperties.getSubscribedServices())) {
subscribedServices = new HashSet<>(discoveryClient.getServices()); List<String> services = discoveryClient.getServices();
subscribedServices = new HashSet<>(services);
if (logger.isWarnEnabled()) {
logger.warn("Current application will subscribe all services(size:{}) in registry, " +
"a lot of memory and CPU cycles may be used, " +
"thus it's strongly recommend you using the externalized property '{}' " +
"to specify the services",
subscribedServices.size(), "dubbo.cloud.subscribed-services");
}
} else { } else {
subscribedServices = new HashSet<>(dubboCloudProperties.subscribedServices()); subscribedServices = new HashSet<>(dubboCloudProperties.subscribedServices());
} }
@ -305,23 +485,33 @@ public class DubboServiceMetadataRepository {
subscribedServices.remove(currentApplicationName); subscribedServices.remove(currentApplicationName);
} }
private void initDubboServiceKeysRepository() { private void initSubscribedDubboMetadataServices() {
// clear subscribedDubboMetadataServiceURLs
subscribedDubboMetadataServiceURLs.clear();
subscribedServices.stream() subscribedServices.stream()
.map(discoveryClient::getInstances) .map(discoveryClient::getInstances)
.filter(this::isNotEmpty) .filter(this::isNotEmpty)
.forEach(serviceInstances -> { .forEach(serviceInstances -> {
ServiceInstance serviceInstance = serviceInstances.get(0); ServiceInstance serviceInstance = serviceInstances.get(0);
buildURLs(serviceInstance).forEach(url -> { getDubboMetadataServiceURLs(serviceInstance).forEach(dubboMetadataServiceURL -> {
String serviceKey = url.getServiceKey(); initSubscribedDubboMetadataServiceURLs(dubboMetadataServiceURL);
String serviceName = url.getParameter(APPLICATION_KEY); initDubboMetadataServiceProxy(dubboMetadataServiceURL);
dubboServiceKeysRepository.put(serviceKey, serviceName);
}); });
}); });
} }
private void retainAvailableSubscribedServices() { private void initSubscribedDubboMetadataServiceURLs(URL dubboMetadataServiceURL) {
// dubboServiceKeysRepository.values() returns the available services(possible duplicated ones) // add subscriptions
subscribedServices = new HashSet<>(dubboServiceKeysRepository.values()); String serviceKey = dubboMetadataServiceURL.getServiceKey();
subscribedDubboMetadataServiceURLs.add(serviceKey, dubboMetadataServiceURL);
}
private void initDubboMetadataServiceProxy(URL dubboMetadataServiceURL) {
String serviceName = dubboMetadataServiceURL.getParameter(APPLICATION_KEY);
String version = dubboMetadataServiceURL.getParameter(VERSION_KEY);
// Initialize DubboMetadataService with right version
dubboMetadataConfigServiceProxy.initProxy(serviceName, version);
} }
private void initDubboRestServiceMetadataRepository() { private void initDubboRestServiceMetadataRepository() {

@ -24,24 +24,30 @@ import org.apache.dubbo.registry.support.FailbackRegistry;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataService;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceProxy;
import org.springframework.cloud.alibaba.dubbo.util.JSONUtils;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import java.util.Collection; import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singleton; import static org.apache.dubbo.common.Constants.APPLICATION_KEY;
import static org.apache.dubbo.common.Constants.GROUP_KEY;
import static org.apache.dubbo.common.Constants.PROTOCOL_KEY;
import static org.apache.dubbo.common.Constants.PROVIDER_SIDE; import static org.apache.dubbo.common.Constants.PROVIDER_SIDE;
import static org.apache.dubbo.common.Constants.SIDE_KEY; import static org.apache.dubbo.common.Constants.SIDE_KEY;
import static org.springframework.util.ObjectUtils.isEmpty; import static org.apache.dubbo.common.Constants.VERSION_KEY;
import static org.springframework.util.StringUtils.hasText; import static org.springframework.util.StringUtils.hasText;
/** /**
@ -56,6 +62,10 @@ public abstract class AbstractSpringCloudRegistry extends FailbackRegistry {
*/ */
public static final String SERVICES_LOOKUP_INTERVAL_PARAM_NAME = "dubbo.services.lookup.interval"; public static final String SERVICES_LOOKUP_INTERVAL_PARAM_NAME = "dubbo.services.lookup.interval";
protected static final String DUBBO_METADATA_SERVICE_CLASS_NAME = DubboMetadataService.class.getName();
private static final Set<String> schedulerTasks = new HashSet<>();
protected final Logger logger = LoggerFactory.getLogger(getClass()); protected final Logger logger = LoggerFactory.getLogger(getClass());
/** /**
@ -65,14 +75,27 @@ public abstract class AbstractSpringCloudRegistry extends FailbackRegistry {
private final DiscoveryClient discoveryClient; private final DiscoveryClient discoveryClient;
private final DubboServiceMetadataRepository repository;
private final DubboMetadataServiceProxy dubboMetadataConfigServiceProxy;
private final JSONUtils jsonUtils;
protected final ScheduledExecutorService servicesLookupScheduler; protected final ScheduledExecutorService servicesLookupScheduler;
public AbstractSpringCloudRegistry(URL url, public AbstractSpringCloudRegistry(URL url,
DiscoveryClient discoveryClient, DiscoveryClient discoveryClient,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboMetadataServiceProxy dubboMetadataConfigServiceProxy,
JSONUtils jsonUtils,
ScheduledExecutorService servicesLookupScheduler) { ScheduledExecutorService servicesLookupScheduler) {
super(url); super(url);
this.servicesLookupInterval = url.getParameter(SERVICES_LOOKUP_INTERVAL_PARAM_NAME, 60L); this.servicesLookupInterval = url.getParameter(SERVICES_LOOKUP_INTERVAL_PARAM_NAME, 60L);
this.discoveryClient = discoveryClient; this.discoveryClient = discoveryClient;
this.repository = dubboServiceMetadataRepository;
this.dubboMetadataConfigServiceProxy = dubboMetadataConfigServiceProxy;
this.jsonUtils = jsonUtils;
this.servicesLookupScheduler = servicesLookupScheduler; this.servicesLookupScheduler = servicesLookupScheduler;
} }
@ -122,108 +145,138 @@ public abstract class AbstractSpringCloudRegistry extends FailbackRegistry {
@Override @Override
public final void doSubscribe(URL url, NotifyListener listener) { public final void doSubscribe(URL url, NotifyListener listener) {
Set<String> serviceNames = getServiceNames(url);
doSubscribe(url, listener, serviceNames);
}
@Override if (isAdminURL(url)) {
public final void doUnsubscribe(URL url, NotifyListener listener) { // TODO in future
if (isAdminProtocol(url)) { } else if (isDubboMetadataServiceURL(url)) { // for DubboMetadataService
shutdownServiceNamesLookup(); subscribeDubboMetadataServiceURLs(url, listener);
} else { // for general Dubbo Services
subscribeDubboServiceURLs(url, listener);
} }
} }
@Override protected void subscribeDubboServiceURLs(URL url, NotifyListener listener) {
public boolean isAvailable() {
return !discoveryClient.getServices().isEmpty(); doSubscribeDubboServiceURLs(url, listener);
submitSchedulerTaskIfAbsent(url, listener);
} }
protected void shutdownServiceNamesLookup() { private void submitSchedulerTaskIfAbsent(URL url, NotifyListener listener) {
if (servicesLookupScheduler != null) { String taskId = url.toIdentityString();
servicesLookupScheduler.shutdown(); if (schedulerTasks.add(taskId)) {
schedule(() -> doSubscribeDubboServiceURLs(url, listener));
} }
} }
private Set<String> filterServiceNames(Collection<String> serviceNames) { protected void doSubscribeDubboServiceURLs(URL url, NotifyListener listener) {
return new LinkedHashSet<>(filter(serviceNames, this::supports));
Set<String> subscribedServices = repository.getSubscribedServices();
subscribedServices.stream()
.map(dubboMetadataConfigServiceProxy::getProxy)
.filter(Objects::nonNull)
.forEach(dubboMetadataService -> {
List<URL> exportedURLs = getExportedURLs(dubboMetadataService, url);
List<URL> allSubscribedURLs = new LinkedList<>();
for (URL exportedURL : exportedURLs) {
String serviceName = exportedURL.getParameter(APPLICATION_KEY);
List<ServiceInstance> serviceInstances = getServiceInstances(serviceName);
String protocol = exportedURL.getProtocol();
List<URL> subscribedURLs = new LinkedList<>();
serviceInstances.forEach(serviceInstance -> {
Integer port = repository.getDubboProtocolPort(serviceInstance, protocol);
String host = serviceInstance.getHost();
if (port == null) {
if (logger.isWarnEnabled()) {
logger.warn("The protocol[{}] port of Dubbo service instance[host : {}] " +
"can't be resolved", protocol, host);
}
} else {
URL subscribedURL = new URL(protocol, host, port, exportedURL.getParameters());
subscribedURLs.add(subscribedURL);
}
});
if (logger.isDebugEnabled()) {
logger.debug("The subscribed URL[{}] will notify all URLs : {}", url, subscribedURLs);
}
allSubscribedURLs.addAll(subscribedURLs);
}
listener.notify(allSubscribedURLs);
});
} }
protected abstract boolean supports(String serviceName); private List<ServiceInstance> getServiceInstances(String serviceName) {
return hasText(serviceName) ? doGetServiceInstances(serviceName) : emptyList();
protected final Set<String> getAllServiceNames() {
return new LinkedHashSet<>(discoveryClient.getServices());
} }
/** private List<ServiceInstance> doGetServiceInstances(String serviceName) {
* Get the service names from the specified {@link URL url} List<ServiceInstance> serviceInstances = emptyList();
* try {
* @param url {@link URL} serviceInstances = discoveryClient.getInstances(serviceName);
* @return non-null } catch (Exception e) {
*/ if (logger.isErrorEnabled()) {
private Set<String> getServiceNames(URL url) { logger.error(e.getMessage(), e);
if (isAdminProtocol(url)) { }
return getServiceNamesForOps(url);
} else {
return singleton(getServiceName(url));
} }
return serviceInstances;
} }
protected boolean isAdminProtocol(URL url) { private List<URL> getExportedURLs(DubboMetadataService dubboMetadataService, URL url) {
return Constants.ADMIN_PROTOCOL.equals(url.getProtocol()); String serviceInterface = url.getServiceInterface();
String group = url.getParameter(GROUP_KEY);
String version = url.getParameter(VERSION_KEY);
// The subscribed protocol may be null
String subscribedProtocol = url.getParameter(PROTOCOL_KEY);
String exportedURLsJSON = dubboMetadataService.getExportedURLs(serviceInterface, group, version);
return jsonUtils
.toURLs(exportedURLsJSON)
.stream()
.filter(exportedURL ->
subscribedProtocol == null || subscribedProtocol.equalsIgnoreCase(exportedURL.getProtocol())
).collect(Collectors.toList());
} }
/** private void subscribeDubboMetadataServiceURLs(URL url, NotifyListener listener) {
* Get the service names for Dubbo OPS String serviceInterface = url.getServiceInterface();
* String group = url.getParameter(GROUP_KEY);
* @param url {@link URL} String version = url.getParameter(VERSION_KEY);
* @return non-null String protocol = url.getParameter(PROTOCOL_KEY);
*/ List<URL> urls = repository.findSubscribedDubboMetadataServiceURLs(serviceInterface, group, version, protocol);
protected Set<String> getServiceNamesForOps(URL url) { listener.notify(urls);
Set<String> serviceNames = getAllServiceNames();
return filterServiceNames(serviceNames);
} }
protected abstract String getServiceName(URL url); @Override
public final void doUnsubscribe(URL url, NotifyListener listener) {
private void doSubscribe(final URL url, final NotifyListener listener, final Collection<String> serviceNames) { if (isAdminURL(url)) {
shutdownServiceNamesLookup();
subscribe(url, listener, serviceNames); }
schedule(() -> {
subscribe(url, listener, serviceNames);
});
} }
protected ScheduledFuture<?> schedule(Runnable runnable) { @Override
return this.servicesLookupScheduler.scheduleAtFixedRate(runnable, servicesLookupInterval, public boolean isAvailable() {
servicesLookupInterval, TimeUnit.SECONDS); return !discoveryClient.getServices().isEmpty();
} }
protected List<ServiceInstance> getServiceInstances(String serviceName) { protected void shutdownServiceNamesLookup() {
return hasText(serviceName) ? discoveryClient.getInstances(serviceName) : emptyList(); if (servicesLookupScheduler != null) {
servicesLookupScheduler.shutdown();
}
} }
private void subscribe(final URL url, final NotifyListener listener, final Collection<String> serviceNames) { protected boolean isAdminURL(URL url) {
for (String serviceName : serviceNames) { return Constants.ADMIN_PROTOCOL.equals(url.getProtocol());
List<ServiceInstance> serviceInstances = getServiceInstances(serviceName);
if (!isEmpty(serviceInstances)) {
notifySubscriber(url, listener, serviceInstances);
}
}
} }
/** protected boolean isDubboMetadataServiceURL(URL url) {
* Notify the Healthy {@link ServiceInstance service instance} to subscriber. return DUBBO_METADATA_SERVICE_CLASS_NAME.equals(url.getServiceInterface());
* }
* @param url {@link URL}
* @param listener {@link NotifyListener}
* @param serviceInstances all {@link ServiceInstance instances}
*/
protected abstract void notifySubscriber(URL url, NotifyListener listener, List<ServiceInstance> serviceInstances);
protected <T> Collection<T> filter(Collection<T> collection, Predicate<T> filter) { protected ScheduledFuture<?> schedule(Runnable runnable) {
return collection.stream() return this.servicesLookupScheduler.scheduleAtFixedRate(runnable, servicesLookupInterval,
.filter(filter) servicesLookupInterval, TimeUnit.SECONDS);
.collect(Collectors.toList());
} }
} }

@ -17,16 +17,14 @@
package org.springframework.cloud.alibaba.dubbo.registry; package org.springframework.cloud.alibaba.dubbo.registry;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.NotifyListener;
import org.apache.dubbo.registry.RegistryFactory; import org.apache.dubbo.registry.RegistryFactory;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository; import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceProxy;
import org.springframework.cloud.alibaba.dubbo.util.JSONUtils;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;
/** /**
* Dubbo {@link RegistryFactory} uses Spring Cloud Service Registration abstraction, whose protocol is "spring-cloud" * Dubbo {@link RegistryFactory} uses Spring Cloud Service Registration abstraction, whose protocol is "spring-cloud"
@ -38,38 +36,21 @@ public class SpringCloudRegistry extends AbstractSpringCloudRegistry {
private final DubboServiceMetadataRepository dubboServiceMetadataRepository; private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
public SpringCloudRegistry(URL url, DiscoveryClient discoveryClient, public SpringCloudRegistry(URL url, DiscoveryClient discoveryClient,
ScheduledExecutorService servicesLookupScheduler, DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboServiceMetadataRepository dubboServiceMetadataRepository) { DubboMetadataServiceProxy dubboMetadataConfigServiceProxy,
super(url, discoveryClient, servicesLookupScheduler); JSONUtils jsonUtils,
ScheduledExecutorService servicesLookupScheduler) {
super(url, discoveryClient, dubboServiceMetadataRepository, dubboMetadataConfigServiceProxy, jsonUtils, servicesLookupScheduler);
this.dubboServiceMetadataRepository = dubboServiceMetadataRepository; this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
} }
@Override @Override
protected void doRegister0(URL url) { protected void doRegister0(URL url) {
dubboServiceMetadataRepository.registerURL(url); dubboServiceMetadataRepository.exportURL(url);
} }
@Override @Override
protected void doUnregister0(URL url) { protected void doUnregister0(URL url) {
dubboServiceMetadataRepository.unregisterURL(url); dubboServiceMetadataRepository.unexportURL(url);
}
@Override
protected boolean supports(String serviceName) {
return dubboServiceMetadataRepository.isSubscribedService(serviceName);
}
@Override
protected String getServiceName(URL url) {
return dubboServiceMetadataRepository.getServiceName(url);
}
@Override
protected void notifySubscriber(URL url, NotifyListener listener, List<ServiceInstance> serviceInstances) {
List<URL> urls = serviceInstances.stream()
.map(dubboServiceMetadataRepository::buildURLs)
.flatMap(List::stream)
.collect(Collectors.toList());
notify(url, listener, urls);
} }
} }

@ -22,6 +22,8 @@ import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.registry.RegistryFactory; import org.apache.dubbo.registry.RegistryFactory;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository; import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.service.DubboMetadataServiceProxy;
import org.springframework.cloud.alibaba.dubbo.util.JSONUtils;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@ -50,6 +52,10 @@ public class SpringCloudRegistryFactory implements RegistryFactory {
private DubboServiceMetadataRepository dubboServiceMetadataRepository; private DubboServiceMetadataRepository dubboServiceMetadataRepository;
private DubboMetadataServiceProxy dubboMetadataConfigServiceProxy;
private JSONUtils jsonUtils;
private volatile boolean initialized = false; private volatile boolean initialized = false;
public SpringCloudRegistryFactory() { public SpringCloudRegistryFactory() {
@ -63,12 +69,15 @@ public class SpringCloudRegistryFactory implements RegistryFactory {
} }
this.discoveryClient = applicationContext.getBean(DiscoveryClient.class); this.discoveryClient = applicationContext.getBean(DiscoveryClient.class);
this.dubboServiceMetadataRepository = applicationContext.getBean(DubboServiceMetadataRepository.class); this.dubboServiceMetadataRepository = applicationContext.getBean(DubboServiceMetadataRepository.class);
this.dubboMetadataConfigServiceProxy = applicationContext.getBean(DubboMetadataServiceProxy.class);
this.jsonUtils = applicationContext.getBean(JSONUtils.class);
} }
@Override @Override
public Registry getRegistry(URL url) { public Registry getRegistry(URL url) {
init(); init();
return new SpringCloudRegistry(url, discoveryClient, servicesLookupScheduler, dubboServiceMetadataRepository); return new SpringCloudRegistry(url, discoveryClient, dubboServiceMetadataRepository,
dubboMetadataConfigServiceProxy, jsonUtils, servicesLookupScheduler);
} }
public static void setApplicationContext(ConfigurableApplicationContext applicationContext) { public static void setApplicationContext(ConfigurableApplicationContext applicationContext) {

@ -62,9 +62,9 @@ public class DubboGenericServiceFactory {
return referenceBean == null ? null : referenceBean.get(); return referenceBean == null ? null : referenceBean.get();
} }
public GenericService create(String serviceName, Class<?> serviceClass) { public GenericService create(String serviceName, Class<?> serviceClass, String version) {
String interfaceName = serviceClass.getName(); String interfaceName = serviceClass.getName();
ReferenceBean<GenericService> referenceBean = build(interfaceName, serviceName, null, emptyMap()); ReferenceBean<GenericService> referenceBean = build(interfaceName, version, serviceName, emptyMap());
return referenceBean.get(); return referenceBean.get();
} }

@ -16,21 +16,61 @@
*/ */
package org.springframework.cloud.alibaba.dubbo.service; package org.springframework.cloud.alibaba.dubbo.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata; import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
* Dubbo Metadata Service * Dubbo Metadata Service is a core interface for service subscribers,
* it must keep the stable of structure in every evolution , makes sure all subscribers' compatibility.
* <p>
* The interface contract's version must be {@link #VERSION} constant and group must be current Dubbo application name
* *
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a> * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/ */
public interface DubboMetadataService { public interface DubboMetadataService {
/** /**
* Get The json content of {@link ServiceRestMetadata} {@link Set} * Current version of the interface contract
*/
String VERSION = "1.0.0";
/**
* Get the json content of {@link ServiceRestMetadata} {@link Set}
* *
* @return <code>null</code> if present * @return <code>null</code> if present
*/ */
String getServiceRestMetadata(); String getServiceRestMetadata();
/**
* Get all exported {@link URL#getServiceKey() service keys}
*
* @return non-null read-only {@link Set}
*/
Set<String> getAllServiceKeys();
/**
* Get all exported Dubbo's {@link URL URLs} {@link Map} whose key is the return value of
* {@link URL#getServiceKey()} method and value is the json content of List<URL> of {@link URL URLs}
*
* @return non-null read-only {@link Map}
*/
Map<String, String> getAllExportedURLs();
/**
* Get the json content of an exported List<URL> of {@link URL URLs} by the serviceInterface , group and version
*
* @param serviceInterface The class name of service interface
* @param group {@link Service#group() the service group} (optional)
* @param version {@link Service#version() the service version} (optional)
* @return non-null read-only {@link List}
* @see URL
*/
String getExportedURLs(String serviceInterface, String group, String version);
} }

@ -16,16 +16,20 @@
*/ */
package org.springframework.cloud.alibaba.dubbo.service; package org.springframework.cloud.alibaba.dubbo.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.ServiceConfig; import org.apache.dubbo.config.ServiceConfig;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
@ -42,12 +46,12 @@ public class DubboMetadataServiceExporter {
private ApplicationConfig applicationConfig; private ApplicationConfig applicationConfig;
@Autowired @Autowired
private DubboMetadataService dubboMetadataService; private ObjectProvider<DubboMetadataService> dubboMetadataService;
@Autowired @Autowired
private Supplier<ProtocolConfig> protocolConfigSupplier; private Supplier<ProtocolConfig> protocolConfigSupplier;
@Value("${spring.application.name:application}") @Value("${spring.application.name:${dubbo.application.name:application}}")
private String currentApplicationName; private String currentApplicationName;
/** /**
@ -57,33 +61,39 @@ public class DubboMetadataServiceExporter {
/** /**
* export {@link DubboMetadataService} as Dubbo service * export {@link DubboMetadataService} as Dubbo service
*
* @return the exported {@link URL URLs}
*/ */
public void export() { public List<URL> export() {
if (serviceConfig != null && serviceConfig.isExported()) { if (serviceConfig == null || !serviceConfig.isExported()) {
return;
}
serviceConfig = new ServiceConfig<>(); serviceConfig = new ServiceConfig<>();
serviceConfig.setInterface(DubboMetadataService.class); serviceConfig.setInterface(DubboMetadataService.class);
// Use current Spring application name as the Dubbo Service version // Use DubboMetadataService.VERSION as the Dubbo Service version
serviceConfig.setVersion(currentApplicationName); serviceConfig.setVersion(DubboMetadataService.VERSION);
serviceConfig.setRef(dubboMetadataService); // Use current Spring application name as the Dubbo Service group
serviceConfig.setApplication(applicationConfig); serviceConfig.setGroup(currentApplicationName);
serviceConfig.setProtocol(protocolConfigSupplier.get()); serviceConfig.setRef(dubboMetadataService.getIfAvailable());
serviceConfig.setApplication(applicationConfig);
serviceConfig.setProtocol(protocolConfigSupplier.get());
serviceConfig.export(); serviceConfig.export();
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("The Dubbo service[{}] has been exported.", serviceConfig.toString()); logger.info("The Dubbo service[{}] has been exported.", serviceConfig.toString());
}
} }
return serviceConfig.getExportedUrls();
} }
/** /**
* unexport {@link DubboMetadataService} * unexport {@link DubboMetadataService}
*/ */
@PreDestroy
public void unexport() { public void unexport() {
if (serviceConfig == null || serviceConfig.isUnexported()) { if (serviceConfig == null || serviceConfig.isUnexported()) {

@ -18,8 +18,12 @@ package org.springframework.cloud.alibaba.dubbo.service;
import org.apache.dubbo.rpc.service.GenericService; import org.apache.dubbo.rpc.service.GenericService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.stream.Stream;
/** /**
* {@link DubboMetadataService} {@link InvocationHandler} * {@link DubboMetadataService} {@link InvocationHandler}
@ -28,27 +32,29 @@ import java.lang.reflect.Method;
*/ */
class DubboMetadataServiceInvocationHandler implements InvocationHandler { class DubboMetadataServiceInvocationHandler implements InvocationHandler {
/** private final Logger logger = LoggerFactory.getLogger(getClass());
* The method name of {@link DubboMetadataService#getServiceRestMetadata()}
*/
private static final String METHOD_NAME = "getServiceRestMetadata";
private static final String[] PARAMETER_TYPES = new String[0];
private static final String[] PARAMETER_VALUES = new String[0];
private final GenericService genericService; private final GenericService genericService;
public DubboMetadataServiceInvocationHandler(String serviceName, DubboGenericServiceFactory dubboGenericServiceFactory) { public DubboMetadataServiceInvocationHandler(String serviceName, String version, DubboGenericServiceFactory dubboGenericServiceFactory) {
this.genericService = dubboGenericServiceFactory.create(serviceName, DubboMetadataService.class); this.genericService = dubboGenericServiceFactory.create(serviceName, DubboMetadataService.class, version);
} }
@Override @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName(); Object returnValue = null;
if (METHOD_NAME.equals(methodName)) { try {
return genericService.$invoke(methodName, PARAMETER_TYPES, PARAMETER_VALUES); returnValue = genericService.$invoke(method.getName(), getParameterTypes(method), args);
} catch (Throwable e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
} }
return method.invoke(proxy, args); return returnValue;
}
private String[] getParameterTypes(Method method) {
Class<?>[] parameterTypes = method.getParameterTypes();
return Stream.of(parameterTypes).map(Class::getName).toArray(length -> new String[length]);
} }
} }

@ -17,35 +17,68 @@
package org.springframework.cloud.alibaba.dubbo.service; package org.springframework.cloud.alibaba.dubbo.service;
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.DisposableBean;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static java.lang.reflect.Proxy.newProxyInstance; import static java.lang.reflect.Proxy.newProxyInstance;
/** /**
* The proxy of {@link DubboMetadataService} * The proxy of {@link DubboMetadataService}
*/ */
public class DubboMetadataServiceProxy implements BeanClassLoaderAware { public class DubboMetadataServiceProxy implements BeanClassLoaderAware, DisposableBean {
private final DubboGenericServiceFactory dubboGenericServiceFactory; private final DubboGenericServiceFactory dubboGenericServiceFactory;
private ClassLoader classLoader; private ClassLoader classLoader;
private final Map<String, DubboMetadataService> dubboMetadataServiceCache = new ConcurrentHashMap<>();
public DubboMetadataServiceProxy(DubboGenericServiceFactory dubboGenericServiceFactory) { public DubboMetadataServiceProxy(DubboGenericServiceFactory dubboGenericServiceFactory) {
this.dubboGenericServiceFactory = dubboGenericServiceFactory; this.dubboGenericServiceFactory = dubboGenericServiceFactory;
} }
/** /**
* New proxy instance of {@link DubboMetadataService} via the specified service name * Initializes {@link DubboMetadataService}'s Proxy
* *
* @param serviceName the service name * @param serviceName the service name
* @param version the service version
* @return a {@link DubboMetadataService} proxy * @return a {@link DubboMetadataService} proxy
*/ */
public DubboMetadataService newProxy(String serviceName) { public DubboMetadataService initProxy(String serviceName, String version) {
return (DubboMetadataService) newProxyInstance(classLoader, new Class[]{DubboMetadataService.class}, return dubboMetadataServiceCache.computeIfAbsent(serviceName, name -> newProxy(name, version));
new DubboMetadataServiceInvocationHandler(serviceName, dubboGenericServiceFactory)); }
/**
* Get a proxy instance of {@link DubboMetadataService} via the specified service name
*
* @param serviceName the service name
* @return a {@link DubboMetadataService} proxy
*/
public DubboMetadataService getProxy(String serviceName) {
return dubboMetadataServiceCache.get(serviceName);
} }
@Override @Override
public void setBeanClassLoader(ClassLoader classLoader) { public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader; this.classLoader = classLoader;
} }
@Override
public void destroy() throws Exception {
dubboMetadataServiceCache.clear();
}
/**
* New a proxy instance of {@link DubboMetadataService} via the specified service name
*
* @param serviceName the service name
* @param version the service version
* @return a {@link DubboMetadataService} proxy
*/
protected DubboMetadataService newProxy(String serviceName, String version) {
return (DubboMetadataService) newProxyInstance(classLoader, new Class[]{DubboMetadataService.class},
new DubboMetadataServiceInvocationHandler(serviceName, version, dubboGenericServiceFactory));
}
} }

@ -0,0 +1,96 @@
/*
* 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.service;
import org.apache.dubbo.common.URL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
import org.springframework.cloud.alibaba.dubbo.util.JSONUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.unmodifiableMap;
import static org.springframework.util.CollectionUtils.isEmpty;
/**
* Introspective {@link DubboMetadataService} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class IntrospectiveDubboMetadataService implements DubboMetadataService {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectProvider<DubboServiceMetadataRepository> dubboServiceMetadataRepository;
@Autowired
private JSONUtils jsonUtils;
@Override
public String getServiceRestMetadata() {
Set<ServiceRestMetadata> serviceRestMetadata = getRepository().getServiceRestMetadata();
String serviceRestMetadataJsonConfig = null;
if (!isEmpty(serviceRestMetadata)) {
serviceRestMetadataJsonConfig = jsonUtils.toJSON(serviceRestMetadata);
}
return serviceRestMetadataJsonConfig;
}
@Override
public Set<String> getAllServiceKeys() {
return getRepository().getAllServiceKeys();
}
@Override
public Map<String, String> getAllExportedURLs() {
Map<String, List<URL>> allExportedUrls = getRepository().getAllExportedUrls();
if (isEmpty(allExportedUrls)) {
if (logger.isDebugEnabled()) {
logger.debug("There is no registered URL.");
}
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
allExportedUrls.forEach((serviceKey, urls) -> {
result.put(serviceKey, jsonUtils.toJSON(urls));
});
return unmodifiableMap(result);
}
@Override
public String getExportedURLs(String serviceInterface, String group, String version) {
List<URL> urls = getRepository().getExportedURLs(serviceInterface, group, version);
return jsonUtils.toJSON(urls);
}
private DubboServiceMetadataRepository getRepository() {
return dubboServiceMetadataRepository.getIfAvailable();
}
}

@ -1,66 +0,0 @@
/*
* 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.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
import org.springframework.cloud.alibaba.dubbo.util.JSONUtils;
import org.springframework.util.CollectionUtils;
import java.util.LinkedHashSet;
import java.util.Set;
import static org.springframework.util.ObjectUtils.isEmpty;
/**
* Publishing {@link DubboMetadataService} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class PublishingDubboMetadataService implements DubboMetadataService {
/**
* 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 Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>();
@Autowired
private JSONUtils jsonUtils;
/**
* Publish the {@link Set} of {@link ServiceRestMetadata}
*
* @param serviceRestMetadataSet the {@link Set} of {@link ServiceRestMetadata}
*/
public void publishServiceRestMetadata(Set<ServiceRestMetadata> serviceRestMetadataSet) {
for (ServiceRestMetadata serviceRestMetadata : serviceRestMetadataSet) {
if (!CollectionUtils.isEmpty(serviceRestMetadata.getMeta())) {
this.serviceRestMetadata.add(serviceRestMetadata);
}
}
}
@Override
public String getServiceRestMetadata() {
String serviceRestMetadataJsonConfig = null;
if (!isEmpty(serviceRestMetadata)) {
serviceRestMetadataJsonConfig = jsonUtils.toJSON(serviceRestMetadata);
}
return serviceRestMetadataJsonConfig;
}
}

@ -16,6 +16,8 @@
*/ */
package org.springframework.cloud.alibaba.dubbo.util; package org.springframework.cloud.alibaba.dubbo.util;
import org.apache.dubbo.common.URL;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
@ -25,8 +27,10 @@ import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* JSON Utilities class * JSON Utilities class
@ -44,6 +48,10 @@ public class JSONUtils {
this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT); this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
} }
public String toJSON(Collection<URL> urls) {
return toJSON(urls.stream().map(URL::toFullString).collect(Collectors.toSet()));
}
public String toJSON(Object object) { public String toJSON(Object object) {
String jsonContent = null; String jsonContent = null;
try { try {
@ -56,6 +64,11 @@ public class JSONUtils {
return jsonContent; return jsonContent;
} }
public List<URL> toURLs(String urlsJSON) {
List<String> list = toList(urlsJSON);
return list.stream().map(URL::valueOf).collect(Collectors.toList());
}
public List<String> toList(String json) { public List<String> toList(String json) {
List<String> list = Collections.emptyList(); List<String> list = Collections.emptyList();
try { try {

Loading…
Cancel
Save