Format Code using Spring Boot Code Conventions

pull/796/head
mercyblitz 6 years ago
parent 12efadd61f
commit 3df938d229

@ -16,7 +16,6 @@
*/
package com.alibaba.cloud.dubbo.actuate;
import com.alibaba.cloud.dubbo.actuate.endpoint.DubboRestMetadataEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -25,6 +24,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import com.alibaba.cloud.dubbo.actuate.endpoint.DubboRestMetadataEndpoint;
/**
* Dubbo Metadata Endpoints Auto-{@link Configuration}
*
@ -35,12 +36,10 @@ import org.springframework.context.annotation.PropertySource;
@ManagementContextConfiguration
public class DubboMetadataEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public DubboRestMetadataEndpoint dubboRestMetadataEndpoint() {
return new DubboRestMetadataEndpoint();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public DubboRestMetadataEndpoint dubboRestMetadataEndpoint() {
return new DubboRestMetadataEndpoint();
}
}

@ -16,12 +16,13 @@
*/
package com.alibaba.cloud.dubbo.actuate.endpoint;
import com.alibaba.cloud.dubbo.service.DubboMetadataService;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
import com.alibaba.cloud.dubbo.service.DubboMetadataService;
/**
* Dubbo Rest Metadata {@link Endpoint}
@ -31,11 +32,11 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE;
@Endpoint(id = "dubborestmetadata")
public class DubboRestMetadataEndpoint {
@Autowired
private DubboMetadataService dubboMetadataService;
@Autowired
private DubboMetadataService dubboMetadataService;
@ReadOperation(produces = APPLICATION_JSON_UTF8_VALUE)
public String get() {
return dubboMetadataService.getServiceRestMetadata();
}
@ReadOperation(produces = APPLICATION_JSON_UTF8_VALUE)
public String get() {
return dubboMetadataService.getServiceRestMetadata();
}
}

@ -16,13 +16,7 @@
*/
package com.alibaba.cloud.dubbo.annotation;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.rpc.ExporterListener;
import org.apache.dubbo.rpc.Filter;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.client.RestTemplate;
import static org.apache.dubbo.rpc.cluster.Constants.DEFAULT_RETRIES;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@ -30,22 +24,31 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static org.apache.dubbo.rpc.cluster.Constants.DEFAULT_RETRIES;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.rpc.ExporterListener;
import org.apache.dubbo.rpc.Filter;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.client.RestTemplate;
/**
* {@link DubboTransported @DubboTransported} annotation indicates that the traditional Spring Cloud Service-to-Service call is transported
* by Dubbo under the hood, there are two main scenarios:
* {@link DubboTransported @DubboTransported} annotation indicates that the traditional
* Spring Cloud Service-to-Service call is transported by Dubbo under the hood, there are
* two main scenarios:
* <ol>
* <li>{@link FeignClient @FeignClient} annotated classes:
* <ul>
* If {@link DubboTransported @DubboTransported} annotated classes, the invocation of all methods of
* {@link FeignClient @FeignClient} annotated classes.
* If {@link DubboTransported @DubboTransported} annotated classes, the invocation of all
* methods of {@link FeignClient @FeignClient} annotated classes.
* </ul>
* <ul>
* If {@link DubboTransported @DubboTransported} annotated methods of {@link FeignClient @FeignClient} annotated classes.
* If {@link DubboTransported @DubboTransported} annotated methods of
* {@link FeignClient @FeignClient} annotated classes.
* </ul>
* </li>
* <li>{@link LoadBalanced @LoadBalanced} {@link RestTemplate} annotated field, method and parameters</li>
* <li>{@link LoadBalanced @LoadBalanced} {@link RestTemplate} annotated field, method and
* parameters</li>
* </ol>
* <p>
*
@ -54,94 +57,99 @@ import static org.apache.dubbo.rpc.cluster.Constants.DEFAULT_RETRIES;
* @see LoadBalanced
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Target(value = { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,
ElementType.PARAMETER })
@Documented
public @interface DubboTransported {
/**
* The protocol of Dubbo transport whose value could be used the placeholder "dubbo.transport.protocol"
*
* @return the default protocol is "dubbo"
*/
String protocol() default "${dubbo.transport.protocol:dubbo}";
/**
* The cluster of Dubbo transport whose value could be used the placeholder "dubbo.transport.cluster"
*
* @return the default cluster is "failover"
*/
String cluster() default "${dubbo.transport.cluster:failover}";
/**
* Whether to reconnect if connection is lost, if not specify, reconnect is enabled by default, and the interval
* for retry connecting is 2000 ms
*
* @see Reference#reconnect()
*/
String reconnect() default "${dubbo.transport.reconnect:2000}";
/**
* Maximum connections service provider can accept, default value is 0 - connection is shared
*
* @see Reference#connections()
*/
int connections() default 0;
/**
* Service invocation retry times
*
* @see Reference#retries()
*/
int retries() default DEFAULT_RETRIES;
/**
* Load balance strategy, legal values include: random, roundrobin, leastactive
*
* @see Reference#loadbalance()
*/
String loadbalance() default "${dubbo.transport.loadbalance:}";
/**
* Maximum active requests allowed, default value is 0
*
* @see Reference#actives()
*/
int actives() default 0;
/**
* Timeout value for service invocation, default value is 0
*
* @see Reference#timeout()
*/
int timeout() default 0;
/**
* Specify cache implementation for service invocation, legal values include: lru, threadlocal, jcache
*
* @see Reference#cache()
*/
String cache() default "${dubbo.transport.cache:}";
/**
* Filters for service invocation
*
* @see Filter
* @see Reference#filter()
*/
String[] filter() default {};
/**
* Listeners for service exporting and unexporting
*
* @see ExporterListener
* @see Reference#listener()
*/
String[] listener() default {};
/**
* Customized parameter key-value pair, for example: {key1, value1, key2, value2}
*
* @see Reference#parameters()
*/
String[] parameters() default {};
/**
* The protocol of Dubbo transport whose value could be used the placeholder
* "dubbo.transport.protocol"
*
* @return the default protocol is "dubbo"
*/
String protocol() default "${dubbo.transport.protocol:dubbo}";
/**
* The cluster of Dubbo transport whose value could be used the placeholder
* "dubbo.transport.cluster"
*
* @return the default cluster is "failover"
*/
String cluster() default "${dubbo.transport.cluster:failover}";
/**
* Whether to reconnect if connection is lost, if not specify, reconnect is enabled by
* default, and the interval for retry connecting is 2000 ms
*
* @see Reference#reconnect()
*/
String reconnect() default "${dubbo.transport.reconnect:2000}";
/**
* Maximum connections service provider can accept, default value is 0 - connection is
* shared
*
* @see Reference#connections()
*/
int connections() default 0;
/**
* Service invocation retry times
*
* @see Reference#retries()
*/
int retries() default DEFAULT_RETRIES;
/**
* Load balance strategy, legal values include: random, roundrobin, leastactive
*
* @see Reference#loadbalance()
*/
String loadbalance() default "${dubbo.transport.loadbalance:}";
/**
* Maximum active requests allowed, default value is 0
*
* @see Reference#actives()
*/
int actives() default 0;
/**
* Timeout value for service invocation, default value is 0
*
* @see Reference#timeout()
*/
int timeout() default 0;
/**
* Specify cache implementation for service invocation, legal values include: lru,
* threadlocal, jcache
*
* @see Reference#cache()
*/
String cache() default "${dubbo.transport.cache:}";
/**
* Filters for service invocation
*
* @see Filter
* @see Reference#filter()
*/
String[] filter() default {};
/**
* Listeners for service exporting and unexporting
*
* @see ExporterListener
* @see Reference#listener()
*/
String[] listener() default {};
/**
* Customized parameter key-value pair, for example: {key1, value1, key2, value2}
*
* @see Reference#parameters()
*/
String[] parameters() default {};
}

@ -16,13 +16,11 @@
*/
package com.alibaba.cloud.dubbo.autoconfigure;
import com.alibaba.cloud.dubbo.annotation.DubboTransported;
import com.alibaba.cloud.dubbo.client.loadbalancer.DubboMetadataInitializerInterceptor;
import com.alibaba.cloud.dubbo.client.loadbalancer.DubboTransporterInterceptor;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.metadata.resolver.DubboTransportedAttributesResolver;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
@ -44,133 +42,154 @@ import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.dubbo.annotation.DubboTransported;
import com.alibaba.cloud.dubbo.client.loadbalancer.DubboMetadataInitializerInterceptor;
import com.alibaba.cloud.dubbo.client.loadbalancer.DubboTransporterInterceptor;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.metadata.resolver.DubboTransportedAttributesResolver;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
/**
* Dubbo Auto-{@link Configuration} for {@link LoadBalanced @LoadBalanced} {@link RestTemplate}
* Dubbo Auto-{@link Configuration} for {@link LoadBalanced @LoadBalanced}
* {@link RestTemplate}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@ConditionalOnClass(name = {"org.springframework.web.client.RestTemplate"})
@AutoConfigureAfter(name = {"org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration"})
public class DubboLoadBalancedRestTemplateAutoConfiguration implements BeanClassLoaderAware, SmartInitializingSingleton {
private static final Class<DubboTransported> DUBBO_TRANSPORTED_CLASS = DubboTransported.class;
private static final String DUBBO_TRANSPORTED_CLASS_NAME = DUBBO_TRANSPORTED_CLASS.getName();
@Autowired
private DubboServiceMetadataRepository repository;
@Autowired(required = false)
private LoadBalancerInterceptor loadBalancerInterceptor;
@Autowired(required = false)
private RetryLoadBalancerInterceptor retryLoadBalancerInterceptor;
@Autowired
private ConfigurableListableBeanFactory beanFactory;
@Autowired
private DubboGenericServiceFactory serviceFactory;
@Autowired
private DubboGenericServiceExecutionContextFactory contextFactory;
@Autowired
private Environment environment;
@LoadBalanced
@Autowired(required = false)
private Map<String, RestTemplate> restTemplates = Collections.emptyMap();
private ClassLoader classLoader;
/**
* The {@link ClientHttpRequestInterceptor} bean that may be {@link LoadBalancerInterceptor} or {@link RetryLoadBalancerInterceptor}
*/
private ClientHttpRequestInterceptor loadBalancerInterceptorBean;
@Override
public void afterSingletonsInstantiated() {
loadBalancerInterceptorBean = retryLoadBalancerInterceptor != null ?
retryLoadBalancerInterceptor :
loadBalancerInterceptor;
}
/**
* Adapt the {@link RestTemplate} beans that are annotated {@link LoadBalanced @LoadBalanced} and
* {@link LoadBalanced @LoadBalanced} when Spring Boot application started
* (after the callback of {@link SmartInitializingSingleton} beans or
* {@link RestTemplateCustomizer#customize(RestTemplate) customization})
*/
@EventListener(ApplicationStartedEvent.class)
public void adaptRestTemplates() {
DubboTransportedAttributesResolver attributesResolver = new DubboTransportedAttributesResolver(environment);
for (Map.Entry<String, RestTemplate> entry : restTemplates.entrySet()) {
String beanName = entry.getKey();
Map<String, Object> dubboTranslatedAttributes = getDubboTranslatedAttributes(beanName, attributesResolver);
if (!CollectionUtils.isEmpty(dubboTranslatedAttributes)) {
adaptRestTemplate(entry.getValue(), dubboTranslatedAttributes);
}
}
}
/**
* Gets the annotation attributes {@link RestTemplate} bean being annotated
* {@link DubboTransported @DubboTransported}
*
* @param beanName the bean name of {@link LoadBalanced @LoadBalanced} {@link RestTemplate}
* @param attributesResolver {@link DubboTransportedAttributesResolver}
* @return non-null {@link Map}
*/
private Map<String, Object> getDubboTranslatedAttributes(String beanName,
DubboTransportedAttributesResolver attributesResolver) {
Map<String, Object> attributes = Collections.emptyMap();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (beanDefinition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
MethodMetadata factoryMethodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata();
attributes = factoryMethodMetadata != null ?
factoryMethodMetadata.getAnnotationAttributes(DUBBO_TRANSPORTED_CLASS_NAME) : Collections.emptyMap();
}
return attributesResolver.resolve(attributes);
}
/**
* Adapt the instance of {@link DubboTransporterInterceptor} to the {@link LoadBalancerInterceptor} Bean.
*
* @param restTemplate {@link LoadBalanced @LoadBalanced} {@link RestTemplate} Bean
* @param dubboTranslatedAttributes the annotation dubboTranslatedAttributes {@link RestTemplate} bean being annotated
* {@link DubboTransported @DubboTransported}
*/
private void adaptRestTemplate(RestTemplate restTemplate, Map<String, Object> dubboTranslatedAttributes) {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors());
int index = loadBalancerInterceptorBean == null ? -1 : interceptors.indexOf(loadBalancerInterceptorBean);
index = index < 0 ? 0 : index;
// Add ClientHttpRequestInterceptor instances before loadBalancerInterceptor
interceptors.add(index++, new DubboMetadataInitializerInterceptor(repository));
interceptors.add(index++, new DubboTransporterInterceptor(repository, restTemplate.getMessageConverters(),
classLoader, dubboTranslatedAttributes, serviceFactory, contextFactory));
restTemplate.setInterceptors(interceptors);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@ConditionalOnClass(name = { "org.springframework.web.client.RestTemplate" })
@AutoConfigureAfter(name = {
"org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration" })
public class DubboLoadBalancedRestTemplateAutoConfiguration
implements BeanClassLoaderAware, SmartInitializingSingleton {
private static final Class<DubboTransported> DUBBO_TRANSPORTED_CLASS = DubboTransported.class;
private static final String DUBBO_TRANSPORTED_CLASS_NAME = DUBBO_TRANSPORTED_CLASS
.getName();
@Autowired
private DubboServiceMetadataRepository repository;
@Autowired(required = false)
private LoadBalancerInterceptor loadBalancerInterceptor;
@Autowired(required = false)
private RetryLoadBalancerInterceptor retryLoadBalancerInterceptor;
@Autowired
private ConfigurableListableBeanFactory beanFactory;
@Autowired
private DubboGenericServiceFactory serviceFactory;
@Autowired
private DubboGenericServiceExecutionContextFactory contextFactory;
@Autowired
private Environment environment;
@LoadBalanced
@Autowired(required = false)
private Map<String, RestTemplate> restTemplates = Collections.emptyMap();
private ClassLoader classLoader;
/**
* The {@link ClientHttpRequestInterceptor} bean that may be
* {@link LoadBalancerInterceptor} or {@link RetryLoadBalancerInterceptor}
*/
private ClientHttpRequestInterceptor loadBalancerInterceptorBean;
@Override
public void afterSingletonsInstantiated() {
loadBalancerInterceptorBean = retryLoadBalancerInterceptor != null
? retryLoadBalancerInterceptor
: loadBalancerInterceptor;
}
/**
* Adapt the {@link RestTemplate} beans that are annotated
* {@link LoadBalanced @LoadBalanced} and {@link LoadBalanced @LoadBalanced} when
* Spring Boot application started (after the callback of
* {@link SmartInitializingSingleton} beans or
* {@link RestTemplateCustomizer#customize(RestTemplate) customization})
*/
@EventListener(ApplicationStartedEvent.class)
public void adaptRestTemplates() {
DubboTransportedAttributesResolver attributesResolver = new DubboTransportedAttributesResolver(
environment);
for (Map.Entry<String, RestTemplate> entry : restTemplates.entrySet()) {
String beanName = entry.getKey();
Map<String, Object> dubboTranslatedAttributes = getDubboTranslatedAttributes(
beanName, attributesResolver);
if (!CollectionUtils.isEmpty(dubboTranslatedAttributes)) {
adaptRestTemplate(entry.getValue(), dubboTranslatedAttributes);
}
}
}
/**
* Gets the annotation attributes {@link RestTemplate} bean being annotated
* {@link DubboTransported @DubboTransported}
*
* @param beanName the bean name of {@link LoadBalanced @LoadBalanced}
* {@link RestTemplate}
* @param attributesResolver {@link DubboTransportedAttributesResolver}
* @return non-null {@link Map}
*/
private Map<String, Object> getDubboTranslatedAttributes(String beanName,
DubboTransportedAttributesResolver attributesResolver) {
Map<String, Object> attributes = Collections.emptyMap();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (beanDefinition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
MethodMetadata factoryMethodMetadata = annotatedBeanDefinition
.getFactoryMethodMetadata();
attributes = factoryMethodMetadata != null
? factoryMethodMetadata
.getAnnotationAttributes(DUBBO_TRANSPORTED_CLASS_NAME)
: Collections.emptyMap();
}
return attributesResolver.resolve(attributes);
}
/**
* Adapt the instance of {@link DubboTransporterInterceptor} to the
* {@link LoadBalancerInterceptor} Bean.
*
* @param restTemplate {@link LoadBalanced @LoadBalanced} {@link RestTemplate} Bean
* @param dubboTranslatedAttributes the annotation dubboTranslatedAttributes
* {@link RestTemplate} bean being annotated
* {@link DubboTransported @DubboTransported}
*/
private void adaptRestTemplate(RestTemplate restTemplate,
Map<String, Object> dubboTranslatedAttributes) {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(
restTemplate.getInterceptors());
int index = loadBalancerInterceptorBean == null ? -1
: interceptors.indexOf(loadBalancerInterceptorBean);
index = index < 0 ? 0 : index;
// Add ClientHttpRequestInterceptor instances before loadBalancerInterceptor
interceptors.add(index++, new DubboMetadataInitializerInterceptor(repository));
interceptors.add(index++,
new DubboTransporterInterceptor(repository,
restTemplate.getMessageConverters(), classLoader,
dubboTranslatedAttributes, serviceFactory, contextFactory));
restTemplate.setInterceptors(interceptors);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}

@ -16,20 +16,13 @@
*/
package com.alibaba.cloud.dubbo.autoconfigure;
import java.util.Collection;
import java.util.function.Supplier;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.spring.ServiceBean;
import org.apache.dubbo.config.spring.context.event.ServiceBeanExportedEvent;
import com.alibaba.cloud.dubbo.metadata.DubboProtocolConfigSupplier;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.metadata.resolver.DubboServiceBeanMetadataResolver;
import com.alibaba.cloud.dubbo.metadata.resolver.MetadataResolver;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
import com.alibaba.cloud.dubbo.service.DubboMetadataServiceExporter;
import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy;
import com.alibaba.cloud.dubbo.service.IntrospectiveDubboMetadataService;
import com.alibaba.cloud.dubbo.util.JSONUtils;
import feign.Contract;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -40,8 +33,17 @@ import org.springframework.context.annotation.Import;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import java.util.Collection;
import java.util.function.Supplier;
import com.alibaba.cloud.dubbo.metadata.DubboProtocolConfigSupplier;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.metadata.resolver.DubboServiceBeanMetadataResolver;
import com.alibaba.cloud.dubbo.metadata.resolver.MetadataResolver;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
import com.alibaba.cloud.dubbo.service.DubboMetadataServiceExporter;
import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy;
import com.alibaba.cloud.dubbo.service.IntrospectiveDubboMetadataService;
import com.alibaba.cloud.dubbo.util.JSONUtils;
import feign.Contract;
/**
* Spring Boot Auto-Configuration class for Dubbo Metadata
@ -49,61 +51,62 @@ import java.util.function.Supplier;
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@Import({DubboServiceMetadataRepository.class,
IntrospectiveDubboMetadataService.class,
DubboMetadataServiceExporter.class,
JSONUtils.class})
@Import({ DubboServiceMetadataRepository.class, IntrospectiveDubboMetadataService.class,
DubboMetadataServiceExporter.class, JSONUtils.class })
public class DubboMetadataAutoConfiguration {
@Autowired
private ObjectProvider<DubboServiceMetadataRepository> dubboServiceMetadataRepository;
@Autowired
private MetadataResolver metadataResolver;
@Autowired
private DubboMetadataServiceExporter dubboMetadataConfigServiceExporter;
@Bean
@ConditionalOnMissingBean
public MetadataResolver metadataJsonResolver(ObjectProvider<Contract> contract) {
return new DubboServiceBeanMetadataResolver(contract);
}
@Bean
public Supplier<ProtocolConfig> dubboProtocolConfigSupplier(ObjectProvider<Collection<ProtocolConfig>> protocols) {
return new DubboProtocolConfigSupplier(protocols);
}
@Bean
@ConditionalOnMissingBean
public DubboMetadataServiceProxy dubboMetadataConfigServiceProxy(DubboGenericServiceFactory factory) {
return new DubboMetadataServiceProxy(factory);
}
// Event-Handling
@EventListener(ServiceBeanExportedEvent.class)
public void onServiceBeanExported(ServiceBeanExportedEvent event) {
ServiceBean serviceBean = event.getServiceBean();
publishServiceRestMetadata(serviceBean);
}
@EventListener(ApplicationFailedEvent.class)
public void onApplicationFailed() {
unExportDubboMetadataConfigService();
}
@EventListener(ContextClosedEvent.class)
public void onContextClosed() {
unExportDubboMetadataConfigService();
}
private void publishServiceRestMetadata(ServiceBean serviceBean) {
dubboServiceMetadataRepository.getIfAvailable().publishServiceRestMetadata(metadataResolver.resolveServiceRestMetadata(serviceBean));
}
private void unExportDubboMetadataConfigService() {
dubboMetadataConfigServiceExporter.unexport();
}
@Autowired
private ObjectProvider<DubboServiceMetadataRepository> dubboServiceMetadataRepository;
@Autowired
private MetadataResolver metadataResolver;
@Autowired
private DubboMetadataServiceExporter dubboMetadataConfigServiceExporter;
@Bean
@ConditionalOnMissingBean
public MetadataResolver metadataJsonResolver(ObjectProvider<Contract> contract) {
return new DubboServiceBeanMetadataResolver(contract);
}
@Bean
public Supplier<ProtocolConfig> dubboProtocolConfigSupplier(
ObjectProvider<Collection<ProtocolConfig>> protocols) {
return new DubboProtocolConfigSupplier(protocols);
}
@Bean
@ConditionalOnMissingBean
public DubboMetadataServiceProxy dubboMetadataConfigServiceProxy(
DubboGenericServiceFactory factory) {
return new DubboMetadataServiceProxy(factory);
}
// Event-Handling
@EventListener(ServiceBeanExportedEvent.class)
public void onServiceBeanExported(ServiceBeanExportedEvent event) {
ServiceBean serviceBean = event.getServiceBean();
publishServiceRestMetadata(serviceBean);
}
@EventListener(ApplicationFailedEvent.class)
public void onApplicationFailed() {
unExportDubboMetadataConfigService();
}
@EventListener(ContextClosedEvent.class)
public void onContextClosed() {
unExportDubboMetadataConfigService();
}
private void publishServiceRestMetadata(ServiceBean serviceBean) {
dubboServiceMetadataRepository.getIfAvailable().publishServiceRestMetadata(
metadataResolver.resolveServiceRestMetadata(serviceBean));
}
private void unExportDubboMetadataConfigService() {
dubboMetadataConfigServiceExporter.unexport();
}
}

@ -16,38 +16,39 @@
*/
package com.alibaba.cloud.dubbo.autoconfigure;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.openfeign.TargeterBeanPostProcessor;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration.TARGETER_CLASS_NAME;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration.TARGETER_CLASS_NAME;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.openfeign.TargeterBeanPostProcessor;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
/**
* Dubbo Feign Auto-{@link Configuration Configuration}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@ConditionalOnClass(name = {"feign.Feign", TARGETER_CLASS_NAME})
@AutoConfigureAfter(name = {"org.springframework.cloud.openfeign.FeignAutoConfiguration"})
@ConditionalOnClass(name = { "feign.Feign", TARGETER_CLASS_NAME })
@AutoConfigureAfter(name = {
"org.springframework.cloud.openfeign.FeignAutoConfiguration" })
@Configuration
public class DubboOpenFeignAutoConfiguration {
public static final String TARGETER_CLASS_NAME = "org.springframework.cloud.openfeign.Targeter";
public static final String TARGETER_CLASS_NAME = "org.springframework.cloud.openfeign.Targeter";
@Bean
public TargeterBeanPostProcessor targeterBeanPostProcessor(Environment environment,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboGenericServiceFactory dubboGenericServiceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
return new TargeterBeanPostProcessor(environment, dubboServiceMetadataRepository,
dubboGenericServiceFactory, contextFactory);
}
@Bean
public TargeterBeanPostProcessor targeterBeanPostProcessor(Environment environment,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboGenericServiceFactory dubboGenericServiceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
return new TargeterBeanPostProcessor(environment, dubboServiceMetadataRepository,
dubboGenericServiceFactory, contextFactory);
}
}

@ -16,13 +16,6 @@
*/
package com.alibaba.cloud.dubbo.autoconfigure;
import com.alibaba.cloud.dubbo.env.DubboCloudProperties;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
import com.alibaba.cloud.dubbo.service.parameter.PathVariableServiceParameterResolver;
import com.alibaba.cloud.dubbo.service.parameter.RequestBodyServiceParameterResolver;
import com.alibaba.cloud.dubbo.service.parameter.RequestHeaderServiceParameterResolver;
import com.alibaba.cloud.dubbo.service.parameter.RequestParamServiceParameterResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -33,6 +26,14 @@ import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import com.alibaba.cloud.dubbo.env.DubboCloudProperties;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
import com.alibaba.cloud.dubbo.service.parameter.PathVariableServiceParameterResolver;
import com.alibaba.cloud.dubbo.service.parameter.RequestBodyServiceParameterResolver;
import com.alibaba.cloud.dubbo.service.parameter.RequestHeaderServiceParameterResolver;
import com.alibaba.cloud.dubbo.service.parameter.RequestParamServiceParameterResolver;
/**
* Spring Boot Auto-Configuration class for Dubbo Service
*
@ -42,32 +43,30 @@ import org.springframework.core.env.PropertyResolver;
@EnableConfigurationProperties(DubboCloudProperties.class)
public class DubboServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DubboGenericServiceFactory dubboGenericServiceFactory() {
return new DubboGenericServiceFactory();
}
@Bean
@ConditionalOnMissingBean
public DubboGenericServiceFactory dubboGenericServiceFactory() {
return new DubboGenericServiceFactory();
}
/**
* Build a primary {@link PropertyResolver} bean to {@link Autowired @Autowired}
*
* @param environment {@link Environment}
* @return alias bean for {@link Environment}
*/
@Bean
@Primary
public PropertyResolver primaryPropertyResolver(Environment environment) {
return environment;
}
/**
* Build a primary {@link PropertyResolver} bean to {@link Autowired @Autowired}
*
* @param environment {@link Environment}
* @return alias bean for {@link Environment}
*/
@Bean
@Primary
public PropertyResolver primaryPropertyResolver(Environment environment) {
return environment;
}
@Configuration
@Import(value = {
DubboGenericServiceExecutionContextFactory.class,
RequestParamServiceParameterResolver.class,
RequestBodyServiceParameterResolver.class,
RequestHeaderServiceParameterResolver.class,
PathVariableServiceParameterResolver.class
})
static class ParameterResolversConfiguration {
}
@Configuration
@Import(value = { DubboGenericServiceExecutionContextFactory.class,
RequestParamServiceParameterResolver.class,
RequestBodyServiceParameterResolver.class,
RequestHeaderServiceParameterResolver.class,
PathVariableServiceParameterResolver.class })
static class ParameterResolversConfiguration {
}
}

@ -16,21 +16,24 @@
*/
package com.alibaba.cloud.dubbo.autoconfigure;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.NotifyListener;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration.CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration.NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration.ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.nacos.discovery.NacosDiscoveryClient.hostToServiceInstanceList;
import static org.springframework.util.StringUtils.hasText;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Stream;
import com.alibaba.cloud.dubbo.env.DubboCloudProperties;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.registry.AbstractSpringCloudRegistry;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancesChangedEvent;
import com.alibaba.cloud.dubbo.registry.event.SubscribedServicesChangedEvent;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.discovery.NacosWatch;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.netflix.discovery.CacheRefreshedEvent;
import com.netflix.discovery.shared.Applications;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.listen.Listenable;
import org.apache.curator.framework.listen.ListenerContainer;
@ -38,6 +41,10 @@ import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.NotifyListener;
import org.apache.zookeeper.Watcher;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
@ -67,26 +74,22 @@ import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ReflectionUtils;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration.CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration.NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration.ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.nacos.discovery.NacosDiscoveryClient.hostToServiceInstanceList;
import static org.springframework.util.StringUtils.hasText;
import com.alibaba.cloud.dubbo.env.DubboCloudProperties;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.registry.AbstractSpringCloudRegistry;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancesChangedEvent;
import com.alibaba.cloud.dubbo.registry.event.SubscribedServicesChangedEvent;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.discovery.NacosWatch;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.netflix.discovery.CacheRefreshedEvent;
import com.netflix.discovery.shared.Applications;
/**
* Dubbo Service Discovery Auto {@link Configuration} (after {@link DubboServiceRegistrationAutoConfiguration})
* Dubbo Service Discovery Auto {@link Configuration} (after
* {@link DubboServiceRegistrationAutoConfiguration})
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see DubboServiceRegistrationAutoConfiguration
@ -96,421 +99,454 @@ import static org.springframework.util.StringUtils.hasText;
@Configuration
@ConditionalOnClass(name = "org.springframework.cloud.client.discovery.DiscoveryClient")
@ConditionalOnProperty(name = "spring.cloud.discovery.enabled", matchIfMissing = true)
@AutoConfigureAfter(name = {
EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME,
ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME,
CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME,
NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME
}, value = {DubboServiceRegistrationAutoConfiguration.class})
@AutoConfigureAfter(name = { EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME,
ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME,
CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME,
NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME }, value = {
DubboServiceRegistrationAutoConfiguration.class })
public class DubboServiceDiscoveryAutoConfiguration {
public static final String ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryAutoConfiguration";
public static final String CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.consul.discovery.ConsulDiscoveryClientConfiguration";
public static final String NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME = "com.alibaba.cloud.nacos.NacosDiscoveryAutoConfiguration";
private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ApplicationEventPublisher applicationEventPublisher;
private final DiscoveryClient discoveryClient;
private final AtomicReference<Object> heartbeatState = new AtomicReference<>();
/**
* @see #defaultHeartbeatEventChangePredicate()
*/
private final ObjectProvider<Predicate<HeartbeatEvent>> heartbeatEventChangedPredicate;
public DubboServiceDiscoveryAutoConfiguration(DubboServiceMetadataRepository dubboServiceMetadataRepository,
ApplicationEventPublisher applicationEventPublisher,
DiscoveryClient discoveryClient,
ObjectProvider<Predicate<HeartbeatEvent>> heartbeatEventChangedPredicate) {
this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
this.applicationEventPublisher = applicationEventPublisher;
this.discoveryClient = discoveryClient;
this.heartbeatEventChangedPredicate = heartbeatEventChangedPredicate;
}
/**
* Dispatch a {@link ServiceInstancesChangedEvent}
*
* @param serviceName the name of service
* @param serviceInstances the {@link ServiceInstance instances} of some service
* @see AbstractSpringCloudRegistry#registerServiceInstancesChangedEventListener(URL, NotifyListener)
*/
private void dispatchServiceInstancesChangedEvent(String serviceName, Collection<ServiceInstance> serviceInstances) {
if (!hasText(serviceName) || serviceInstances == null) {
return;
}
ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent(serviceName, serviceInstances);
if (logger.isInfoEnabled()) {
logger.info("The event of the service instances[name : {} , size : {}] change is about to be dispatched",
serviceName, serviceInstances.size());
}
applicationEventPublisher.publishEvent(event);
}
private List<ServiceInstance> getInstances(String serviceName) {
return discoveryClient.getInstances(serviceName);
}
/**
* Dispatch a {@link ServiceInstancesChangedEvent} when the {@link HeartbeatEvent} raised, use for these scenarios:
* <ul>
* <li>Eureka : {@link CloudEurekaClient#onCacheRefreshed()} publishes a {@link HeartbeatEvent} instead of {@link CacheRefreshedEvent}</li>
* <li>Zookeeper : {@link ZookeeperServiceWatch#childEvent(CuratorFramework, TreeCacheEvent)} publishes a
* {@link HeartbeatEvent} when {@link ZookeeperDiscoveryProperties#getRoot() the services' root path} has been changed</li>
* <li>Consul : {@link ConsulCatalogWatch#catalogServicesWatch()} publishes a {@link HeartbeatEvent} when
* <a href="https://www.consul.io/api/features/blocking.html">Consul's Blocking Queries response</a>
* </li>
* <li>Nacos : {@link NacosWatch#nacosServicesWatch()} publishes a {@link HeartbeatEvent} under a
* {@link TaskScheduler} every {@link NacosDiscoveryProperties#getWatchDelay()} milliseconds</li>
* </ul>
* <p>
* In order to reduce the duplicated handling for {@link ServiceInstancesChangedEvent},
* {@link #defaultHeartbeatEventChangePredicate()} method providers the default implementation to detect whether the
* {@link HeartbeatEvent#getValue() state} is changed or not. If and only if changed, the
* {@link #dispatchServiceInstancesChangedEvent(String, Collection)} will be executed.
* <p>
* <b>Note : </b>
* Spring Cloud {@link HeartbeatEvent} has a critical flaw that can't figure out which service was changed precisely,
* it's just used for a notification that the
* {@link DubboServiceMetadataRepository#getSubscribedServices() subscribed services} used to
* {@link NotifyListener#notify(List) notify} the Dubbo consumer cached {@link URL URLs}.
* Because of some {@link DiscoveryClient} implementations have the better and fine-grained the event mechanism for service instances
* change, thus {@link HeartbeatEvent} handle will be ignored in these scenarios:
* <ul>
* <li>Zookeeper : {@link Watcher}, see {@link ZookeeperConfiguration#heartbeatEventChangedPredicate()}</li>
* <li>Nacos : {@link com.alibaba.nacos.api.naming.listener.EventListener} , see {@link NacosConfiguration#heartbeatEventChangedPredicate()}</li>
* </ul>
* <p>
* If the customized {@link DiscoveryClient} also providers the similar mechanism, the implementation could declare
* a Spring Bean of {@link Predicate} of {@link HeartbeatEvent} to override
* {@link #defaultHeartbeatEventChangePredicate() default one}.
*
* @param event the instance of {@link HeartbeatEvent}
* @see HeartbeatEvent
*/
@EventListener(HeartbeatEvent.class)
public void onHeartbeatEvent(HeartbeatEvent event) {
/**
* Try to re-initialize the subscribed services, in order to sense the change of services if
* {@link DubboCloudProperties#getSubscribedServices()} is wildcard that indicates all services should be
* subscribed.
*/
Stream<String> subscribedServices = dubboServiceMetadataRepository.initSubscribedServices();
heartbeatEventChangedPredicate.ifAvailable(predicate -> {
if (predicate.test(event)) {
// Dispatch ServiceInstancesChangedEvent for each service
subscribedServices.forEach(serviceName -> {
List<ServiceInstance> serviceInstances = getInstances(serviceName);
dispatchServiceInstancesChangedEvent(serviceName, serviceInstances);
});
}
});
}
/**
* The default {@link Predicate} implementation of {@link HeartbeatEvent} based on the comparison between
* previous and current {@link HeartbeatEvent#getValue() state value}, the {@link DiscoveryClient}
* implementation may override current.
*
* @return {@link Predicate<HeartbeatEvent>}
* @see EurekaConfiguration#heartbeatEventChangedPredicate()
*/
@Bean
@ConditionalOnMissingBean
public Predicate<HeartbeatEvent> defaultHeartbeatEventChangePredicate() {
return event -> {
Object oldState = heartbeatState.get();
Object newState = event.getValue();
return heartbeatState.compareAndSet(oldState, newState) && !Objects.equals(oldState, newState);
};
}
/**
* Eureka Customized Configuration
*/
@Configuration
@ConditionalOnBean(name = EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME)
public class EurekaConfiguration {
private final AtomicReference<String> appsHashCodeCache = new AtomicReference<>();
/**
* Compare {@link Applications#getAppsHashCode() apps hash code} between last {@link Applications} and current.
*
* @see Applications#getAppsHashCode()
*/
@Bean
public Predicate<HeartbeatEvent> heartbeatEventChangedPredicate() {
return event -> {
String oldAppsHashCode = appsHashCodeCache.get();
CloudEurekaClient cloudEurekaClient = (CloudEurekaClient) event.getSource();
Applications applications = cloudEurekaClient.getApplications();
String appsHashCode = applications.getAppsHashCode();
return appsHashCodeCache.compareAndSet(oldAppsHashCode, appsHashCode) &&
!Objects.equals(oldAppsHashCode, appsHashCode);
};
}
}
/**
* Zookeeper Customized Configuration
*/
@Configuration
@ConditionalOnBean(name = ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME)
@Aspect
public class ZookeeperConfiguration implements ApplicationListener<InstanceRegisteredEvent> {
/**
* The pointcut expression for {@link ZookeeperServiceWatch#childEvent(CuratorFramework, TreeCacheEvent)}
*/
public static final String CHILD_EVENT_POINTCUT_EXPRESSION =
"execution(void org.springframework.cloud.zookeeper.discovery.ZookeeperServiceWatch.childEvent(..)) && args(client,event)";
/**
* The path separator of Zookeeper node
*/
public static final String NODE_PATH_SEPARATOR = "/";
/**
* The path variable name for the name of service
*/
private static final String SERVICE_NAME_PATH_VARIABLE_NAME = "serviceName";
/**
* The path variable name for the id of {@link ServiceInstance service instance}
*/
private static final String SERVICE_INSTANCE_ID_PATH_VARIABLE_NAME = "serviceInstanceId";
private final ZookeeperServiceWatch zookeeperServiceWatch;
private final String rootPath;
private final AntPathMatcher pathMatcher;
/**
* Ant Path pattern for {@link ServiceInstance} :
* <p>
* <p>
* ${{@link #rootPath}}/{serviceName}/{serviceInstanceId}
*
* @see #rootPath
* @see #SERVICE_NAME_PATH_VARIABLE_NAME
* @see #SERVICE_INSTANCE_ID_PATH_VARIABLE_NAME
*/
private final String serviceInstancePathPattern;
/**
* The {@link ThreadLocal} holds the processed service name
*/
private final ThreadLocal<String> processedServiceNameThreadLocal;
ZookeeperConfiguration(
ZookeeperDiscoveryProperties zookeeperDiscoveryProperties,
ZookeeperServiceWatch zookeeperServiceWatch) {
this.zookeeperServiceWatch = zookeeperServiceWatch;
this.rootPath = zookeeperDiscoveryProperties.getRoot();
this.pathMatcher = new AntPathMatcher(NODE_PATH_SEPARATOR);
this.serviceInstancePathPattern = rootPath +
NODE_PATH_SEPARATOR + "{" + SERVICE_NAME_PATH_VARIABLE_NAME + "}" +
NODE_PATH_SEPARATOR + "{" + SERVICE_INSTANCE_ID_PATH_VARIABLE_NAME + "}";
this.processedServiceNameThreadLocal = new ThreadLocal<>();
}
/**
* Zookeeper uses {@link TreeCacheEvent} to trigger {@link #dispatchServiceInstancesChangedEvent(String, Collection)}
* , thus {@link HeartbeatEvent} handle is always ignored
*
* @return <code>false</code> forever
*/
@Bean
public Predicate<HeartbeatEvent> heartbeatEventChangedPredicate() {
return event -> false;
}
/**
* Handle on {@link InstanceRegisteredEvent} after
* {@link ZookeeperServiceWatch#onApplicationEvent(InstanceRegisteredEvent)}
*
* @param event {@link InstanceRegisteredEvent}
* @see #reattachTreeCacheListeners()
*/
@Override
public void onApplicationEvent(InstanceRegisteredEvent event) {
reattachTreeCacheListeners();
}
/**
* Re-attach the {@link TreeCacheListener TreeCacheListeners}
*/
private void reattachTreeCacheListeners() {
TreeCache treeCache = zookeeperServiceWatch.getCache();
Listenable<TreeCacheListener> listenable = treeCache.getListenable();
/**
* All registered TreeCacheListeners except {@link ZookeeperServiceWatch}.
* Usually, "otherListeners" will be empty because Spring Cloud Zookeeper only adds
* "zookeeperServiceWatch" bean as {@link TreeCacheListener}
*/
List<TreeCacheListener> otherListeners = new LinkedList<>();
if (listenable instanceof ListenerContainer) {
ListenerContainer<TreeCacheListener> listenerContainer = (ListenerContainer) listenable;
listenerContainer.forEach(listener -> {
/**
* Even though "listener" is an instance of {@link ZookeeperServiceWatch},
* "zookeeperServiceWatch" bean that was enhanced by AOP is different from the former,
* thus it's required to exclude "listener"
*/
if (!(listener instanceof ZookeeperServiceWatch)) {
otherListeners.add(listener);
}
return null;
});
// remove all TreeCacheListeners temporarily
listenerContainer.clear();
// re-store zookeeperServiceWatch, and make sure it's first one
// now "beforeChildEvent" is available for Spring AOP
listenerContainer.addListener(zookeeperServiceWatch);
// re-store others
otherListeners.forEach(listenerContainer::addListener);
} else {
if (logger.isWarnEnabled()) {
logger.warn("Tell me which version Curator framework current application used? I will do better :D");
}
}
}
/**
* Try to {@link #dispatchServiceInstancesChangedEvent(String, Collection) dispatch}
* {@link ServiceInstancesChangedEvent} before {@link ZookeeperServiceWatch#childEvent(CuratorFramework, TreeCacheEvent)}
* execution if required
*
* @param client {@link CuratorFramework}
* @param event {@link TreeCacheEvent}
*/
@Before(CHILD_EVENT_POINTCUT_EXPRESSION)
public void beforeChildEvent(CuratorFramework client, TreeCacheEvent event) {
if (supportsEventType(event)) {
String serviceName = resolveServiceName(event);
if (hasText(serviceName)) {
dispatchServiceInstancesChangedEvent(serviceName, getInstances(serviceName));
}
}
}
@After(CHILD_EVENT_POINTCUT_EXPRESSION)
public void afterChildEvent(CuratorFramework client, TreeCacheEvent event) {
}
/**
* Resolve the name of service
*
* @param event {@link TreeCacheEvent}
* @return If the Zookeeper's {@link ChildData#getPath() node path} that was notified comes from
* {@link ServiceInstance the service instance}, return it's parent path as the service name,
* or return <code>null</code>
*/
private String resolveServiceName(TreeCacheEvent event) {
ChildData childData = event.getData();
String path = childData.getPath();
if (logger.isDebugEnabled()) {
logger.debug("ZK node[path : {}] event type : {}", path, event.getType());
}
String serviceName = null;
if (pathMatcher.match(serviceInstancePathPattern, path)) {
Map<String, String> variables = pathMatcher.extractUriTemplateVariables(serviceInstancePathPattern, path);
serviceName = variables.get(SERVICE_NAME_PATH_VARIABLE_NAME);
}
return serviceName;
}
/**
* The {@link TreeCacheEvent#getType() event type} is supported or not
*
* @param event {@link TreeCacheEvent}
* @return the rule is same as {@link ZookeeperServiceWatch#childEvent(CuratorFramework, TreeCacheEvent)} method
*/
private boolean supportsEventType(TreeCacheEvent event) {
TreeCacheEvent.Type eventType = event.getType();
return eventType.equals(TreeCacheEvent.Type.NODE_ADDED)
|| eventType.equals(TreeCacheEvent.Type.NODE_REMOVED)
|| eventType.equals(TreeCacheEvent.Type.NODE_UPDATED);
}
}
/**
* Consul Customized Configuration
*/
@Configuration
@ConditionalOnBean(name = CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME)
class ConsulConfiguration {
}
/**
* Nacos Customized Configuration
*/
@Configuration
@ConditionalOnBean(name = NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME)
class NacosConfiguration {
private final NamingService namingService;
/**
* the set of services is listening
*/
private final Set<String> listeningServices;
NacosConfiguration(NacosDiscoveryProperties nacosDiscoveryProperties) {
this.namingService = nacosDiscoveryProperties.namingServiceInstance();
this.listeningServices = new ConcurrentSkipListSet<>();
}
/**
* Nacos uses {@link EventListener} to trigger {@link #dispatchServiceInstancesChangedEvent(String, Collection)}
* , thus {@link HeartbeatEvent} handle is always ignored
*
* @return <code>false</code> forever
*/
@Bean
public Predicate<HeartbeatEvent> heartbeatEventChangedPredicate() {
return event -> false;
}
@EventListener(SubscribedServicesChangedEvent.class)
public void onSubscribedServicesChangedEvent(SubscribedServicesChangedEvent event) throws Exception {
// subscribe EventListener for each service
event.getNewSubscribedServices().forEach(this::subscribeEventListener);
}
private void subscribeEventListener(String serviceName) {
if (listeningServices.add(serviceName)) {
try {
namingService.subscribe(serviceName, event -> {
if (event instanceof NamingEvent) {
NamingEvent namingEvent = (NamingEvent) event;
List<ServiceInstance> serviceInstances = hostToServiceInstanceList(namingEvent.getInstances(), serviceName);
dispatchServiceInstancesChangedEvent(serviceName, serviceInstances);
}
});
} catch (NacosException e) {
ReflectionUtils.rethrowRuntimeException(e);
}
}
}
}
public static final String ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryAutoConfiguration";
public static final String CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.consul.discovery.ConsulDiscoveryClientConfiguration";
public static final String NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME = "com.alibaba.cloud.nacos.NacosDiscoveryAutoConfiguration";
private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ApplicationEventPublisher applicationEventPublisher;
private final DiscoveryClient discoveryClient;
private final AtomicReference<Object> heartbeatState = new AtomicReference<>();
/**
* @see #defaultHeartbeatEventChangePredicate()
*/
private final ObjectProvider<Predicate<HeartbeatEvent>> heartbeatEventChangedPredicate;
public DubboServiceDiscoveryAutoConfiguration(
DubboServiceMetadataRepository dubboServiceMetadataRepository,
ApplicationEventPublisher applicationEventPublisher,
DiscoveryClient discoveryClient,
ObjectProvider<Predicate<HeartbeatEvent>> heartbeatEventChangedPredicate) {
this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
this.applicationEventPublisher = applicationEventPublisher;
this.discoveryClient = discoveryClient;
this.heartbeatEventChangedPredicate = heartbeatEventChangedPredicate;
}
/**
* Dispatch a {@link ServiceInstancesChangedEvent}
*
* @param serviceName the name of service
* @param serviceInstances the {@link ServiceInstance instances} of some service
* @see AbstractSpringCloudRegistry#registerServiceInstancesChangedEventListener(URL,
* NotifyListener)
*/
private void dispatchServiceInstancesChangedEvent(String serviceName,
Collection<ServiceInstance> serviceInstances) {
if (!hasText(serviceName) || serviceInstances == null) {
return;
}
ServiceInstancesChangedEvent event = new ServiceInstancesChangedEvent(serviceName,
serviceInstances);
if (logger.isInfoEnabled()) {
logger.info(
"The event of the service instances[name : {} , size : {}] change is about to be dispatched",
serviceName, serviceInstances.size());
}
applicationEventPublisher.publishEvent(event);
}
private List<ServiceInstance> getInstances(String serviceName) {
return discoveryClient.getInstances(serviceName);
}
/**
* Dispatch a {@link ServiceInstancesChangedEvent} when the {@link HeartbeatEvent}
* raised, use for these scenarios:
* <ul>
* <li>Eureka : {@link CloudEurekaClient#onCacheRefreshed()} publishes a
* {@link HeartbeatEvent} instead of {@link CacheRefreshedEvent}</li>
* <li>Zookeeper :
* {@link ZookeeperServiceWatch#childEvent(CuratorFramework, TreeCacheEvent)}
* publishes a {@link HeartbeatEvent} when
* {@link ZookeeperDiscoveryProperties#getRoot() the services' root path} has been
* changed</li>
* <li>Consul : {@link ConsulCatalogWatch#catalogServicesWatch()} publishes a
* {@link HeartbeatEvent} when
* <a href="https://www.consul.io/api/features/blocking.html">Consul's Blocking
* Queries response</a></li>
* <li>Nacos : {@link NacosWatch#nacosServicesWatch()} publishes a
* {@link HeartbeatEvent} under a {@link TaskScheduler} every
* {@link NacosDiscoveryProperties#getWatchDelay()} milliseconds</li>
* </ul>
* <p>
* In order to reduce the duplicated handling for
* {@link ServiceInstancesChangedEvent},
* {@link #defaultHeartbeatEventChangePredicate()} method providers the default
* implementation to detect whether the {@link HeartbeatEvent#getValue() state} is
* changed or not. If and only if changed, the
* {@link #dispatchServiceInstancesChangedEvent(String, Collection)} will be executed.
* <p>
* <b>Note : </b> Spring Cloud {@link HeartbeatEvent} has a critical flaw that can't
* figure out which service was changed precisely, it's just used for a notification
* that the {@link DubboServiceMetadataRepository#getSubscribedServices() subscribed
* services} used to {@link NotifyListener#notify(List) notify} the Dubbo consumer
* cached {@link URL URLs}. Because of some {@link DiscoveryClient} implementations
* have the better and fine-grained the event mechanism for service instances change,
* thus {@link HeartbeatEvent} handle will be ignored in these scenarios:
* <ul>
* <li>Zookeeper : {@link Watcher}, see
* {@link ZookeeperConfiguration#heartbeatEventChangedPredicate()}</li>
* <li>Nacos : {@link com.alibaba.nacos.api.naming.listener.EventListener} , see
* {@link NacosConfiguration#heartbeatEventChangedPredicate()}</li>
* </ul>
* <p>
* If the customized {@link DiscoveryClient} also providers the similar mechanism, the
* implementation could declare a Spring Bean of {@link Predicate} of
* {@link HeartbeatEvent} to override {@link #defaultHeartbeatEventChangePredicate()
* default one}.
*
* @param event the instance of {@link HeartbeatEvent}
* @see HeartbeatEvent
*/
@EventListener(HeartbeatEvent.class)
public void onHeartbeatEvent(HeartbeatEvent event) {
/**
* Try to re-initialize the subscribed services, in order to sense the change of
* services if {@link DubboCloudProperties#getSubscribedServices()} is wildcard
* that indicates all services should be subscribed.
*/
Stream<String> subscribedServices = dubboServiceMetadataRepository
.initSubscribedServices();
heartbeatEventChangedPredicate.ifAvailable(predicate -> {
if (predicate.test(event)) {
// Dispatch ServiceInstancesChangedEvent for each service
subscribedServices.forEach(serviceName -> {
List<ServiceInstance> serviceInstances = getInstances(serviceName);
dispatchServiceInstancesChangedEvent(serviceName, serviceInstances);
});
}
});
}
/**
* The default {@link Predicate} implementation of {@link HeartbeatEvent} based on the
* comparison between previous and current {@link HeartbeatEvent#getValue() state
* value}, the {@link DiscoveryClient} implementation may override current.
*
* @return {@link Predicate<HeartbeatEvent>}
* @see EurekaConfiguration#heartbeatEventChangedPredicate()
*/
@Bean
@ConditionalOnMissingBean
public Predicate<HeartbeatEvent> defaultHeartbeatEventChangePredicate() {
return event -> {
Object oldState = heartbeatState.get();
Object newState = event.getValue();
return heartbeatState.compareAndSet(oldState, newState)
&& !Objects.equals(oldState, newState);
};
}
/**
* Eureka Customized Configuration
*/
@Configuration
@ConditionalOnBean(name = EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME)
public class EurekaConfiguration {
private final AtomicReference<String> appsHashCodeCache = new AtomicReference<>();
/**
* Compare {@link Applications#getAppsHashCode() apps hash code} between last
* {@link Applications} and current.
*
* @see Applications#getAppsHashCode()
*/
@Bean
public Predicate<HeartbeatEvent> heartbeatEventChangedPredicate() {
return event -> {
String oldAppsHashCode = appsHashCodeCache.get();
CloudEurekaClient cloudEurekaClient = (CloudEurekaClient) event
.getSource();
Applications applications = cloudEurekaClient.getApplications();
String appsHashCode = applications.getAppsHashCode();
return appsHashCodeCache.compareAndSet(oldAppsHashCode, appsHashCode)
&& !Objects.equals(oldAppsHashCode, appsHashCode);
};
}
}
/**
* Zookeeper Customized Configuration
*/
@Configuration
@ConditionalOnBean(name = ZOOKEEPER_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME)
@Aspect
public class ZookeeperConfiguration
implements ApplicationListener<InstanceRegisteredEvent> {
/**
* The pointcut expression for
* {@link ZookeeperServiceWatch#childEvent(CuratorFramework, TreeCacheEvent)}
*/
public static final String CHILD_EVENT_POINTCUT_EXPRESSION = "execution(void org.springframework.cloud.zookeeper.discovery.ZookeeperServiceWatch.childEvent(..)) && args(client,event)";
/**
* The path separator of Zookeeper node
*/
public static final String NODE_PATH_SEPARATOR = "/";
/**
* The path variable name for the name of service
*/
private static final String SERVICE_NAME_PATH_VARIABLE_NAME = "serviceName";
/**
* The path variable name for the id of {@link ServiceInstance service instance}
*/
private static final String SERVICE_INSTANCE_ID_PATH_VARIABLE_NAME = "serviceInstanceId";
private final ZookeeperServiceWatch zookeeperServiceWatch;
private final String rootPath;
private final AntPathMatcher pathMatcher;
/**
* Ant Path pattern for {@link ServiceInstance} :
* <p>
* <p>
* ${{@link #rootPath}}/{serviceName}/{serviceInstanceId}
*
* @see #rootPath
* @see #SERVICE_NAME_PATH_VARIABLE_NAME
* @see #SERVICE_INSTANCE_ID_PATH_VARIABLE_NAME
*/
private final String serviceInstancePathPattern;
/**
* The {@link ThreadLocal} holds the processed service name
*/
private final ThreadLocal<String> processedServiceNameThreadLocal;
ZookeeperConfiguration(ZookeeperDiscoveryProperties zookeeperDiscoveryProperties,
ZookeeperServiceWatch zookeeperServiceWatch) {
this.zookeeperServiceWatch = zookeeperServiceWatch;
this.rootPath = zookeeperDiscoveryProperties.getRoot();
this.pathMatcher = new AntPathMatcher(NODE_PATH_SEPARATOR);
this.serviceInstancePathPattern = rootPath + NODE_PATH_SEPARATOR + "{"
+ SERVICE_NAME_PATH_VARIABLE_NAME + "}" + NODE_PATH_SEPARATOR + "{"
+ SERVICE_INSTANCE_ID_PATH_VARIABLE_NAME + "}";
this.processedServiceNameThreadLocal = new ThreadLocal<>();
}
/**
* Zookeeper uses {@link TreeCacheEvent} to trigger
* {@link #dispatchServiceInstancesChangedEvent(String, Collection)} , thus
* {@link HeartbeatEvent} handle is always ignored
*
* @return <code>false</code> forever
*/
@Bean
public Predicate<HeartbeatEvent> heartbeatEventChangedPredicate() {
return event -> false;
}
/**
* Handle on {@link InstanceRegisteredEvent} after
* {@link ZookeeperServiceWatch#onApplicationEvent(InstanceRegisteredEvent)}
*
* @param event {@link InstanceRegisteredEvent}
* @see #reattachTreeCacheListeners()
*/
@Override
public void onApplicationEvent(InstanceRegisteredEvent event) {
reattachTreeCacheListeners();
}
/**
* Re-attach the {@link TreeCacheListener TreeCacheListeners}
*/
private void reattachTreeCacheListeners() {
TreeCache treeCache = zookeeperServiceWatch.getCache();
Listenable<TreeCacheListener> listenable = treeCache.getListenable();
/**
* All registered TreeCacheListeners except {@link ZookeeperServiceWatch}.
* Usually, "otherListeners" will be empty because Spring Cloud Zookeeper only
* adds "zookeeperServiceWatch" bean as {@link TreeCacheListener}
*/
List<TreeCacheListener> otherListeners = new LinkedList<>();
if (listenable instanceof ListenerContainer) {
ListenerContainer<TreeCacheListener> listenerContainer = (ListenerContainer) listenable;
listenerContainer.forEach(listener -> {
/**
* Even though "listener" is an instance of
* {@link ZookeeperServiceWatch}, "zookeeperServiceWatch" bean that
* was enhanced by AOP is different from the former, thus it's
* required to exclude "listener"
*/
if (!(listener instanceof ZookeeperServiceWatch)) {
otherListeners.add(listener);
}
return null;
});
// remove all TreeCacheListeners temporarily
listenerContainer.clear();
// re-store zookeeperServiceWatch, and make sure it's first one
// now "beforeChildEvent" is available for Spring AOP
listenerContainer.addListener(zookeeperServiceWatch);
// re-store others
otherListeners.forEach(listenerContainer::addListener);
}
else {
if (logger.isWarnEnabled()) {
logger.warn(
"Tell me which version Curator framework current application used? I will do better :D");
}
}
}
/**
* Try to {@link #dispatchServiceInstancesChangedEvent(String, Collection)
* dispatch} {@link ServiceInstancesChangedEvent} before
* {@link ZookeeperServiceWatch#childEvent(CuratorFramework, TreeCacheEvent)}
* execution if required
*
* @param client {@link CuratorFramework}
* @param event {@link TreeCacheEvent}
*/
@Before(CHILD_EVENT_POINTCUT_EXPRESSION)
public void beforeChildEvent(CuratorFramework client, TreeCacheEvent event) {
if (supportsEventType(event)) {
String serviceName = resolveServiceName(event);
if (hasText(serviceName)) {
dispatchServiceInstancesChangedEvent(serviceName,
getInstances(serviceName));
}
}
}
@After(CHILD_EVENT_POINTCUT_EXPRESSION)
public void afterChildEvent(CuratorFramework client, TreeCacheEvent event) {
}
/**
* Resolve the name of service
*
* @param event {@link TreeCacheEvent}
* @return If the Zookeeper's {@link ChildData#getPath() node path} that was
* notified comes from {@link ServiceInstance the service instance}, return it's
* parent path as the service name, or return <code>null</code>
*/
private String resolveServiceName(TreeCacheEvent event) {
ChildData childData = event.getData();
String path = childData.getPath();
if (logger.isDebugEnabled()) {
logger.debug("ZK node[path : {}] event type : {}", path, event.getType());
}
String serviceName = null;
if (pathMatcher.match(serviceInstancePathPattern, path)) {
Map<String, String> variables = pathMatcher
.extractUriTemplateVariables(serviceInstancePathPattern, path);
serviceName = variables.get(SERVICE_NAME_PATH_VARIABLE_NAME);
}
return serviceName;
}
/**
* The {@link TreeCacheEvent#getType() event type} is supported or not
*
* @param event {@link TreeCacheEvent}
* @return the rule is same as
* {@link ZookeeperServiceWatch#childEvent(CuratorFramework, TreeCacheEvent)}
* method
*/
private boolean supportsEventType(TreeCacheEvent event) {
TreeCacheEvent.Type eventType = event.getType();
return eventType.equals(TreeCacheEvent.Type.NODE_ADDED)
|| eventType.equals(TreeCacheEvent.Type.NODE_REMOVED)
|| eventType.equals(TreeCacheEvent.Type.NODE_UPDATED);
}
}
/**
* Consul Customized Configuration
*/
@Configuration
@ConditionalOnBean(name = CONSUL_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME)
class ConsulConfiguration {
}
/**
* Nacos Customized Configuration
*/
@Configuration
@ConditionalOnBean(name = NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASS_NAME)
class NacosConfiguration {
private final NamingService namingService;
/**
* the set of services is listening
*/
private final Set<String> listeningServices;
NacosConfiguration(NacosDiscoveryProperties nacosDiscoveryProperties) {
this.namingService = nacosDiscoveryProperties.namingServiceInstance();
this.listeningServices = new ConcurrentSkipListSet<>();
}
/**
* Nacos uses {@link EventListener} to trigger
* {@link #dispatchServiceInstancesChangedEvent(String, Collection)} , thus
* {@link HeartbeatEvent} handle is always ignored
*
* @return <code>false</code> forever
*/
@Bean
public Predicate<HeartbeatEvent> heartbeatEventChangedPredicate() {
return event -> false;
}
@EventListener(SubscribedServicesChangedEvent.class)
public void onSubscribedServicesChangedEvent(SubscribedServicesChangedEvent event)
throws Exception {
// subscribe EventListener for each service
event.getNewSubscribedServices().forEach(this::subscribeEventListener);
}
private void subscribeEventListener(String serviceName) {
if (listeningServices.add(serviceName)) {
try {
namingService.subscribe(serviceName, event -> {
if (event instanceof NamingEvent) {
NamingEvent namingEvent = (NamingEvent) event;
List<ServiceInstance> serviceInstances = hostToServiceInstanceList(
namingEvent.getInstances(), serviceName);
dispatchServiceInstancesChangedEvent(serviceName,
serviceInstances);
}
});
}
catch (NacosException e) {
ReflectionUtils.rethrowRuntimeException(e);
}
}
}
}
}

@ -16,15 +16,19 @@
*/
package com.alibaba.cloud.dubbo.autoconfigure;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory.ADDRESS;
import static com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory.PROTOCOL;
import static org.springframework.util.ObjectUtils.isEmpty;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.ServiceBean;
import com.alibaba.cloud.dubbo.autoconfigure.condition.MissingSpringCloudRegistryConfigPropertyCondition;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.registry.DubboServiceRegistrationEventPublishingAspect;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancePreRegisteredEvent;
import com.ecwid.consul.v1.agent.model.NewService;
import com.netflix.appinfo.InstanceInfo;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -49,15 +53,12 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.EventListener;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory.ADDRESS;
import static com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory.PROTOCOL;
import static org.springframework.util.ObjectUtils.isEmpty;
import com.alibaba.cloud.dubbo.autoconfigure.condition.MissingSpringCloudRegistryConfigPropertyCondition;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.registry.DubboServiceRegistrationEventPublishingAspect;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancePreRegisteredEvent;
import com.ecwid.consul.v1.agent.model.NewService;
import com.netflix.appinfo.InstanceInfo;
/**
* Dubbo Service Registration Auto-{@link Configuration}
@ -65,125 +66,126 @@ import static org.springframework.util.ObjectUtils.isEmpty;
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@Import({DubboServiceRegistrationEventPublishingAspect.class})
@Import({ DubboServiceRegistrationEventPublishingAspect.class })
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter(name = {
EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME,
CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME,
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration"
}, value = {
DubboMetadataAutoConfiguration.class
})
@AutoConfigureAfter(name = { EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME,
CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME,
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" }, value = {
DubboMetadataAutoConfiguration.class })
public class DubboServiceRegistrationAutoConfiguration {
public static final String EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME =
"org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration";
public static final String CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME =
"org.springframework.cloud.consul.serviceregistry.ConsulAutoServiceRegistrationAutoConfiguration";
public static final String CONSUL_AUTO_SERVICE_AUTO_REGISTRATION_CLASS_NAME =
"org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration";
public static final String ZOOKEEPER_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME =
"org.springframework.cloud.zookeeper.serviceregistry.ZookeeperAutoServiceRegistrationAutoConfiguration";
private static final Logger logger = LoggerFactory.getLogger(DubboServiceRegistrationAutoConfiguration.class);
@Autowired
private DubboServiceMetadataRepository dubboServiceMetadataRepository;
@Bean
@Conditional(value = {
MissingSpringCloudRegistryConfigPropertyCondition.class
})
public RegistryConfig defaultSpringCloudRegistryConfig() {
return new RegistryConfig(ADDRESS, PROTOCOL);
}
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) {
Registration registration = event.getSource();
attachDubboMetadataServiceMetadata(registration);
}
private void attachDubboMetadataServiceMetadata(Registration registration) {
if (registration == null) {
return;
}
synchronized (registration) {
Map<String, String> metadata = registration.getMetadata();
attachDubboMetadataServiceMetadata(metadata);
}
}
private void attachDubboMetadataServiceMetadata(Map<String, String> metadata) {
Map<String, String> serviceMetadata = dubboServiceMetadataRepository.getDubboMetadataServiceMetadata();
if (!isEmpty(serviceMetadata)) {
metadata.putAll(serviceMetadata);
}
}
@Configuration
@ConditionalOnBean(name = EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME)
@Aspect
class EurekaConfiguration implements SmartInitializingSingleton {
@Autowired
private ObjectProvider<Collection<ServiceBean>> serviceBeans;
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) {
Registration registration = event.getSource();
EurekaRegistration eurekaRegistration = EurekaRegistration.class.cast(registration);
InstanceInfo instanceInfo = eurekaRegistration.getApplicationInfoManager().getInfo();
attachDubboMetadataServiceMetadata(instanceInfo.getMetadata());
}
/**
* {@link EurekaServiceRegistry} will register current {@link ServiceInstance service instance} on
* {@link EurekaAutoServiceRegistration#start()} execution(in {@link SmartLifecycle#start() start phase}),
* thus this method must {@link ServiceBean#export() export} all {@link ServiceBean ServiceBeans} in advance.
*/
@Override
public void afterSingletonsInstantiated() {
Collection<ServiceBean> serviceBeans = this.serviceBeans.getIfAvailable();
if (!isEmpty(serviceBeans)) {
serviceBeans.forEach(ServiceBean::export);
}
}
}
@Configuration
@ConditionalOnBean(name = CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME)
@AutoConfigureOrder
class ConsulConfiguration {
/**
* Handle the pre-registered event of {@link ServiceInstance} for Consul
*
* @param event {@link ServiceInstancePreRegisteredEvent}
*/
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) {
Registration registration = event.getSource();
Class<?> registrationClass = AopUtils.getTargetClass(registration);
String registrationClassName = registrationClass.getName();
if (CONSUL_AUTO_SERVICE_AUTO_REGISTRATION_CLASS_NAME.equalsIgnoreCase(registrationClassName)) {
ConsulRegistration consulRegistration = (ConsulRegistration) registration;
attachURLsIntoMetadata(consulRegistration);
}
}
private void attachURLsIntoMetadata(ConsulRegistration consulRegistration) {
NewService newService = consulRegistration.getService();
Map<String, String> serviceMetadata = dubboServiceMetadataRepository.getDubboMetadataServiceMetadata();
if (!isEmpty(serviceMetadata)) {
List<String> tags = newService.getTags();
for (Map.Entry<String, String> entry : serviceMetadata.entrySet()) {
tags.add(entry.getKey() + "=" + entry.getValue());
}
}
}
}
public static final String EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration";
public static final String CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.consul.serviceregistry.ConsulAutoServiceRegistrationAutoConfiguration";
public static final String CONSUL_AUTO_SERVICE_AUTO_REGISTRATION_CLASS_NAME = "org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration";
public static final String ZOOKEEPER_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.zookeeper.serviceregistry.ZookeeperAutoServiceRegistrationAutoConfiguration";
private static final Logger logger = LoggerFactory
.getLogger(DubboServiceRegistrationAutoConfiguration.class);
@Autowired
private DubboServiceMetadataRepository dubboServiceMetadataRepository;
@Bean
@Conditional(value = { MissingSpringCloudRegistryConfigPropertyCondition.class })
public RegistryConfig defaultSpringCloudRegistryConfig() {
return new RegistryConfig(ADDRESS, PROTOCOL);
}
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) {
Registration registration = event.getSource();
attachDubboMetadataServiceMetadata(registration);
}
private void attachDubboMetadataServiceMetadata(Registration registration) {
if (registration == null) {
return;
}
synchronized (registration) {
Map<String, String> metadata = registration.getMetadata();
attachDubboMetadataServiceMetadata(metadata);
}
}
private void attachDubboMetadataServiceMetadata(Map<String, String> metadata) {
Map<String, String> serviceMetadata = dubboServiceMetadataRepository
.getDubboMetadataServiceMetadata();
if (!isEmpty(serviceMetadata)) {
metadata.putAll(serviceMetadata);
}
}
@Configuration
@ConditionalOnBean(name = EUREKA_CLIENT_AUTO_CONFIGURATION_CLASS_NAME)
@Aspect
class EurekaConfiguration implements SmartInitializingSingleton {
@Autowired
private ObjectProvider<Collection<ServiceBean>> serviceBeans;
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(
ServiceInstancePreRegisteredEvent event) {
Registration registration = event.getSource();
EurekaRegistration eurekaRegistration = EurekaRegistration.class
.cast(registration);
InstanceInfo instanceInfo = eurekaRegistration.getApplicationInfoManager()
.getInfo();
attachDubboMetadataServiceMetadata(instanceInfo.getMetadata());
}
/**
* {@link EurekaServiceRegistry} will register current {@link ServiceInstance
* service instance} on {@link EurekaAutoServiceRegistration#start()} execution(in
* {@link SmartLifecycle#start() start phase}), thus this method must
* {@link ServiceBean#export() export} all {@link ServiceBean ServiceBeans} in
* advance.
*/
@Override
public void afterSingletonsInstantiated() {
Collection<ServiceBean> serviceBeans = this.serviceBeans.getIfAvailable();
if (!isEmpty(serviceBeans)) {
serviceBeans.forEach(ServiceBean::export);
}
}
}
@Configuration
@ConditionalOnBean(name = CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME)
@AutoConfigureOrder
class ConsulConfiguration {
/**
* Handle the pre-registered event of {@link ServiceInstance} for Consul
*
* @param event {@link ServiceInstancePreRegisteredEvent}
*/
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(
ServiceInstancePreRegisteredEvent event) {
Registration registration = event.getSource();
Class<?> registrationClass = AopUtils.getTargetClass(registration);
String registrationClassName = registrationClass.getName();
if (CONSUL_AUTO_SERVICE_AUTO_REGISTRATION_CLASS_NAME
.equalsIgnoreCase(registrationClassName)) {
ConsulRegistration consulRegistration = (ConsulRegistration) registration;
attachURLsIntoMetadata(consulRegistration);
}
}
private void attachURLsIntoMetadata(ConsulRegistration consulRegistration) {
NewService newService = consulRegistration.getService();
Map<String, String> serviceMetadata = dubboServiceMetadataRepository
.getDubboMetadataServiceMetadata();
if (!isEmpty(serviceMetadata)) {
List<String> tags = newService.getTags();
for (Map.Entry<String, String> entry : serviceMetadata.entrySet()) {
tags.add(entry.getKey() + "=" + entry.getValue());
}
}
}
}
}

@ -16,12 +16,14 @@
*/
package com.alibaba.cloud.dubbo.autoconfigure;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.ZOOKEEPER_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME;
import java.util.List;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.spring.ServiceBean;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancePreRegisteredEvent;
import com.ecwid.consul.v1.agent.model.NewService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@ -41,10 +43,9 @@ import org.springframework.cloud.zookeeper.serviceregistry.ServiceInstanceRegist
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import java.util.List;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAutoConfiguration.ZOOKEEPER_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancePreRegisteredEvent;
import com.ecwid.consul.v1.agent.model.NewService;
/**
* Dubbo Service Registration Auto-{@link Configuration} for Non-Web application
@ -58,111 +59,113 @@ import static com.alibaba.cloud.dubbo.autoconfigure.DubboServiceRegistrationAuto
@Aspect
public class DubboServiceRegistrationNonWebApplicationAutoConfiguration {
private static final String REST_PROTOCOL = "rest";
@Autowired
private ServiceRegistry serviceRegistry;
@Autowired
private Registration registration;
private volatile Integer serverPort = null;
private volatile boolean registered = false;
@Autowired
private DubboServiceMetadataRepository repository;
@Around("execution(* org.springframework.cloud.client.serviceregistry.Registration.getPort())")
public Object getPort(ProceedingJoinPoint pjp) throws Throwable {
return serverPort != null ? serverPort : pjp.proceed();
}
@EventListener(ApplicationStartedEvent.class)
public void onApplicationStarted() {
setServerPort();
register();
}
private void register() {
if (registered) {
return;
}
serviceRegistry.register(registration);
registered = true;
}
/**
* Set web port from {@link ServiceBean#getExportedUrls() exported URLs} if "rest" protocol is present.
*/
private void setServerPort() {
if (serverPort == null) {
for (List<URL> urls : repository.getAllExportedUrls().values()) {
urls.stream()
.filter(url -> REST_PROTOCOL.equalsIgnoreCase(url.getProtocol()))
.findFirst()
.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();
});
}
}
}
}
@Configuration
@ConditionalOnBean(name = ZOOKEEPER_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME)
class ZookeeperConfiguration implements SmartInitializingSingleton {
@Autowired
private ServiceInstanceRegistration registration;
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) {
registration.setPort(serverPort);
}
@Override
public void afterSingletonsInstantiated() {
// invoke getServiceInstance() method to trigger the ServiceInstance building before register
registration.getServiceInstance();
}
}
@Configuration
@ConditionalOnBean(name = CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME)
class ConsulConfiguration {
/**
* Handle the pre-registered event of {@link ServiceInstance} for Consul
*
* @param event {@link ServiceInstancePreRegisteredEvent}
*/
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(ServiceInstancePreRegisteredEvent event) {
Registration registration = event.getSource();
ConsulAutoRegistration consulRegistration = (ConsulAutoRegistration) registration;
setPort(consulRegistration);
}
/**
* Set port on Non-Web Application
*
* @param consulRegistration {@link ConsulRegistration}
*/
private void setPort(ConsulAutoRegistration consulRegistration) {
int port = consulRegistration.getPort();
NewService newService = consulRegistration.getService();
if (newService.getPort() == null) {
newService.setPort(port);
}
}
}
private static final String REST_PROTOCOL = "rest";
@Autowired
private ServiceRegistry serviceRegistry;
@Autowired
private Registration registration;
private volatile Integer serverPort = null;
private volatile boolean registered = false;
@Autowired
private DubboServiceMetadataRepository repository;
@Around("execution(* org.springframework.cloud.client.serviceregistry.Registration.getPort())")
public Object getPort(ProceedingJoinPoint pjp) throws Throwable {
return serverPort != null ? serverPort : pjp.proceed();
}
@EventListener(ApplicationStartedEvent.class)
public void onApplicationStarted() {
setServerPort();
register();
}
private void register() {
if (registered) {
return;
}
serviceRegistry.register(registration);
registered = true;
}
/**
* Set web port from {@link ServiceBean#getExportedUrls() exported URLs} if "rest"
* protocol is present.
*/
private void setServerPort() {
if (serverPort == null) {
for (List<URL> urls : repository.getAllExportedUrls().values()) {
urls.stream()
.filter(url -> REST_PROTOCOL.equalsIgnoreCase(url.getProtocol()))
.findFirst().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();
});
}
}
}
}
@Configuration
@ConditionalOnBean(name = ZOOKEEPER_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME)
class ZookeeperConfiguration implements SmartInitializingSingleton {
@Autowired
private ServiceInstanceRegistration registration;
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(
ServiceInstancePreRegisteredEvent event) {
registration.setPort(serverPort);
}
@Override
public void afterSingletonsInstantiated() {
// invoke getServiceInstance() method to trigger the ServiceInstance building
// before register
registration.getServiceInstance();
}
}
@Configuration
@ConditionalOnBean(name = CONSUL_AUTO_SERVICE_AUTO_CONFIGURATION_CLASS_NAME)
class ConsulConfiguration {
/**
* Handle the pre-registered event of {@link ServiceInstance} for Consul
*
* @param event {@link ServiceInstancePreRegisteredEvent}
*/
@EventListener(ServiceInstancePreRegisteredEvent.class)
public void onServiceInstancePreRegistered(
ServiceInstancePreRegisteredEvent event) {
Registration registration = event.getSource();
ConsulAutoRegistration consulRegistration = (ConsulAutoRegistration) registration;
setPort(consulRegistration);
}
/**
* Set port on Non-Web Application
*
* @param consulRegistration {@link ConsulRegistration}
*/
private void setPort(ConsulAutoRegistration consulRegistration) {
int port = consulRegistration.getPort();
NewService newService = consulRegistration.getService();
if (newService.getPort() == null) {
newService.setPort(port);
}
}
}
}

@ -16,7 +16,11 @@
*/
package com.alibaba.cloud.dubbo.autoconfigure.condition;
import com.alibaba.cloud.dubbo.registry.SpringCloudRegistry;
import static com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory.PROTOCOL;
import static org.apache.dubbo.config.spring.util.PropertySourcesUtils.getSubProperties;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.Condition;
@ -25,10 +29,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.StringUtils;
import java.util.Map;
import static com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory.PROTOCOL;
import static org.apache.dubbo.config.spring.util.PropertySourcesUtils.getSubProperties;
import com.alibaba.cloud.dubbo.registry.SpringCloudRegistry;
/**
* Missing {@link SpringCloudRegistry} Property {@link Condition}
@ -37,35 +38,43 @@ import static org.apache.dubbo.config.spring.util.PropertySourcesUtils.getSubPro
* @see SpringCloudRegistry
* @see Condition
*/
public class MissingSpringCloudRegistryConfigPropertyCondition extends SpringBootCondition {
public class MissingSpringCloudRegistryConfigPropertyCondition
extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConfigurableEnvironment environment = (ConfigurableEnvironment) context
.getEnvironment();
String protocol = environment.getProperty("dubbo.registry.protocol");
String protocol = environment.getProperty("dubbo.registry.protocol");
if (PROTOCOL.equals(protocol)) {
return ConditionOutcome.noMatch("'spring-cloud' protocol was found from 'dubbo.registry.protocol'");
}
if (PROTOCOL.equals(protocol)) {
return ConditionOutcome.noMatch(
"'spring-cloud' protocol was found from 'dubbo.registry.protocol'");
}
String address = environment.getProperty("dubbo.registry.address");
String address = environment.getProperty("dubbo.registry.address");
if (StringUtils.startsWithIgnoreCase(address, PROTOCOL)) {
return ConditionOutcome.noMatch("'spring-cloud' protocol was found from 'dubbo.registry.address'");
}
if (StringUtils.startsWithIgnoreCase(address, PROTOCOL)) {
return ConditionOutcome.noMatch(
"'spring-cloud' protocol was found from 'dubbo.registry.address'");
}
Map<String, Object> properties = getSubProperties(environment, "dubbo.registries.");
Map<String, Object> properties = getSubProperties(environment,
"dubbo.registries.");
boolean found = properties.entrySet().stream().anyMatch(entry -> {
String key = entry.getKey();
String value = String.valueOf(entry.getValue());
return (key.endsWith(".address") && value.startsWith(PROTOCOL)) ||
(key.endsWith(".protocol") && PROTOCOL.equals(value));
boolean found = properties.entrySet().stream().anyMatch(entry -> {
String key = entry.getKey();
String value = String.valueOf(entry.getValue());
return (key.endsWith(".address") && value.startsWith(PROTOCOL))
|| (key.endsWith(".protocol") && PROTOCOL.equals(value));
});
});
return found ? ConditionOutcome.noMatch("'spring-cloud' protocol was found in 'dubbo.registries.*'") : ConditionOutcome.match();
}
return found
? ConditionOutcome.noMatch(
"'spring-cloud' protocol was found in 'dubbo.registries.*'")
: ConditionOutcome.match();
}
}

@ -16,15 +16,15 @@
*/
package com.alibaba.cloud.dubbo.client.loadbalancer;
import java.io.IOException;
import java.io.InputStream;
import org.apache.dubbo.rpc.service.GenericException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
import java.io.InputStream;
/**
* Dubbo {@link ClientHttpResponse} implementation
*
@ -33,47 +33,50 @@ import java.io.InputStream;
*/
class DubboClientHttpResponse implements ClientHttpResponse {
private final HttpStatus httpStatus;
private final HttpStatus httpStatus;
private final String statusText;
private final String statusText;
private final HttpHeaders httpHeaders = new HttpHeaders();
private final HttpHeaders httpHeaders = new HttpHeaders();
private final DubboHttpOutputMessage httpOutputMessage;
private final DubboHttpOutputMessage httpOutputMessage;
public DubboClientHttpResponse(DubboHttpOutputMessage httpOutputMessage, GenericException exception) {
this.httpStatus = exception != null ? HttpStatus.INTERNAL_SERVER_ERROR : HttpStatus.OK;
this.statusText = exception != null ? exception.getExceptionMessage() : httpStatus.getReasonPhrase();
this.httpOutputMessage = httpOutputMessage;
this.httpHeaders.putAll(httpOutputMessage.getHeaders());
}
public DubboClientHttpResponse(DubboHttpOutputMessage httpOutputMessage,
GenericException exception) {
this.httpStatus = exception != null ? HttpStatus.INTERNAL_SERVER_ERROR
: HttpStatus.OK;
this.statusText = exception != null ? exception.getExceptionMessage()
: httpStatus.getReasonPhrase();
this.httpOutputMessage = httpOutputMessage;
this.httpHeaders.putAll(httpOutputMessage.getHeaders());
}
@Override
public HttpStatus getStatusCode() throws IOException {
return httpStatus;
}
@Override
public HttpStatus getStatusCode() throws IOException {
return httpStatus;
}
@Override
public int getRawStatusCode() throws IOException {
return httpStatus.value();
}
@Override
public int getRawStatusCode() throws IOException {
return httpStatus.value();
}
@Override
public String getStatusText() throws IOException {
return statusText;
}
@Override
public String getStatusText() throws IOException {
return statusText;
}
@Override
public void close() {
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return httpOutputMessage.getBody().getInputStream();
}
@Override
public InputStream getBody() throws IOException {
return httpOutputMessage.getBody().getInputStream();
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
}

@ -16,18 +16,19 @@
*/
package com.alibaba.cloud.dubbo.client.loadbalancer;
import java.io.IOException;
import java.util.List;
import org.apache.dubbo.rpc.service.GenericException;
import com.alibaba.cloud.dubbo.http.converter.HttpMessageConverterHolder;
import com.alibaba.cloud.dubbo.http.util.HttpMessageConverterResolver;
import com.alibaba.cloud.dubbo.metadata.RequestMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.HttpMessageConverter;
import java.io.IOException;
import java.util.List;
import com.alibaba.cloud.dubbo.http.converter.HttpMessageConverterHolder;
import com.alibaba.cloud.dubbo.http.util.HttpMessageConverterResolver;
import com.alibaba.cloud.dubbo.metadata.RequestMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
/**
* Dubbo {@link ClientHttpResponse} Factory
@ -36,29 +37,33 @@ import java.util.List;
*/
class DubboClientHttpResponseFactory {
private final HttpMessageConverterResolver httpMessageConverterResolver;
private final HttpMessageConverterResolver httpMessageConverterResolver;
public DubboClientHttpResponseFactory(List<HttpMessageConverter<?>> messageConverters, ClassLoader classLoader) {
this.httpMessageConverterResolver = new HttpMessageConverterResolver(messageConverters, classLoader);
}
public DubboClientHttpResponseFactory(List<HttpMessageConverter<?>> messageConverters,
ClassLoader classLoader) {
this.httpMessageConverterResolver = new HttpMessageConverterResolver(
messageConverters, classLoader);
}
public ClientHttpResponse build(Object result, GenericException exception,
RequestMetadata requestMetadata, RestMethodMetadata restMethodMetadata) {
public ClientHttpResponse build(Object result, GenericException exception,
RequestMetadata requestMetadata, RestMethodMetadata restMethodMetadata) {
DubboHttpOutputMessage httpOutputMessage = new DubboHttpOutputMessage();
DubboHttpOutputMessage httpOutputMessage = new DubboHttpOutputMessage();
HttpMessageConverterHolder httpMessageConverterHolder = httpMessageConverterResolver.resolve(requestMetadata, restMethodMetadata);
HttpMessageConverterHolder httpMessageConverterHolder = httpMessageConverterResolver
.resolve(requestMetadata, restMethodMetadata);
if (httpMessageConverterHolder != null) {
MediaType mediaType = httpMessageConverterHolder.getMediaType();
HttpMessageConverter converter = httpMessageConverterHolder.getConverter();
try {
converter.write(result, mediaType, httpOutputMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpMessageConverterHolder != null) {
MediaType mediaType = httpMessageConverterHolder.getMediaType();
HttpMessageConverter converter = httpMessageConverterHolder.getConverter();
try {
converter.write(result, mediaType, httpOutputMessage);
}
catch (IOException e) {
e.printStackTrace();
}
}
return new DubboClientHttpResponse(httpOutputMessage, exception);
}
return new DubboClientHttpResponse(httpOutputMessage, exception);
}
}

@ -16,12 +16,12 @@
*/
package com.alibaba.cloud.dubbo.client.loadbalancer;
import java.io.IOException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.util.FastByteArrayOutputStream;
import java.io.IOException;
/**
* Dubbo {@link HttpOutputMessage} implementation
*
@ -29,17 +29,17 @@ import java.io.IOException;
*/
class DubboHttpOutputMessage implements HttpOutputMessage {
private final FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream();
private final FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream();
private final HttpHeaders httpHeaders = new HttpHeaders();
private final HttpHeaders httpHeaders = new HttpHeaders();
@Override
public FastByteArrayOutputStream getBody() throws IOException {
return outputStream;
}
@Override
public FastByteArrayOutputStream getBody() throws IOException {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
}

@ -16,39 +16,42 @@
*/
package com.alibaba.cloud.dubbo.client.loadbalancer;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import java.io.IOException;
import java.net.URI;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
import java.net.URI;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
/**
* Dubbo Metadata {@link ClientHttpRequestInterceptor} Initializing Interceptor executes intercept before
* {@link DubboTransporterInterceptor}
* Dubbo Metadata {@link ClientHttpRequestInterceptor} Initializing Interceptor executes
* intercept before {@link DubboTransporterInterceptor}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboMetadataInitializerInterceptor implements ClientHttpRequestInterceptor {
private final DubboServiceMetadataRepository repository;
private final DubboServiceMetadataRepository repository;
public DubboMetadataInitializerInterceptor(DubboServiceMetadataRepository repository) {
this.repository = repository;
}
public DubboMetadataInitializerInterceptor(
DubboServiceMetadataRepository repository) {
this.repository = repository;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
String serviceName = originalUri.getHost();
repository.initializeMetadata(serviceName);
repository.initializeMetadata(serviceName);
// Execute next
return execution.execute(request, body);
}
// Execute next
return execution.execute(request, body);
}
}

@ -16,17 +16,16 @@
*/
package com.alibaba.cloud.dubbo.client.loadbalancer;
import static org.springframework.web.util.UriComponentsBuilder.fromUri;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import org.apache.dubbo.rpc.service.GenericException;
import org.apache.dubbo.rpc.service.GenericService;
import com.alibaba.cloud.dubbo.http.MutableHttpServerRequest;
import com.alibaba.cloud.dubbo.metadata.DubboRestServiceMetadata;
import com.alibaba.cloud.dubbo.metadata.RequestMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContext;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
@ -38,12 +37,14 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.util.UriComponents;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import static org.springframework.web.util.UriComponentsBuilder.fromUri;
import com.alibaba.cloud.dubbo.http.MutableHttpServerRequest;
import com.alibaba.cloud.dubbo.metadata.DubboRestServiceMetadata;
import com.alibaba.cloud.dubbo.metadata.RequestMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContext;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
/**
* Dubbo Transporter {@link ClientHttpRequestInterceptor} implementation
@ -53,91 +54,100 @@ import static org.springframework.web.util.UriComponentsBuilder.fromUri;
*/
public class DubboTransporterInterceptor implements ClientHttpRequestInterceptor {
private final DubboServiceMetadataRepository repository;
private final DubboServiceMetadataRepository repository;
private final DubboClientHttpResponseFactory clientHttpResponseFactory;
private final DubboClientHttpResponseFactory clientHttpResponseFactory;
private final Map<String, Object> dubboTranslatedAttributes;
private final Map<String, Object> dubboTranslatedAttributes;
private final DubboGenericServiceFactory serviceFactory;
private final DubboGenericServiceFactory serviceFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
private final PathMatcher pathMatcher = new AntPathMatcher();
private final PathMatcher pathMatcher = new AntPathMatcher();
public DubboTransporterInterceptor(DubboServiceMetadataRepository dubboServiceMetadataRepository,
List<HttpMessageConverter<?>> messageConverters,
ClassLoader classLoader,
Map<String, Object> dubboTranslatedAttributes,
DubboGenericServiceFactory serviceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.repository = dubboServiceMetadataRepository;
this.dubboTranslatedAttributes = dubboTranslatedAttributes;
this.clientHttpResponseFactory = new DubboClientHttpResponseFactory(messageConverters, classLoader);
this.serviceFactory = serviceFactory;
this.contextFactory = contextFactory;
}
public DubboTransporterInterceptor(
DubboServiceMetadataRepository dubboServiceMetadataRepository,
List<HttpMessageConverter<?>> messageConverters, ClassLoader classLoader,
Map<String, Object> dubboTranslatedAttributes,
DubboGenericServiceFactory serviceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.repository = dubboServiceMetadataRepository;
this.dubboTranslatedAttributes = dubboTranslatedAttributes;
this.clientHttpResponseFactory = new DubboClientHttpResponseFactory(
messageConverters, classLoader);
this.serviceFactory = serviceFactory;
this.contextFactory = contextFactory;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
String serviceName = originalUri.getHost();
RequestMetadata clientMetadata = buildRequestMetadata(request);
RequestMetadata clientMetadata = buildRequestMetadata(request);
DubboRestServiceMetadata metadata = repository.get(serviceName, clientMetadata);
DubboRestServiceMetadata metadata = repository.get(serviceName, clientMetadata);
if (metadata == null) {
// if DubboServiceMetadata is not found, executes next
return execution.execute(request, body);
}
if (metadata == null) {
// if DubboServiceMetadata is not found, executes next
return execution.execute(request, body);
}
RestMethodMetadata dubboRestMethodMetadata = metadata.getRestMethodMetadata();
RestMethodMetadata dubboRestMethodMetadata = metadata.getRestMethodMetadata();
GenericService genericService = serviceFactory.create(metadata, dubboTranslatedAttributes);
GenericService genericService = serviceFactory.create(metadata,
dubboTranslatedAttributes);
MutableHttpServerRequest httpServerRequest = new MutableHttpServerRequest(request, body);
MutableHttpServerRequest httpServerRequest = new MutableHttpServerRequest(request,
body);
customizeRequest(httpServerRequest, dubboRestMethodMetadata, clientMetadata);
customizeRequest(httpServerRequest, dubboRestMethodMetadata, clientMetadata);
DubboGenericServiceExecutionContext context = contextFactory.create(dubboRestMethodMetadata, httpServerRequest);
DubboGenericServiceExecutionContext context = contextFactory
.create(dubboRestMethodMetadata, httpServerRequest);
Object result = null;
GenericException exception = null;
Object result = null;
GenericException exception = null;
try {
result = genericService.$invoke(context.getMethodName(), context.getParameterTypes(), context.getParameters());
} catch (GenericException e) {
exception = e;
}
try {
result = genericService.$invoke(context.getMethodName(),
context.getParameterTypes(), context.getParameters());
}
catch (GenericException e) {
exception = e;
}
return clientHttpResponseFactory.build(result, exception, clientMetadata, dubboRestMethodMetadata);
}
return clientHttpResponseFactory.build(result, exception, clientMetadata,
dubboRestMethodMetadata);
}
protected void customizeRequest(MutableHttpServerRequest httpServerRequest,
RestMethodMetadata dubboRestMethodMetadata, RequestMetadata clientMetadata) {
protected void customizeRequest(MutableHttpServerRequest httpServerRequest,
RestMethodMetadata dubboRestMethodMetadata, RequestMetadata clientMetadata) {
RequestMetadata dubboRequestMetadata = dubboRestMethodMetadata.getRequest();
String pathPattern = dubboRequestMetadata.getPath();
RequestMetadata dubboRequestMetadata = dubboRestMethodMetadata.getRequest();
String pathPattern = dubboRequestMetadata.getPath();
Map<String, String> pathVariables = pathMatcher.extractUriTemplateVariables(pathPattern, httpServerRequest.getPath());
Map<String, String> pathVariables = pathMatcher
.extractUriTemplateVariables(pathPattern, httpServerRequest.getPath());
if (!CollectionUtils.isEmpty(pathVariables)) {
// Put path variables Map into query parameters Map
httpServerRequest.params(pathVariables);
}
if (!CollectionUtils.isEmpty(pathVariables)) {
// Put path variables Map into query parameters Map
httpServerRequest.params(pathVariables);
}
}
}
private RequestMetadata buildRequestMetadata(HttpRequest request) {
UriComponents uriComponents = fromUri(request.getURI()).build(true);
RequestMetadata requestMetadata = new RequestMetadata();
requestMetadata.setPath(uriComponents.getPath());
requestMetadata.setMethod(request.getMethod().name());
requestMetadata.setParams(uriComponents.getQueryParams());
requestMetadata.setHeaders(request.getHeaders());
return requestMetadata;
}
private RequestMetadata buildRequestMetadata(HttpRequest request) {
UriComponents uriComponents = fromUri(request.getURI()).build(true);
RequestMetadata requestMetadata = new RequestMetadata();
requestMetadata.setPath(uriComponents.getPath());
requestMetadata.setMethod(request.getMethod().name());
requestMetadata.setParams(uriComponents.getQueryParams());
requestMetadata.setHeaders(request.getHeaders());
return requestMetadata;
}
}

@ -16,23 +16,26 @@
*/
package com.alibaba.cloud.dubbo.context;
import com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory;
/**
* The Dubbo services will be registered as the specified Spring cloud applications that will not be considered
* normal ones, but only are used to Dubbo's service discovery even if it is based on Spring Cloud Commons abstraction.
* However, current application will be registered by other DiscoveryClientAutoConfiguration.
* The Dubbo services will be registered as the specified Spring cloud applications that
* will not be considered normal ones, but only are used to Dubbo's service discovery even
* if it is based on Spring Cloud Commons abstraction. However, current application will
* be registered by other DiscoveryClientAutoConfiguration.
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboServiceRegistrationApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
public class DubboServiceRegistrationApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// Set ApplicationContext into SpringCloudRegistryFactory before Dubbo Service Register
SpringCloudRegistryFactory.setApplicationContext(applicationContext);
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// Set ApplicationContext into SpringCloudRegistryFactory before Dubbo Service
// Register
SpringCloudRegistryFactory.setApplicationContext(applicationContext);
}
}

@ -16,15 +16,15 @@
*/
package com.alibaba.cloud.dubbo.env;
import org.springframework.boot.context.properties.ConfigurationProperties;
import static org.springframework.util.StringUtils.commaDelimitedListToStringArray;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.util.StringUtils.trimAllWhitespace;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import static org.springframework.util.StringUtils.commaDelimitedListToStringArray;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.util.StringUtils.trimAllWhitespace;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Dubbo Cloud {@link ConfigurationProperties Properties}
@ -34,48 +34,49 @@ import static org.springframework.util.StringUtils.trimAllWhitespace;
@ConfigurationProperties(prefix = "dubbo.cloud")
public class DubboCloudProperties {
/**
* All services of Dubbo
*/
public static final String ALL_DUBBO_SERVICES = "*";
/**
* All services of Dubbo
*/
public static final String ALL_DUBBO_SERVICES = "*";
/**
* The subscribed services, the default value is "*". The multiple value will use comma(",") as the separator.
*
* @see #ALL_DUBBO_SERVICES
*/
private String subscribedServices = ALL_DUBBO_SERVICES;
/**
* The subscribed services, the default value is "*". The multiple value will use
* comma(",") as the separator.
*
* @see #ALL_DUBBO_SERVICES
*/
private String subscribedServices = ALL_DUBBO_SERVICES;
public String getSubscribedServices() {
return subscribedServices;
}
public String getSubscribedServices() {
return subscribedServices;
}
public void setSubscribedServices(String subscribedServices) {
this.subscribedServices = subscribedServices;
}
public void setSubscribedServices(String subscribedServices) {
this.subscribedServices = subscribedServices;
}
/**
* Get the subscribed services as a {@link Set} with configuration order.
*
* @return non-null Read-only {@link Set}
*/
public Set<String> subscribedServices() {
/**
* Get the subscribed services as a {@link Set} with configuration order.
*
* @return non-null Read-only {@link Set}
*/
public Set<String> subscribedServices() {
String[] services = commaDelimitedListToStringArray(getSubscribedServices());
String[] services = commaDelimitedListToStringArray(getSubscribedServices());
if (services.length < 1) {
return Collections.emptySet();
}
if (services.length < 1) {
return Collections.emptySet();
}
Set<String> subscribedServices = new LinkedHashSet<>();
Set<String> subscribedServices = new LinkedHashSet<>();
for (String service : services) {
if (hasText(service)) { // filter blank service name
// remove all whitespace
subscribedServices.add(trimAllWhitespace(service));
}
}
for (String service : services) {
if (hasText(service)) { // filter blank service name
// remove all whitespace
subscribedServices.add(trimAllWhitespace(service));
}
}
return Collections.unmodifiableSet(subscribedServices);
}
return Collections.unmodifiableSet(subscribedServices);
}
}

@ -16,6 +16,13 @@
*/
package com.alibaba.cloud.dubbo.env;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_PROTOCOL;
import static org.apache.dubbo.config.spring.util.PropertySourcesUtils.getSubProperties;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
@ -29,179 +36,191 @@ import org.springframework.core.env.PropertySource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_PROTOCOL;
import static org.apache.dubbo.config.spring.util.PropertySourcesUtils.getSubProperties;
/**
* Dubbo {@link WebApplicationType#NONE Non-Web Application} {@link EnvironmentPostProcessor}
* Dubbo {@link WebApplicationType#NONE Non-Web Application}
* {@link EnvironmentPostProcessor}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboNonWebApplicationEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private static final String DOT = ".";
/**
* The name of default {@link PropertySource} defined in SpringApplication#configurePropertySources method.
*/
private static final String PROPERTY_SOURCE_NAME = "defaultProperties";
public class DubboNonWebApplicationEnvironmentPostProcessor
implements EnvironmentPostProcessor, Ordered {
private static final String SERVER_PORT_PROPERTY_NAME = "server.port";
private static final String DOT = ".";
private static final String PORT_PROPERTY_NAME = "port";
/**
* The name of default {@link PropertySource} defined in
* SpringApplication#configurePropertySources method.
*/
private static final String PROPERTY_SOURCE_NAME = "defaultProperties";
private static final String PROTOCOL_PROPERTY_NAME_PREFIX = "dubbo.protocol";
private static final String SERVER_PORT_PROPERTY_NAME = "server.port";
private static final String PROTOCOL_NAME_PROPERTY_NAME_SUFFIX = DOT + "name";
private static final String PORT_PROPERTY_NAME = "port";
private static final String PROTOCOL_PORT_PROPERTY_NAME_SUFFIX = DOT + PORT_PROPERTY_NAME;
private static final String PROTOCOL_PROPERTY_NAME_PREFIX = "dubbo.protocol";
private static final String PROTOCOL_PORT_PROPERTY_NAME = PROTOCOL_PROPERTY_NAME_PREFIX + PROTOCOL_PORT_PROPERTY_NAME_SUFFIX;
private static final String PROTOCOL_NAME_PROPERTY_NAME_SUFFIX = DOT + "name";
private static final String PROTOCOL_NAME_PROPERTY_NAME = PROTOCOL_PROPERTY_NAME_PREFIX + PROTOCOL_NAME_PROPERTY_NAME_SUFFIX;
private static final String PROTOCOL_PORT_PROPERTY_NAME_SUFFIX = DOT
+ PORT_PROPERTY_NAME;
private static final String PROTOCOLS_PROPERTY_NAME_PREFIX = "dubbo.protocols";
private static final String PROTOCOL_PORT_PROPERTY_NAME = PROTOCOL_PROPERTY_NAME_PREFIX
+ PROTOCOL_PORT_PROPERTY_NAME_SUFFIX;
private static final String REST_PROTOCOL = "rest";
private static final String PROTOCOL_NAME_PROPERTY_NAME = PROTOCOL_PROPERTY_NAME_PREFIX
+ PROTOCOL_NAME_PROPERTY_NAME_SUFFIX;
private final Logger logger = LoggerFactory.getLogger(DubboNonWebApplicationEnvironmentPostProcessor.class);
private static final String PROTOCOLS_PROPERTY_NAME_PREFIX = "dubbo.protocols";
private static boolean isRestProtocol(String protocol) {
return REST_PROTOCOL.equalsIgnoreCase(protocol);
}
private static final String REST_PROTOCOL = "rest";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
WebApplicationType webApplicationType = application.getWebApplicationType();
private final Logger logger = LoggerFactory
.getLogger(DubboNonWebApplicationEnvironmentPostProcessor.class);
if (!WebApplicationType.NONE.equals(webApplicationType)) { // Just works in Non-Web Application
if (logger.isDebugEnabled()) {
logger.debug("Current application is a Web Application, the process will be ignored.");
}
return;
}
private static boolean isRestProtocol(String protocol) {
return REST_PROTOCOL.equalsIgnoreCase(protocol);
}
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> defaultProperties = createDefaultProperties(environment);
if (!CollectionUtils.isEmpty(defaultProperties)) {
addOrReplace(propertySources, defaultProperties);
}
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
WebApplicationType webApplicationType = application.getWebApplicationType();
if (!WebApplicationType.NONE.equals(webApplicationType)) { // Just works in
// Non-Web Application
if (logger.isDebugEnabled()) {
logger.debug(
"Current application is a Web Application, the process will be ignored.");
}
return;
}
private Map<String, Object> createDefaultProperties(ConfigurableEnvironment environment) {
Map<String, Object> defaultProperties = new HashMap<String, Object>();
resetServerPort(environment, defaultProperties);
return defaultProperties;
}
/**
* Reset server port property if it's absent, whose value is configured by "dubbbo.protocol.port"
* or "dubbo.protcols.rest.port"
*
* @param environment
* @param defaultProperties
*/
private void resetServerPort(ConfigurableEnvironment environment, Map<String, Object> defaultProperties) {
String serverPort = environment.getProperty(SERVER_PORT_PROPERTY_NAME, environment.getProperty(PORT_PROPERTY_NAME));
if (serverPort != null) {
return;
}
serverPort = getRestPortFromProtocolProperty(environment);
if (serverPort == null) {
serverPort = getRestPortFromProtocolsProperties(environment);
}
setServerPort(environment, serverPort, defaultProperties);
}
private String getRestPortFromProtocolProperty(ConfigurableEnvironment environment) {
String protocol = environment.getProperty(PROTOCOL_NAME_PROPERTY_NAME, DEFAULT_PROTOCOL);
return isRestProtocol(protocol) ?
environment.getProperty(PROTOCOL_PORT_PROPERTY_NAME) :
null;
}
private String getRestPortFromProtocolsProperties(ConfigurableEnvironment environment) {
String restPort = null;
Map<String, Object> subProperties = getSubProperties(environment, PROTOCOLS_PROPERTY_NAME_PREFIX);
Properties properties = new Properties();
properties.putAll(subProperties);
for (String propertyName : properties.stringPropertyNames()) {
if (propertyName.endsWith(PROTOCOL_NAME_PROPERTY_NAME_SUFFIX)) { // protocol name property
String protocol = properties.getProperty(propertyName);
if (isRestProtocol(protocol)) {
String beanName = resolveBeanName(propertyName);
if (StringUtils.hasText(beanName)) {
restPort = properties.getProperty(beanName + PROTOCOL_PORT_PROPERTY_NAME_SUFFIX);
break;
}
}
}
}
return restPort;
}
private String resolveBeanName(String propertyName) {
int index = propertyName.indexOf(DOT);
return index > -1 ? propertyName.substring(0, index) : null;
}
private void setServerPort(ConfigurableEnvironment environment, String serverPort,
Map<String, Object> defaultProperties) {
if (serverPort == null) {
return;
}
defaultProperties.put(SERVER_PORT_PROPERTY_NAME, serverPort);
}
/**
* Copy from BusEnvironmentPostProcessor#addOrReplace(MutablePropertySources, Map)
*
* @param propertySources {@link MutablePropertySources}
* @param map Default Dubbo Properties
*/
private void addOrReplace(MutablePropertySources propertySources,
Map<String, Object> map) {
MapPropertySource target = null;
if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
if (source instanceof MapPropertySource) {
target = (MapPropertySource) source;
for (String key : map.keySet()) {
if (!target.containsProperty(key)) {
target.getSource().put(key, map.get(key));
}
}
}
}
if (target == null) {
target = new MapPropertySource(PROPERTY_SOURCE_NAME, map);
}
if (!propertySources.contains(PROPERTY_SOURCE_NAME)) {
propertySources.addLast(target);
}
}
@Override
public int getOrder() { // Keep LOWEST_PRECEDENCE
return LOWEST_PRECEDENCE;
}
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> defaultProperties = createDefaultProperties(environment);
if (!CollectionUtils.isEmpty(defaultProperties)) {
addOrReplace(propertySources, defaultProperties);
}
}
private Map<String, Object> createDefaultProperties(
ConfigurableEnvironment environment) {
Map<String, Object> defaultProperties = new HashMap<String, Object>();
resetServerPort(environment, defaultProperties);
return defaultProperties;
}
/**
* Reset server port property if it's absent, whose value is configured by
* "dubbbo.protocol.port" or "dubbo.protcols.rest.port"
*
* @param environment
* @param defaultProperties
*/
private void resetServerPort(ConfigurableEnvironment environment,
Map<String, Object> defaultProperties) {
String serverPort = environment.getProperty(SERVER_PORT_PROPERTY_NAME,
environment.getProperty(PORT_PROPERTY_NAME));
if (serverPort != null) {
return;
}
serverPort = getRestPortFromProtocolProperty(environment);
if (serverPort == null) {
serverPort = getRestPortFromProtocolsProperties(environment);
}
setServerPort(environment, serverPort, defaultProperties);
}
private String getRestPortFromProtocolProperty(ConfigurableEnvironment environment) {
String protocol = environment.getProperty(PROTOCOL_NAME_PROPERTY_NAME,
DEFAULT_PROTOCOL);
return isRestProtocol(protocol)
? environment.getProperty(PROTOCOL_PORT_PROPERTY_NAME)
: null;
}
private String getRestPortFromProtocolsProperties(
ConfigurableEnvironment environment) {
String restPort = null;
Map<String, Object> subProperties = getSubProperties(environment,
PROTOCOLS_PROPERTY_NAME_PREFIX);
Properties properties = new Properties();
properties.putAll(subProperties);
for (String propertyName : properties.stringPropertyNames()) {
if (propertyName.endsWith(PROTOCOL_NAME_PROPERTY_NAME_SUFFIX)) { // protocol
// name
// property
String protocol = properties.getProperty(propertyName);
if (isRestProtocol(protocol)) {
String beanName = resolveBeanName(propertyName);
if (StringUtils.hasText(beanName)) {
restPort = properties.getProperty(
beanName + PROTOCOL_PORT_PROPERTY_NAME_SUFFIX);
break;
}
}
}
}
return restPort;
}
private String resolveBeanName(String propertyName) {
int index = propertyName.indexOf(DOT);
return index > -1 ? propertyName.substring(0, index) : null;
}
private void setServerPort(ConfigurableEnvironment environment, String serverPort,
Map<String, Object> defaultProperties) {
if (serverPort == null) {
return;
}
defaultProperties.put(SERVER_PORT_PROPERTY_NAME, serverPort);
}
/**
* Copy from BusEnvironmentPostProcessor#addOrReplace(MutablePropertySources, Map)
*
* @param propertySources {@link MutablePropertySources}
* @param map Default Dubbo Properties
*/
private void addOrReplace(MutablePropertySources propertySources,
Map<String, Object> map) {
MapPropertySource target = null;
if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
if (source instanceof MapPropertySource) {
target = (MapPropertySource) source;
for (String key : map.keySet()) {
if (!target.containsProperty(key)) {
target.getSource().put(key, map.get(key));
}
}
}
}
if (target == null) {
target = new MapPropertySource(PROPERTY_SOURCE_NAME, map);
}
if (!propertySources.contains(PROPERTY_SOURCE_NAME)) {
propertySources.addLast(target);
}
}
@Override
public int getOrder() { // Keep LOWEST_PRECEDENCE
return LOWEST_PRECEDENCE;
}
}

@ -16,14 +16,14 @@
*/
package com.alibaba.cloud.dubbo.http;
import java.io.IOException;
import java.io.InputStream;
import org.apache.dubbo.common.io.UnsafeByteArrayInputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import java.io.IOException;
import java.io.InputStream;
/**
* Byte array {@link HttpInputMessage} implementation
*
@ -31,26 +31,26 @@ import java.io.InputStream;
*/
class ByteArrayHttpInputMessage implements HttpInputMessage {
private final HttpHeaders httpHeaders;
private final HttpHeaders httpHeaders;
private final InputStream inputStream;
private final InputStream inputStream;
public ByteArrayHttpInputMessage(byte[] body) {
this(new HttpHeaders(), body);
}
public ByteArrayHttpInputMessage(byte[] body) {
this(new HttpHeaders(), body);
}
public ByteArrayHttpInputMessage(HttpHeaders httpHeaders, byte[] body) {
this.httpHeaders = httpHeaders;
this.inputStream = new UnsafeByteArrayInputStream(body);
}
public ByteArrayHttpInputMessage(HttpHeaders httpHeaders, byte[] body) {
this.httpHeaders = httpHeaders;
this.inputStream = new UnsafeByteArrayInputStream(body);
}
@Override
public InputStream getBody() throws IOException {
return inputStream;
}
@Override
public InputStream getBody() throws IOException {
return inputStream;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
}

@ -16,6 +16,12 @@
*/
package com.alibaba.cloud.dubbo.http;
import static org.springframework.web.util.UriComponentsBuilder.fromPath;
import java.net.URI;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
@ -23,12 +29,6 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.List;
import java.util.Map;
import static org.springframework.web.util.UriComponentsBuilder.fromPath;
/**
* Default {@link HttpRequest} implementation
*
@ -36,96 +36,95 @@ import static org.springframework.web.util.UriComponentsBuilder.fromPath;
*/
public class DefaultHttpRequest implements HttpRequest {
private final String method;
private final URI uri;
private final String method;
private final HttpHeaders headers = new HttpHeaders();
private final URI uri;
public DefaultHttpRequest(String method, String path, Map<String, List<String>> params,
Map<String, List<String>> headers) {
this.method = method == null ? HttpMethod.GET.name() : method.toUpperCase();
this.uri = buildURI(path, params);
this.headers.putAll(headers);
}
private final HttpHeaders headers = new HttpHeaders();
public static Builder builder() {
return new Builder();
}
public DefaultHttpRequest(String method, String path,
Map<String, List<String>> params, Map<String, List<String>> headers) {
this.method = method == null ? HttpMethod.GET.name() : method.toUpperCase();
this.uri = buildURI(path, params);
this.headers.putAll(headers);
}
private URI buildURI(String path, Map<String, List<String>> params) {
UriComponentsBuilder builder = fromPath(path)
.queryParams(new LinkedMultiValueMap<>(params));
return builder.build().toUri();
}
public static Builder builder() {
return new Builder();
}
@Override
public HttpMethod getMethod() {
return HttpMethod.resolve(getMethodValue());
}
private URI buildURI(String path, Map<String, List<String>> params) {
UriComponentsBuilder builder = fromPath(path)
.queryParams(new LinkedMultiValueMap<>(params));
return builder.build().toUri();
}
@Override
public String getMethodValue() {
return method;
}
@Override
public HttpMethod getMethod() {
return HttpMethod.resolve(getMethodValue());
}
@Override
public URI getURI() {
return uri;
}
@Override
public String getMethodValue() {
return method;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
@Override
public URI getURI() {
return uri;
}
/**
* {@link HttpRequest} Builder
*/
public static class Builder {
@Override
public HttpHeaders getHeaders() {
return headers;
}
String method;
/**
* {@link HttpRequest} Builder
*/
public static class Builder {
String path;
String method;
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
String path;
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
public Builder method(String method) {
this.method = method;
return this;
}
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
public Builder path(String path) {
this.path = path;
return this;
}
public Builder method(String method) {
this.method = method;
return this;
}
public Builder param(String name, String value) {
this.params.add(name, value);
return this;
}
public Builder path(String path) {
this.path = path;
return this;
}
public Builder header(String name, String value) {
this.headers.add(name, value);
return this;
}
public Builder param(String name, String value) {
this.params.add(name, value);
return this;
}
public Builder params(Map<String, List<String>> params) {
this.params.putAll(params);
return this;
}
public Builder header(String name, String value) {
this.headers.add(name, value);
return this;
}
public Builder headers(Map<String, List<String>> headers) {
this.headers.putAll(headers);
return this;
}
public Builder params(Map<String, List<String>> params) {
this.params.putAll(params);
return this;
}
public HttpRequest build() {
return new DefaultHttpRequest(method, path, params, headers);
}
}
public Builder headers(Map<String, List<String>> headers) {
this.headers.putAll(headers);
return this;
}
public HttpRequest build() {
return new DefaultHttpRequest(method, path, params, headers);
}
}
}

@ -27,16 +27,16 @@ import org.springframework.util.MultiValueMap;
*/
public interface HttpServerRequest extends HttpRequest, HttpInputMessage {
/**
* Return a path of current HTTP request
*
* @return
*/
String getPath();
/**
* Return a path of current HTTP request
*
* @return
*/
String getPath();
/**
* Return a map with parsed and decoded query parameter values.
*/
MultiValueMap<String, String> getQueryParams();
/**
* Return a map with parsed and decoded query parameter values.
*/
MultiValueMap<String, String> getQueryParams();
}

@ -16,18 +16,18 @@
*/
package com.alibaba.cloud.dubbo.http;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.util.MultiValueMap;
import static com.alibaba.cloud.dubbo.http.util.HttpUtils.getParameters;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Map;
import static com.alibaba.cloud.dubbo.http.util.HttpUtils.getParameters;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.util.MultiValueMap;
/**
* Mutable {@link HttpServerRequest} implementation
@ -36,65 +36,65 @@ import static com.alibaba.cloud.dubbo.http.util.HttpUtils.getParameters;
*/
public class MutableHttpServerRequest implements HttpServerRequest {
private final HttpMethod httpMethod;
private final HttpMethod httpMethod;
private final URI uri;
private final URI uri;
private final String path;
private final String path;
private final MultiValueMap<String, String> queryParams;
private final MultiValueMap<String, String> queryParams;
private final HttpHeaders httpHeaders;
private final HttpHeaders httpHeaders;
private final HttpInputMessage httpInputMessage;
private final HttpInputMessage httpInputMessage;
public MutableHttpServerRequest(HttpRequest httpRequest, byte[] body) {
this.httpMethod = httpRequest.getMethod();
this.uri = httpRequest.getURI();
this.path = uri.getPath();
this.httpHeaders = httpRequest.getHeaders();
this.queryParams = getParameters(httpRequest);
this.httpInputMessage = new ByteArrayHttpInputMessage(body);
}
public MutableHttpServerRequest(HttpRequest httpRequest, byte[] body) {
this.httpMethod = httpRequest.getMethod();
this.uri = httpRequest.getURI();
this.path = uri.getPath();
this.httpHeaders = httpRequest.getHeaders();
this.queryParams = getParameters(httpRequest);
this.httpInputMessage = new ByteArrayHttpInputMessage(body);
}
public MutableHttpServerRequest params(Map<String, String> params) {
queryParams.setAll(params);
return this;
}
public MutableHttpServerRequest params(Map<String, String> params) {
queryParams.setAll(params);
return this;
}
@Override
public InputStream getBody() throws IOException {
return httpInputMessage.getBody();
}
@Override
public InputStream getBody() throws IOException {
return httpInputMessage.getBody();
}
@Override
public HttpMethod getMethod() {
return httpMethod;
}
@Override
public HttpMethod getMethod() {
return httpMethod;
}
// Override method since Spring Framework 5.0
@Override
public String getMethodValue() {
return httpMethod.name();
}
// Override method since Spring Framework 5.0
@Override
public String getMethodValue() {
return httpMethod.name();
}
@Override
public URI getURI() {
return uri;
}
@Override
public URI getURI() {
return uri;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
@Override
public String getPath() {
return path;
}
@Override
public String getPath() {
return path;
}
@Override
public MultiValueMap<String, String> getQueryParams() {
return queryParams;
}
@Override
public MultiValueMap<String, String> getQueryParams() {
return queryParams;
}
}

@ -26,20 +26,21 @@ import org.springframework.http.converter.HttpMessageConverter;
*/
public class HttpMessageConverterHolder {
private final MediaType mediaType;
private final MediaType mediaType;
private final HttpMessageConverter<?> converter;
private final HttpMessageConverter<?> converter;
public HttpMessageConverterHolder(MediaType mediaType, HttpMessageConverter<?> converter) {
this.mediaType = mediaType;
this.converter = converter;
}
public HttpMessageConverterHolder(MediaType mediaType,
HttpMessageConverter<?> converter) {
this.mediaType = mediaType;
this.converter = converter;
}
public MediaType getMediaType() {
return mediaType;
}
public MediaType getMediaType() {
return mediaType;
}
public HttpMessageConverter<?> getConverter() {
return converter;
}
public HttpMessageConverter<?> getConverter() {
return converter;
}
}

@ -27,50 +27,50 @@ import java.util.Iterator;
*/
public abstract class AbstractHttpRequestMatcher implements HttpRequestMatcher {
/**
* Return the discrete items a request condition is composed of.
* <p>
* For example URL patterns, HTTP request methods, param expressions, etc.
*
* @return a collection of objects, never {@code null}
*/
protected abstract Collection<?> getContent();
/**
* Return the discrete items a request condition is composed of.
* <p>
* For example URL patterns, HTTP request methods, param expressions, etc.
*
* @return a collection of objects, never {@code null}
*/
protected abstract Collection<?> getContent();
/**
* The notation to use when printing discrete items of content.
* <p>
* For example {@code " || "} for URL patterns or {@code " && "} for param
* expressions.
*/
protected abstract String getToStringInfix();
/**
* The notation to use when printing discrete items of content.
* <p>
* For example {@code " || "} for URL patterns or {@code " && "} for param
* expressions.
*/
protected abstract String getToStringInfix();
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
return getContent().equals(((AbstractHttpRequestMatcher) other).getContent());
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
return getContent().equals(((AbstractHttpRequestMatcher) other).getContent());
}
@Override
public int hashCode() {
return getContent().hashCode();
}
@Override
public int hashCode() {
return getContent().hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
for (Iterator<?> iterator = getContent().iterator(); iterator.hasNext(); ) {
Object expression = iterator.next();
builder.append(expression.toString());
if (iterator.hasNext()) {
builder.append(getToStringInfix());
}
}
builder.append("]");
return builder.toString();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
for (Iterator<?> iterator = getContent().iterator(); iterator.hasNext();) {
Object expression = iterator.next();
builder.append(expression.toString());
if (iterator.hasNext()) {
builder.append(getToStringInfix());
}
}
builder.append("]");
return builder.toString();
}
}

@ -19,73 +19,77 @@ package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.MediaType;
/**
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.AbstractMediaTypeExpression
* The some source code is scratched from
* org.springframework.web.servlet.mvc.condition.AbstractMediaTypeExpression
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class AbstractMediaTypeExpression implements MediaTypeExpression, Comparable<AbstractMediaTypeExpression> {
public class AbstractMediaTypeExpression
implements MediaTypeExpression, Comparable<AbstractMediaTypeExpression> {
private final MediaType mediaType;
private final MediaType mediaType;
private final boolean negated;
private final boolean negated;
AbstractMediaTypeExpression(String expression) {
if (expression.startsWith("!")) {
this.negated = true;
expression = expression.substring(1);
} else {
this.negated = false;
}
this.mediaType = MediaType.parseMediaType(expression);
}
AbstractMediaTypeExpression(String expression) {
if (expression.startsWith("!")) {
this.negated = true;
expression = expression.substring(1);
}
else {
this.negated = false;
}
this.mediaType = MediaType.parseMediaType(expression);
}
AbstractMediaTypeExpression(MediaType mediaType, boolean negated) {
this.mediaType = mediaType;
this.negated = negated;
}
AbstractMediaTypeExpression(MediaType mediaType, boolean negated) {
this.mediaType = mediaType;
this.negated = negated;
}
@Override
public MediaType getMediaType() {
return this.mediaType;
}
@Override
public MediaType getMediaType() {
return this.mediaType;
}
@Override
public boolean isNegated() {
return this.negated;
}
@Override
public boolean isNegated() {
return this.negated;
}
@Override
public int compareTo(AbstractMediaTypeExpression other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(),
other.getMediaType());
}
@Override
public int compareTo(AbstractMediaTypeExpression other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType());
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
AbstractMediaTypeExpression otherExpr = (AbstractMediaTypeExpression) other;
return (this.mediaType.equals(otherExpr.mediaType)
&& this.negated == otherExpr.negated);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
AbstractMediaTypeExpression otherExpr = (AbstractMediaTypeExpression) other;
return (this.mediaType.equals(otherExpr.mediaType) && this.negated == otherExpr.negated);
}
@Override
public int hashCode() {
return this.mediaType.hashCode();
}
@Override
public int hashCode() {
return this.mediaType.hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (this.negated) {
builder.append('!');
}
builder.append(this.mediaType.toString());
return builder.toString();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (this.negated) {
builder.append('!');
}
builder.append(this.mediaType.toString());
return builder.toString();
}
}

@ -16,14 +16,15 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import static org.springframework.util.StringUtils.trimWhitespace;
import org.springframework.http.HttpRequest;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.springframework.util.StringUtils.trimWhitespace;
/**
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.AbstractNameValueExpression
* The some source code is scratched from
* org.springframework.web.servlet.mvc.condition.AbstractNameValueExpression
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
@ -31,116 +32,123 @@ import static org.springframework.util.StringUtils.trimWhitespace;
*/
abstract class AbstractNameValueExpression<T> implements NameValueExpression<T> {
protected final String name;
protected final T value;
protected final boolean negated;
AbstractNameValueExpression(String expression) {
int separator = expression.indexOf('=');
if (separator == -1) {
this.negated = expression.startsWith("!");
this.name = trimWhitespace((this.negated ? expression.substring(1) : expression));
this.value = null;
} else {
this.negated = (separator > 0) && (expression.charAt(separator - 1) == '!');
this.name = trimWhitespace((this.negated ? expression.substring(0, separator - 1)
: expression.substring(0, separator)));
String valueExpression = getValueExpression(expression, separator);
this.value = isExcludedValue(valueExpression) ? null : parseValue(valueExpression);
}
}
private String getValueExpression(String expression, int separator) {
return trimWhitespace(expression.substring(separator + 1));
}
/**
* Exclude the pattern value Expression: "{value}", subclass could override this method.
*
* @param valueExpression
* @return
*/
protected boolean isExcludedValue(String valueExpression) {
return StringUtils.hasText(valueExpression) &&
valueExpression.startsWith("{")
&& valueExpression.endsWith("}");
}
@Override
public String getName() {
return this.name;
}
@Override
public T getValue() {
return this.value;
}
@Override
public boolean isNegated() {
return this.negated;
}
public final boolean match(HttpRequest request) {
boolean isMatch;
if (this.value != null) {
isMatch = matchValue(request);
} else {
isMatch = matchName(request);
}
return (this.negated ? !isMatch : isMatch);
}
protected abstract boolean isCaseSensitiveName();
protected abstract T parseValue(String valueExpression);
protected abstract boolean matchName(HttpRequest request);
protected abstract boolean matchValue(HttpRequest request);
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
AbstractNameValueExpression<?> that = (AbstractNameValueExpression<?>) other;
return ((isCaseSensitiveName() ? this.name.equals(that.name) : this.name.equalsIgnoreCase(that.name)) &&
ObjectUtils.nullSafeEquals(this.value, that.value) && this.negated == that.negated);
}
@Override
public int hashCode() {
int result = (isCaseSensitiveName() ? this.name.hashCode() : this.name.toLowerCase().hashCode());
result = 31 * result + (this.value != null ? this.value.hashCode() : 0);
result = 31 * result + (this.negated ? 1 : 0);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (this.value != null) {
builder.append(this.name);
if (this.negated) {
builder.append('!');
}
builder.append('=');
builder.append(this.value);
} else {
if (this.negated) {
builder.append('!');
}
builder.append(this.name);
}
return builder.toString();
}
protected final String name;
protected final T value;
protected final boolean negated;
AbstractNameValueExpression(String expression) {
int separator = expression.indexOf('=');
if (separator == -1) {
this.negated = expression.startsWith("!");
this.name = trimWhitespace(
(this.negated ? expression.substring(1) : expression));
this.value = null;
}
else {
this.negated = (separator > 0) && (expression.charAt(separator - 1) == '!');
this.name = trimWhitespace(
(this.negated ? expression.substring(0, separator - 1)
: expression.substring(0, separator)));
String valueExpression = getValueExpression(expression, separator);
this.value = isExcludedValue(valueExpression) ? null
: parseValue(valueExpression);
}
}
private String getValueExpression(String expression, int separator) {
return trimWhitespace(expression.substring(separator + 1));
}
/**
* Exclude the pattern value Expression: "{value}", subclass could override this
* method.
*
* @param valueExpression
* @return
*/
protected boolean isExcludedValue(String valueExpression) {
return StringUtils.hasText(valueExpression) && valueExpression.startsWith("{")
&& valueExpression.endsWith("}");
}
@Override
public String getName() {
return this.name;
}
@Override
public T getValue() {
return this.value;
}
@Override
public boolean isNegated() {
return this.negated;
}
public final boolean match(HttpRequest request) {
boolean isMatch;
if (this.value != null) {
isMatch = matchValue(request);
}
else {
isMatch = matchName(request);
}
return (this.negated ? !isMatch : isMatch);
}
protected abstract boolean isCaseSensitiveName();
protected abstract T parseValue(String valueExpression);
protected abstract boolean matchName(HttpRequest request);
protected abstract boolean matchValue(HttpRequest request);
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
AbstractNameValueExpression<?> that = (AbstractNameValueExpression<?>) other;
return ((isCaseSensitiveName() ? this.name.equals(that.name)
: this.name.equalsIgnoreCase(that.name))
&& ObjectUtils.nullSafeEquals(this.value, that.value)
&& this.negated == that.negated);
}
@Override
public int hashCode() {
int result = (isCaseSensitiveName() ? this.name.hashCode()
: this.name.toLowerCase().hashCode());
result = 31 * result + (this.value != null ? this.value.hashCode() : 0);
result = 31 * result + (this.negated ? 1 : 0);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (this.value != null) {
builder.append(this.name);
if (this.negated) {
builder.append('!');
}
builder.append('=');
builder.append(this.value);
}
else {
if (this.negated) {
builder.append('!');
}
builder.append(this.name);
}
return builder.toString();
}
}

@ -16,13 +16,13 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.HttpRequest;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.springframework.http.HttpRequest;
/**
* Composite {@link HttpRequestMatcher} implementation
*
@ -30,44 +30,44 @@ import java.util.List;
*/
public abstract class CompositeHttpRequestMatcher extends AbstractHttpRequestMatcher {
private final List<HttpRequestMatcher> matchers = new LinkedList<>();
private final List<HttpRequestMatcher> matchers = new LinkedList<>();
public CompositeHttpRequestMatcher(HttpRequestMatcher... matchers) {
this.matchers.addAll(Arrays.asList(matchers));
}
public CompositeHttpRequestMatcher(HttpRequestMatcher... matchers) {
this.matchers.addAll(Arrays.asList(matchers));
}
public CompositeHttpRequestMatcher and(HttpRequestMatcher matcher) {
this.matchers.add(matcher);
return this;
}
public CompositeHttpRequestMatcher and(HttpRequestMatcher matcher) {
this.matchers.add(matcher);
return this;
}
@Override
public boolean match(HttpRequest request) {
for (HttpRequestMatcher matcher : matchers) {
if (!matcher.match(request)) {
return false;
}
}
return true;
}
@Override
public boolean match(HttpRequest request) {
for (HttpRequestMatcher matcher : matchers) {
if (!matcher.match(request)) {
return false;
}
}
return true;
}
protected List<HttpRequestMatcher> getMatchers() {
return this.matchers;
}
protected List<HttpRequestMatcher> getMatchers() {
return this.matchers;
}
@Override
protected Collection<?> getContent() {
List<Object> content = new LinkedList<>();
for (HttpRequestMatcher matcher : getMatchers()) {
if (matcher instanceof AbstractHttpRequestMatcher) {
content.addAll(((AbstractHttpRequestMatcher) matcher).getContent());
}
}
return content;
}
@Override
protected Collection<?> getContent() {
List<Object> content = new LinkedList<>();
for (HttpRequestMatcher matcher : getMatchers()) {
if (matcher instanceof AbstractHttpRequestMatcher) {
content.addAll(((AbstractHttpRequestMatcher) matcher).getContent());
}
}
return content;
}
@Override
protected String getToStringInfix() {
return " && ";
}
@Override
protected String getToStringInfix() {
return " && ";
}
}

@ -29,16 +29,16 @@ import org.springframework.http.MediaType;
*/
class ConsumeMediaTypeExpression extends AbstractMediaTypeExpression {
ConsumeMediaTypeExpression(String expression) {
super(expression);
}
ConsumeMediaTypeExpression(String expression) {
super(expression);
}
ConsumeMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
ConsumeMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
public final boolean match(MediaType contentType) {
boolean match = getMediaType().includes(contentType);
return (!isNegated() ? match : !match);
}
public final boolean match(MediaType contentType) {
boolean match = getMediaType().includes(contentType);
return (!isNegated() ? match : !match);
}
}

@ -23,7 +23,8 @@ import org.springframework.util.ObjectUtils;
/**
* Parses and matches a single header expression to a request.
* <p>
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression
* The some source code is scratched from
* org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@ -31,30 +32,30 @@ import org.springframework.util.ObjectUtils;
*/
class HeaderExpression extends AbstractNameValueExpression<String> {
HeaderExpression(String expression) {
super(expression);
}
HeaderExpression(String expression) {
super(expression);
}
@Override
protected boolean isCaseSensitiveName() {
return false;
}
@Override
protected boolean isCaseSensitiveName() {
return false;
}
@Override
protected String parseValue(String valueExpression) {
return valueExpression;
}
@Override
protected String parseValue(String valueExpression) {
return valueExpression;
}
@Override
protected boolean matchName(HttpRequest request) {
HttpHeaders httpHeaders = request.getHeaders();
return httpHeaders.containsKey(this.name);
}
@Override
protected boolean matchName(HttpRequest request) {
HttpHeaders httpHeaders = request.getHeaders();
return httpHeaders.containsKey(this.name);
}
@Override
protected boolean matchValue(HttpRequest request) {
HttpHeaders httpHeaders = request.getHeaders();
String headerValue = httpHeaders.getFirst(this.name);
return ObjectUtils.nullSafeEquals(this.value, headerValue);
}
@Override
protected boolean matchValue(HttpRequest request) {
HttpHeaders httpHeaders = request.getHeaders();
String headerValue = httpHeaders.getFirst(this.name);
return ObjectUtils.nullSafeEquals(this.value, headerValue);
}
}

@ -16,10 +16,6 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -27,6 +23,10 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
/**
* {@link HttpRequest} 'Content-Type' header {@link HttpRequestMatcher matcher}
*
@ -36,88 +36,91 @@ import java.util.Set;
*/
public class HttpRequestConsumersMatcher extends AbstractHttpRequestMatcher {
private final List<ConsumeMediaTypeExpression> expressions;
/**
* Creates a new instance from 0 or more "consumes" expressions.
*
* @param consumes consumes expressions if 0 expressions are provided,
* the condition will match to every request
*/
public HttpRequestConsumersMatcher(String... consumes) {
this(consumes, null);
}
/**
* Creates a new instance with "consumes" and "header" expressions.
* "Header" expressions where the header name is not 'Content-Type' or have
* no header value defined are ignored. If 0 expressions are provided in
* total, the condition will match to every request
*
* @param consumes consumes expressions
* @param headers headers expressions
*/
public HttpRequestConsumersMatcher(String[] consumes, String[] headers) {
this(parseExpressions(consumes, headers));
}
/**
* Private constructor accepting parsed media type expressions.
*/
private HttpRequestConsumersMatcher(Collection<ConsumeMediaTypeExpression> expressions) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
}
private static Set<ConsumeMediaTypeExpression> parseExpressions(String[] consumes, String[] headers) {
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Content-Type".equalsIgnoreCase(expr.name) && expr.value != null) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ConsumeMediaTypeExpression(mediaType, expr.negated));
}
}
}
}
for (String consume : consumes) {
result.add(new ConsumeMediaTypeExpression(consume));
}
return result;
}
@Override
public boolean match(HttpRequest request) {
if (expressions.isEmpty()) {
return true;
}
HttpHeaders httpHeaders = request.getHeaders();
MediaType contentType = httpHeaders.getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
for (ConsumeMediaTypeExpression expression : expressions) {
if (!expression.match(contentType)) {
return false;
}
}
return true;
}
@Override
protected Collection<ConsumeMediaTypeExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " || ";
}
private final List<ConsumeMediaTypeExpression> expressions;
/**
* Creates a new instance from 0 or more "consumes" expressions.
*
* @param consumes consumes expressions if 0 expressions are provided, the condition
* will match to every request
*/
public HttpRequestConsumersMatcher(String... consumes) {
this(consumes, null);
}
/**
* Creates a new instance with "consumes" and "header" expressions. "Header"
* expressions where the header name is not 'Content-Type' or have no header value
* defined are ignored. If 0 expressions are provided in total, the condition will
* match to every request
*
* @param consumes consumes expressions
* @param headers headers expressions
*/
public HttpRequestConsumersMatcher(String[] consumes, String[] headers) {
this(parseExpressions(consumes, headers));
}
/**
* Private constructor accepting parsed media type expressions.
*/
private HttpRequestConsumersMatcher(
Collection<ConsumeMediaTypeExpression> expressions) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
}
private static Set<ConsumeMediaTypeExpression> parseExpressions(String[] consumes,
String[] headers) {
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Content-Type".equalsIgnoreCase(expr.name) && expr.value != null) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(
new ConsumeMediaTypeExpression(mediaType, expr.negated));
}
}
}
}
for (String consume : consumes) {
result.add(new ConsumeMediaTypeExpression(consume));
}
return result;
}
@Override
public boolean match(HttpRequest request) {
if (expressions.isEmpty()) {
return true;
}
HttpHeaders httpHeaders = request.getHeaders();
MediaType contentType = httpHeaders.getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
for (ConsumeMediaTypeExpression expression : expressions) {
if (!expression.match(contentType)) {
return false;
}
}
return true;
}
@Override
protected Collection<ConsumeMediaTypeExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " || ";
}
}

@ -16,13 +16,13 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
/**
* {@link HttpRequest} headers {@link HttpRequestMatcher matcher}
*
@ -30,42 +30,42 @@ import java.util.Set;
*/
public class HttpRequestHeadersMatcher extends AbstractHttpRequestMatcher {
private final Set<HeaderExpression> expressions;
private final Set<HeaderExpression> expressions;
public HttpRequestHeadersMatcher(String... headers) {
this.expressions = parseExpressions(headers);
}
public HttpRequestHeadersMatcher(String... headers) {
this.expressions = parseExpressions(headers);
}
private static Set<HeaderExpression> parseExpressions(String... headers) {
Set<HeaderExpression> expressions = new LinkedHashSet<>();
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if (HttpHeaders.ACCEPT.equalsIgnoreCase(expr.name) ||
HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(expr.name)) {
continue;
}
expressions.add(expr);
}
return expressions;
}
private static Set<HeaderExpression> parseExpressions(String... headers) {
Set<HeaderExpression> expressions = new LinkedHashSet<>();
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if (HttpHeaders.ACCEPT.equalsIgnoreCase(expr.name)
|| HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(expr.name)) {
continue;
}
expressions.add(expr);
}
return expressions;
}
@Override
public boolean match(HttpRequest request) {
for (HeaderExpression expression : this.expressions) {
if (!expression.match(request)) {
return false;
}
}
return true;
}
@Override
public boolean match(HttpRequest request) {
for (HeaderExpression expression : this.expressions) {
if (!expression.match(request)) {
return false;
}
}
return true;
}
@Override
protected Collection<HeaderExpression> getContent() {
return this.expressions;
}
@Override
protected Collection<HeaderExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " && ";
}
@Override
protected String getToStringInfix() {
return " && ";
}
}

@ -25,11 +25,11 @@ import org.springframework.http.HttpRequest;
*/
public interface HttpRequestMatcher {
/**
* Match {@link HttpRequest} or not
*
* @param request The {@link HttpRequest} instance
* @return if matched, return <code>true</code>, or <code>false</code>.
*/
boolean match(HttpRequest request);
/**
* Match {@link HttpRequest} or not
*
* @param request The {@link HttpRequest} instance
* @return if matched, return <code>true</code>, or <code>false</code>.
*/
boolean match(HttpRequest request);
}

@ -16,15 +16,15 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.util.StringUtils;
import static org.springframework.http.HttpMethod.resolve;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import static org.springframework.http.HttpMethod.resolve;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.util.StringUtils;
/**
* {@link HttpRequest} {@link HttpMethod methods} {@link HttpRequestMatcher matcher}
@ -33,50 +33,50 @@ import static org.springframework.http.HttpMethod.resolve;
*/
public class HttpRequestMethodsMatcher extends AbstractHttpRequestMatcher {
private final Set<HttpMethod> methods;
private final Set<HttpMethod> methods;
public HttpRequestMethodsMatcher(String... methods) {
this.methods = resolveHttpMethods(methods);
}
public HttpRequestMethodsMatcher(String... methods) {
this.methods = resolveHttpMethods(methods);
}
private Set<HttpMethod> resolveHttpMethods(String[] methods) {
Set<HttpMethod> httpMethods = new LinkedHashSet<>(methods.length);
for (String method : methods) {
if (!StringUtils.hasText(method)) {
continue;
}
HttpMethod httpMethod = resolve(method);
httpMethods.add(httpMethod);
}
return httpMethods;
}
private Set<HttpMethod> resolveHttpMethods(String[] methods) {
Set<HttpMethod> httpMethods = new LinkedHashSet<>(methods.length);
for (String method : methods) {
if (!StringUtils.hasText(method)) {
continue;
}
HttpMethod httpMethod = resolve(method);
httpMethods.add(httpMethod);
}
return httpMethods;
}
public Set<HttpMethod> getMethods() {
return methods;
}
public Set<HttpMethod> getMethods() {
return methods;
}
@Override
public boolean match(HttpRequest request) {
boolean matched = false;
HttpMethod httpMethod = request.getMethod();
if (httpMethod != null) {
for (HttpMethod method : getMethods()) {
if (httpMethod.equals(method)) {
matched = true;
break;
}
}
}
return matched;
}
@Override
public boolean match(HttpRequest request) {
boolean matched = false;
HttpMethod httpMethod = request.getMethod();
if (httpMethod != null) {
for (HttpMethod method : getMethods()) {
if (httpMethod.equals(method)) {
matched = true;
break;
}
}
}
return matched;
}
@Override
protected Collection<HttpMethod> getContent() {
return methods;
}
@Override
protected Collection<HttpMethod> getContent() {
return methods;
}
@Override
protected String getToStringInfix() {
return " || ";
}
@Override
protected String getToStringInfix() {
return " || ";
}
}

@ -16,13 +16,13 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.HttpRequest;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.http.HttpRequest;
import org.springframework.util.CollectionUtils;
/**
* {@link HttpRequest} parameters {@link HttpRequestMatcher matcher}
*
@ -30,47 +30,47 @@ import java.util.Set;
*/
public class HttpRequestParamsMatcher extends AbstractHttpRequestMatcher {
private final Set<ParamExpression> expressions;
private final Set<ParamExpression> expressions;
/**
* @param params The pattern of params :
* <ul>
* <li>name=value</li>
* <li>name</li>
* </ul>
*/
public HttpRequestParamsMatcher(String... params) {
this.expressions = parseExpressions(params);
}
/**
* @param params The pattern of params :
* <ul>
* <li>name=value</li>
* <li>name</li>
* </ul>
*/
public HttpRequestParamsMatcher(String... params) {
this.expressions = parseExpressions(params);
}
private static Set<ParamExpression> parseExpressions(String... params) {
Set<ParamExpression> expressions = new LinkedHashSet<>();
for (String param : params) {
expressions.add(new ParamExpression(param));
}
return expressions;
}
private static Set<ParamExpression> parseExpressions(String... params) {
Set<ParamExpression> expressions = new LinkedHashSet<>();
for (String param : params) {
expressions.add(new ParamExpression(param));
}
return expressions;
}
@Override
public boolean match(HttpRequest request) {
if (CollectionUtils.isEmpty(expressions)) {
return true;
}
for (ParamExpression paramExpression : expressions) {
if (paramExpression.match(request)) {
return true;
}
}
return false;
}
@Override
public boolean match(HttpRequest request) {
if (CollectionUtils.isEmpty(expressions)) {
return true;
}
for (ParamExpression paramExpression : expressions) {
if (paramExpression.match(request)) {
return true;
}
}
return false;
}
@Override
protected Collection<ParamExpression> getContent() {
return this.expressions;
}
@Override
protected Collection<ParamExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " && ";
}
@Override
protected String getToStringInfix() {
return " && ";
}
}

@ -16,11 +16,6 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.HttpRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
@ -29,6 +24,11 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.http.HttpRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
/**
* {@link HttpRequest} {@link URI} {@link HttpRequestMatcher matcher}
*
@ -36,82 +36,82 @@ import java.util.Set;
*/
public class HttpRequestPathMatcher extends AbstractHttpRequestMatcher {
private final Set<String> patterns;
private final Set<String> patterns;
private final PathMatcher pathMatcher;
private final PathMatcher pathMatcher;
public HttpRequestPathMatcher(String... patterns) {
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
this.pathMatcher = new AntPathMatcher();
}
public HttpRequestPathMatcher(String... patterns) {
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
this.pathMatcher = new AntPathMatcher();
}
private static Set<String> prependLeadingSlash(String[] patterns) {
Set<String> result = new LinkedHashSet<>(patterns.length);
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
result.add(pattern);
}
return result;
}
private static Set<String> prependLeadingSlash(String[] patterns) {
Set<String> result = new LinkedHashSet<>(patterns.length);
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
result.add(pattern);
}
return result;
}
@Override
public boolean match(HttpRequest request) {
List<String> matches = getMatchingPatterns(request);
return !matches.isEmpty();
}
@Override
public boolean match(HttpRequest request) {
List<String> matches = getMatchingPatterns(request);
return !matches.isEmpty();
}
public List<String> getMatchingPatterns(HttpRequest request) {
String path = getPath(request);
List<String> matches = getMatchingPatterns(path);
return matches;
}
public List<String> getMatchingPatterns(HttpRequest request) {
String path = getPath(request);
List<String> matches = getMatchingPatterns(path);
return matches;
}
public List<String> getMatchingPatterns(String lookupPath) {
List<String> matches = new ArrayList<>();
for (String pattern : this.patterns) {
String match = getMatchingPattern(pattern, lookupPath);
if (match != null) {
matches.add(match);
}
}
if (matches.size() > 1) {
matches.sort(this.pathMatcher.getPatternComparator(lookupPath));
}
return matches;
}
public List<String> getMatchingPatterns(String lookupPath) {
List<String> matches = new ArrayList<>();
for (String pattern : this.patterns) {
String match = getMatchingPattern(pattern, lookupPath);
if (match != null) {
matches.add(match);
}
}
if (matches.size() > 1) {
matches.sort(this.pathMatcher.getPatternComparator(lookupPath));
}
return matches;
}
private String getMatchingPattern(String pattern, String lookupPath) {
if (pattern.equals(lookupPath)) {
return pattern;
}
boolean hasSuffix = pattern.indexOf('.') != -1;
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
return pattern + ".*";
}
if (this.pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
private String getMatchingPattern(String pattern, String lookupPath) {
if (pattern.equals(lookupPath)) {
return pattern;
}
boolean hasSuffix = pattern.indexOf('.') != -1;
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
return pattern + ".*";
}
if (this.pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
return pattern + "/";
}
return null;
}
if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
return pattern + "/";
}
return null;
}
private String getPath(HttpRequest request) {
URI uri = request.getURI();
return uri.getPath();
}
private String getPath(HttpRequest request) {
URI uri = request.getURI();
return uri.getPath();
}
@Override
protected Collection<String> getContent() {
return this.patterns;
}
@Override
protected Collection<String> getContent() {
return this.patterns;
}
@Override
protected String getToStringInfix() {
return " || ";
}
@Override
protected String getToStringInfix() {
return " || ";
}
}

@ -16,10 +16,6 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -27,6 +23,10 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
/**
* {@link HttpRequest} 'Accept' header {@link HttpRequestMatcher matcher}
*
@ -36,84 +36,88 @@ import java.util.Set;
*/
public class HttpRequestProducesMatcher extends AbstractHttpRequestMatcher {
private final List<ProduceMediaTypeExpression> expressions;
/**
* Creates a new instance from "produces" expressions. If 0 expressions
* are provided in total, this condition will match to any request.
*
* @param produces produces expressions
*/
public HttpRequestProducesMatcher(String... produces) {
this(produces, null);
}
/**
* Creates a new instance with "produces" and "header" expressions. "Header"
* expressions where the header name is not 'Accept' or have no header value
* defined are ignored. If 0 expressions are provided in total, this condition
* will match to any request.
*
* @param produces produces expressions
* @param headers headers expressions
*/
public HttpRequestProducesMatcher(String[] produces, String[] headers) {
this(parseExpressions(produces, headers));
}
/**
* Private constructor accepting parsed media type expressions.
*/
private HttpRequestProducesMatcher(Collection<ProduceMediaTypeExpression> expressions) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
}
private static Set<ProduceMediaTypeExpression> parseExpressions(String[] produces, String[] headers) {
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if (HttpHeaders.ACCEPT.equalsIgnoreCase(expr.name) && expr.value != null) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ProduceMediaTypeExpression(mediaType, expr.negated));
}
}
}
}
for (String produce : produces) {
result.add(new ProduceMediaTypeExpression(produce));
}
return result;
}
@Override
public boolean match(HttpRequest request) {
if (expressions.isEmpty()) {
return true;
}
HttpHeaders httpHeaders = request.getHeaders();
List<MediaType> acceptedMediaTypes = httpHeaders.getAccept();
for (ProduceMediaTypeExpression expression : expressions) {
if (!expression.match(acceptedMediaTypes)) {
return false;
}
}
return true;
}
@Override
protected Collection<ProduceMediaTypeExpression> getContent() {
return expressions;
}
@Override
protected String getToStringInfix() {
return " || ";
}
private final List<ProduceMediaTypeExpression> expressions;
/**
* Creates a new instance from "produces" expressions. If 0 expressions are provided
* in total, this condition will match to any request.
*
* @param produces produces expressions
*/
public HttpRequestProducesMatcher(String... produces) {
this(produces, null);
}
/**
* Creates a new instance with "produces" and "header" expressions. "Header"
* expressions where the header name is not 'Accept' or have no header value defined
* are ignored. If 0 expressions are provided in total, this condition will match to
* any request.
*
* @param produces produces expressions
* @param headers headers expressions
*/
public HttpRequestProducesMatcher(String[] produces, String[] headers) {
this(parseExpressions(produces, headers));
}
/**
* Private constructor accepting parsed media type expressions.
*/
private HttpRequestProducesMatcher(
Collection<ProduceMediaTypeExpression> expressions) {
this.expressions = new ArrayList<>(expressions);
Collections.sort(this.expressions);
}
private static Set<ProduceMediaTypeExpression> parseExpressions(String[] produces,
String[] headers) {
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if (HttpHeaders.ACCEPT.equalsIgnoreCase(expr.name)
&& expr.value != null) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(
new ProduceMediaTypeExpression(mediaType, expr.negated));
}
}
}
}
for (String produce : produces) {
result.add(new ProduceMediaTypeExpression(produce));
}
return result;
}
@Override
public boolean match(HttpRequest request) {
if (expressions.isEmpty()) {
return true;
}
HttpHeaders httpHeaders = request.getHeaders();
List<MediaType> acceptedMediaTypes = httpHeaders.getAccept();
for (ProduceMediaTypeExpression expression : expressions) {
if (!expression.match(acceptedMediaTypes)) {
return false;
}
}
return true;
}
@Override
protected Collection<ProduceMediaTypeExpression> getContent() {
return expressions;
}
@Override
protected String getToStringInfix() {
return " || ";
}
}

@ -19,17 +19,18 @@ package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.MediaType;
/**
* A contract for media type expressions (e.g. "text/plain", "!text/plain") as
* defined in the for "consumes" and "produces".
* A contract for media type expressions (e.g. "text/plain", "!text/plain") as defined in
* the for "consumes" and "produces".
* <p>
* The source code is scratched from org.springframework.web.servlet.mvc.condition.MediaTypeExpression
* The source code is scratched from
* org.springframework.web.servlet.mvc.condition.MediaTypeExpression
*
* @author Rossen Stoyanchev
*/
interface MediaTypeExpression {
MediaType getMediaType();
MediaType getMediaType();
boolean isNegated();
boolean isNegated();
}

@ -16,12 +16,12 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
/**
* A contract for {@code "name!=value"} style expression used to specify request
* parameters and request header in HTTP request
* <p>
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.NameValueExpression
* The some source code is scratched from
* org.springframework.web.servlet.mvc.condition.NameValueExpression
*
* @param <T> the value type
* @author Rossen Stoyanchev
@ -29,10 +29,10 @@ package com.alibaba.cloud.dubbo.http.matcher;
*/
interface NameValueExpression<T> {
String getName();
String getName();
T getValue();
T getValue();
boolean isNegated();
boolean isNegated();
}

@ -16,16 +16,17 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import static com.alibaba.cloud.dubbo.http.util.HttpUtils.getParameters;
import org.springframework.http.HttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import static com.alibaba.cloud.dubbo.http.util.HttpUtils.getParameters;
/**
* Parses and matches a single param expression to a request.
* <p>
* The some source code is scratched from org.springframework.web.servlet.mvc.condition.ParamsRequestCondition.ParamExpression
* The some source code is scratched from
* org.springframework.web.servlet.mvc.condition.ParamsRequestCondition.ParamExpression
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@ -33,30 +34,30 @@ import static com.alibaba.cloud.dubbo.http.util.HttpUtils.getParameters;
*/
class ParamExpression extends AbstractNameValueExpression<String> {
ParamExpression(String expression) {
super(expression);
}
@Override
protected boolean isCaseSensitiveName() {
return true;
}
@Override
protected String parseValue(String valueExpression) {
return valueExpression;
}
@Override
protected boolean matchName(HttpRequest request) {
MultiValueMap<String, String> parametersMap = getParameters(request);
return parametersMap.containsKey(this.name);
}
@Override
protected boolean matchValue(HttpRequest request) {
MultiValueMap<String, String> parametersMap = getParameters(request);
String parameterValue = parametersMap.getFirst(this.name);
return ObjectUtils.nullSafeEquals(this.value, parameterValue);
}
ParamExpression(String expression) {
super(expression);
}
@Override
protected boolean isCaseSensitiveName() {
return true;
}
@Override
protected String parseValue(String valueExpression) {
return valueExpression;
}
@Override
protected boolean matchName(HttpRequest request) {
MultiValueMap<String, String> parametersMap = getParameters(request);
return parametersMap.containsKey(this.name);
}
@Override
protected boolean matchValue(HttpRequest request) {
MultiValueMap<String, String> parametersMap = getParameters(request);
String parameterValue = parametersMap.getFirst(this.name);
return ObjectUtils.nullSafeEquals(this.value, parameterValue);
}
}

@ -16,10 +16,10 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import org.springframework.http.MediaType;
import java.util.List;
import org.springframework.http.MediaType;
/**
* Parses and matches a single media type expression to a request's 'Accept' header.
* <p>
@ -31,25 +31,25 @@ import java.util.List;
*/
class ProduceMediaTypeExpression extends AbstractMediaTypeExpression {
ProduceMediaTypeExpression(String expression) {
super(expression);
}
ProduceMediaTypeExpression(String expression) {
super(expression);
}
ProduceMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
ProduceMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
public final boolean match(List<MediaType> acceptedMediaTypes) {
boolean match = matchMediaType(acceptedMediaTypes);
return (!isNegated() ? match : !match);
}
public final boolean match(List<MediaType> acceptedMediaTypes) {
boolean match = matchMediaType(acceptedMediaTypes);
return (!isNegated() ? match : !match);
}
private boolean matchMediaType(List<MediaType> acceptedMediaTypes) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
return true;
}
}
return false;
}
private boolean matchMediaType(List<MediaType> acceptedMediaTypes) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
return true;
}
}
return false;
}
}

@ -16,10 +16,10 @@
*/
package com.alibaba.cloud.dubbo.http.matcher;
import com.alibaba.cloud.dubbo.metadata.RequestMetadata;
import static com.alibaba.cloud.dubbo.http.util.HttpUtils.toNameAndValues;
import com.alibaba.cloud.dubbo.metadata.RequestMetadata;
/**
* {@link RequestMetadata} {@link HttpRequestMatcher} implementation
*
@ -27,20 +27,21 @@ import static com.alibaba.cloud.dubbo.http.util.HttpUtils.toNameAndValues;
*/
public class RequestMetadataMatcher extends CompositeHttpRequestMatcher {
public RequestMetadataMatcher(RequestMetadata metadata) {
super(
// method
new HttpRequestMethodsMatcher(metadata.getMethod()),
// url
new HttpRequestPathMatcher(metadata.getPath()),
// params
new HttpRequestParamsMatcher(toNameAndValues(metadata.getParams())),
// headers
new HttpRequestHeadersMatcher(toNameAndValues(metadata.getHeaders())),
// consumes
new HttpRequestConsumersMatcher(metadata.getConsumes().toArray(new String[0])),
// produces
new HttpRequestProducesMatcher(metadata.getProduces().toArray(new String[0]))
);
}
public RequestMetadataMatcher(RequestMetadata metadata) {
super(
// method
new HttpRequestMethodsMatcher(metadata.getMethod()),
// url
new HttpRequestPathMatcher(metadata.getPath()),
// params
new HttpRequestParamsMatcher(toNameAndValues(metadata.getParams())),
// headers
new HttpRequestHeadersMatcher(toNameAndValues(metadata.getHeaders())),
// consumes
new HttpRequestConsumersMatcher(
metadata.getConsumes().toArray(new String[0])),
// produces
new HttpRequestProducesMatcher(
metadata.getProduces().toArray(new String[0])));
}
}

@ -16,9 +16,14 @@
*/
package com.alibaba.cloud.dubbo.http.util;
import com.alibaba.cloud.dubbo.http.converter.HttpMessageConverterHolder;
import com.alibaba.cloud.dubbo.metadata.RequestMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import static java.util.Collections.unmodifiableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
@ -30,13 +35,9 @@ import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static java.util.Collections.unmodifiableList;
import com.alibaba.cloud.dubbo.http.converter.HttpMessageConverterHolder;
import com.alibaba.cloud.dubbo.metadata.RequestMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
/**
* {@link HttpMessageConverter} Resolver
@ -45,184 +46,202 @@ import static java.util.Collections.unmodifiableList;
*/
public class HttpMessageConverterResolver {
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
private final List<HttpMessageConverter<?>> messageConverters;
private final List<MediaType> allSupportedMediaTypes;
private final ClassLoader classLoader;
public HttpMessageConverterResolver(List<HttpMessageConverter<?>> messageConverters, ClassLoader classLoader) {
this.messageConverters = messageConverters;
this.allSupportedMediaTypes = getAllSupportedMediaTypes(messageConverters);
this.classLoader = classLoader;
}
public HttpMessageConverterHolder resolve(HttpRequest request, Class<?> parameterType) {
HttpMessageConverterHolder httpMessageConverterHolder = null;
HttpHeaders httpHeaders = request.getHeaders();
MediaType contentType = httpHeaders.getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter;
if (genericConverter.canRead(parameterType, parameterType, contentType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(contentType, converter);
break;
}
} else {
if (converter.canRead(parameterType, contentType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(contentType, converter);
break;
}
}
}
return httpMessageConverterHolder;
}
/**
* Resolve the most match {@link HttpMessageConverter} from {@link RequestMetadata}
*
* @param requestMetadata {@link RequestMetadata}
* @param restMethodMetadata {@link RestMethodMetadata}
* @return
*/
public HttpMessageConverterHolder resolve(RequestMetadata requestMetadata, RestMethodMetadata
restMethodMetadata) {
HttpMessageConverterHolder httpMessageConverterHolder = null;
Class<?> returnValueClass = resolveReturnValueClass(restMethodMetadata);
/**
* @see AbstractMessageConverterMethodProcessor#writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)
*/
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(requestMetadata);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(restMethodMetadata, returnValueClass);
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
return httpMessageConverterHolder;
}
List<MediaType> mediaTypes = new ArrayList<>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
} else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(selectedMediaType, messageConverter);
break;
}
}
}
return httpMessageConverterHolder;
}
public List<MediaType> getAllSupportedMediaTypes() {
return unmodifiableList(allSupportedMediaTypes);
}
private Class<?> resolveReturnValueClass(RestMethodMetadata restMethodMetadata) {
String returnClassName = restMethodMetadata.getMethod().getReturnType();
return ClassUtils.resolveClassName(returnClassName, classLoader);
}
/**
* Resolve the {@link MediaType media-types}
*
* @param requestMetadata {@link RequestMetadata} from client side
* @return non-null {@link List}
*/
private List<MediaType> getAcceptableMediaTypes(RequestMetadata requestMetadata) {
return requestMetadata.getProduceMediaTypes();
}
/**
* Returns
* the media types that can be produced: <ul> <li>The producible media types specified in the request mappings, or
* <li>Media types of configured converters that can write the specific return value, or <li>{@link MediaType#ALL}
* </ul>
*
* @param restMethodMetadata {@link RestMethodMetadata} from server side
* @param returnValueClass the class of return value
* @return non-null {@link List}
*/
private List<MediaType> getProducibleMediaTypes(RestMethodMetadata restMethodMetadata, Class<?>
returnValueClass) {
RequestMetadata serverRequestMetadata = restMethodMetadata.getRequest();
List<MediaType> mediaTypes = serverRequestMetadata.getProduceMediaTypes();
if (!CollectionUtils.isEmpty(mediaTypes)) { // Empty
return mediaTypes;
} else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canWrite(returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
} else {
return Collections.singletonList(MediaType.ALL);
}
}
/**
* Return the media types
* supported by all provided message converters sorted by specificity via {@link
* MediaType#sortBySpecificity(List)}.
*
* @param messageConverters
* @return
*/
private List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
for (HttpMessageConverter<?> messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
}
List<MediaType> result = new ArrayList<MediaType>(allSupportedMediaTypes);
MediaType.sortBySpecificity(result);
return unmodifiableList(result);
}
/**
* Return the more specific of the acceptable and the producible media types
* with the q-value of the former.
*/
private MediaType getMostSpecificMediaType(MediaType acceptType, MediaType produceType) {
MediaType produceTypeToUse = produceType.copyQualityValue(acceptType);
return (MediaType.SPECIFICITY_COMPARATOR.compare(acceptType, produceTypeToUse) <= 0 ? acceptType : produceTypeToUse);
}
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
private final List<HttpMessageConverter<?>> messageConverters;
private final List<MediaType> allSupportedMediaTypes;
private final ClassLoader classLoader;
public HttpMessageConverterResolver(List<HttpMessageConverter<?>> messageConverters,
ClassLoader classLoader) {
this.messageConverters = messageConverters;
this.allSupportedMediaTypes = getAllSupportedMediaTypes(messageConverters);
this.classLoader = classLoader;
}
public HttpMessageConverterHolder resolve(HttpRequest request,
Class<?> parameterType) {
HttpMessageConverterHolder httpMessageConverterHolder = null;
HttpHeaders httpHeaders = request.getHeaders();
MediaType contentType = httpHeaders.getContentType();
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter;
if (genericConverter.canRead(parameterType, parameterType, contentType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(
contentType, converter);
break;
}
}
else {
if (converter.canRead(parameterType, contentType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(
contentType, converter);
break;
}
}
}
return httpMessageConverterHolder;
}
/**
* Resolve the most match {@link HttpMessageConverter} from {@link RequestMetadata}
*
* @param requestMetadata {@link RequestMetadata}
* @param restMethodMetadata {@link RestMethodMetadata}
* @return
*/
public HttpMessageConverterHolder resolve(RequestMetadata requestMetadata,
RestMethodMetadata restMethodMetadata) {
HttpMessageConverterHolder httpMessageConverterHolder = null;
Class<?> returnValueClass = resolveReturnValueClass(restMethodMetadata);
/**
* @see AbstractMessageConverterMethodProcessor#writeWithMessageConverters(Object,
* MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)
*/
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(requestMetadata);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(restMethodMetadata,
returnValueClass);
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes
.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
return httpMessageConverterHolder;
}
List<MediaType> mediaTypes = new ArrayList<>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL)
|| mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
httpMessageConverterHolder = new HttpMessageConverterHolder(
selectedMediaType, messageConverter);
break;
}
}
}
return httpMessageConverterHolder;
}
public List<MediaType> getAllSupportedMediaTypes() {
return unmodifiableList(allSupportedMediaTypes);
}
private Class<?> resolveReturnValueClass(RestMethodMetadata restMethodMetadata) {
String returnClassName = restMethodMetadata.getMethod().getReturnType();
return ClassUtils.resolveClassName(returnClassName, classLoader);
}
/**
* Resolve the {@link MediaType media-types}
*
* @param requestMetadata {@link RequestMetadata} from client side
* @return non-null {@link List}
*/
private List<MediaType> getAcceptableMediaTypes(RequestMetadata requestMetadata) {
return requestMetadata.getProduceMediaTypes();
}
/**
* Returns the media types that can be produced:
* <ul>
* <li>The producible media types specified in the request mappings, or
* <li>Media types of configured converters that can write the specific return value,
* or
* <li>{@link MediaType#ALL}
* </ul>
*
* @param restMethodMetadata {@link RestMethodMetadata} from server side
* @param returnValueClass the class of return value
* @return non-null {@link List}
*/
private List<MediaType> getProducibleMediaTypes(RestMethodMetadata restMethodMetadata,
Class<?> returnValueClass) {
RequestMetadata serverRequestMetadata = restMethodMetadata.getRequest();
List<MediaType> mediaTypes = serverRequestMetadata.getProduceMediaTypes();
if (!CollectionUtils.isEmpty(mediaTypes)) { // Empty
return mediaTypes;
}
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canWrite(returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
/**
* Return the media types supported by all provided message converters sorted by
* specificity via {@link MediaType#sortBySpecificity(List)}.
*
* @param messageConverters
* @return
*/
private List<MediaType> getAllSupportedMediaTypes(
List<HttpMessageConverter<?>> messageConverters) {
Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
for (HttpMessageConverter<?> messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
}
List<MediaType> result = new ArrayList<MediaType>(allSupportedMediaTypes);
MediaType.sortBySpecificity(result);
return unmodifiableList(result);
}
/**
* Return the more specific of the acceptable and the producible media types with the
* q-value of the former.
*/
private MediaType getMostSpecificMediaType(MediaType acceptType,
MediaType produceType) {
MediaType produceTypeToUse = produceType.copyQualityValue(acceptType);
return (MediaType.SPECIFICITY_COMPARATOR.compare(acceptType,
produceTypeToUse) <= 0 ? acceptType : produceTypeToUse);
}
}

@ -16,10 +16,9 @@
*/
package com.alibaba.cloud.dubbo.http.util;
import org.springframework.http.HttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import static org.springframework.util.StringUtils.delimitedListToStringArray;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.util.StringUtils.trimAllWhitespace;
import java.io.UnsupportedEncodingException;
import java.net.URI;
@ -31,9 +30,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.springframework.util.StringUtils.delimitedListToStringArray;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.util.StringUtils.trimAllWhitespace;
import org.springframework.http.HttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Http Utilities class
@ -42,198 +42,206 @@ import static org.springframework.util.StringUtils.trimAllWhitespace;
*/
public abstract class HttpUtils {
private static final String UTF_8 = "UTF-8";
private static final String EQUAL = "=";
private static final String AND = "&";
private static final String SEMICOLON = ";";
private static final String QUESTION_MASK = "?";
/**
* The empty value
*/
private static final String EMPTY_VALUE = "";
/**
* Normalize path:
* <ol>
* <li>To remove query string if presents</li>
* <li>To remove duplicated slash("/") if exists</li>
* </ol>
*
* @param path path to be normalized
* @return a normalized path if required
*/
public static String normalizePath(String path) {
if (!hasText(path)) {
return path;
}
String normalizedPath = path;
int index = normalizedPath.indexOf(QUESTION_MASK);
if (index > -1) {
normalizedPath = normalizedPath.substring(0, index);
}
return StringUtils.replace(normalizedPath, "//", "/");
}
/**
* Get Parameters from the specified {@link HttpRequest request}
*
* @param request the specified {@link HttpRequest request}
* @return
*/
public static MultiValueMap<String, String> getParameters(HttpRequest request) {
URI uri = request.getURI();
return getParameters(uri.getQuery());
}
/**
* Get Parameters from the specified query string.
* <p>
*
* @param queryString The query string
* @return The query parameters
*/
public static MultiValueMap<String, String> getParameters(String queryString) {
return getParameters(delimitedListToStringArray(queryString, AND));
}
/**
* Get Parameters from the specified pairs of name-value.
* <p>
*
* @param pairs The pairs of name-value
* @return The query parameters
*/
public static MultiValueMap<String, String> getParameters(Iterable<String> pairs) {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
if (pairs != null) {
for (String pair : pairs) {
String[] nameAndValue = delimitedListToStringArray(pair, EQUAL);
String name = decode(nameAndValue[0]);
String value = nameAndValue.length < 2 ? null : nameAndValue[1];
value = decode(value);
addParam(parameters, name, value);
}
}
return parameters;
}
/**
* Get Parameters from the specified pairs of name-value.
* <p>
*
* @param pairs The pairs of name-value
* @return The query parameters
*/
public static MultiValueMap<String, String> getParameters(String... pairs) {
return getParameters(Arrays.asList(pairs));
}
// /**
// * Parse a read-only {@link MultiValueMap} of {@link HttpCookie} from {@link HttpHeaders}
// *
// * @param httpHeaders {@link HttpHeaders}
// * @return non-null, the key is a cookie name , the value is {@link HttpCookie}
// */
// public static MultiValueMap<String, HttpCookie> parseCookies(HttpHeaders httpHeaders) {
//
// String cookie = httpHeaders.getFirst(COOKIE);
//
// String[] cookieNameAndValues = StringUtils.delimitedListToStringArray(cookie, SEMICOLON);
//
// MultiValueMap<String, HttpCookie> cookies = new LinkedMultiValueMap<>(cookieNameAndValues.length);
//
// for (String cookeNameAndValue : cookieNameAndValues) {
// String[] nameAndValue = delimitedListToStringArray(trimWhitespace(cookeNameAndValue), EQUAL);
// String name = nameAndValue[0];
// String value = nameAndValue.length < 2 ? null : nameAndValue[1];
// HttpCookie httpCookie = new HttpCookie(name, value);
// cookies.add(name, httpCookie);
// }
//
// return cookies;
// }
/**
* To the name and value line sets
*
* @param nameAndValuesMap {@link MultiValueMap} the map of name and values
* @return non-null
*/
public static Set<String> toNameAndValuesSet(Map<String, List<String>> nameAndValuesMap) {
Set<String> nameAndValues = new LinkedHashSet<>();
for (Map.Entry<String, List<String>> entry : nameAndValuesMap.entrySet()) {
String name = entry.getKey();
List<String> values = entry.getValue();
for (String value : values) {
String nameAndValue = name + EQUAL + value;
nameAndValues.add(nameAndValue);
}
}
return nameAndValues;
}
public static String[] toNameAndValues(Map<String, List<String>> nameAndValuesMap) {
return toNameAndValuesSet(nameAndValuesMap).toArray(new String[0]);
}
/**
* Generate a string of query string from the specified request parameters {@link Map}
*
* @param params the specified request parameters {@link Map}
* @return non-null
*/
public static String toQueryString(Map<String, List<String>> params) {
StringBuilder builder = new StringBuilder();
for (String line : toNameAndValuesSet(params)) {
builder.append(line).append(AND);
}
return builder.toString();
}
/**
* Decode value
*
* @param value the value requires to decode
* @return the decoded value
*/
public static String decode(String value) {
if (value == null) {
return value;
}
String decodedValue = value;
try {
decodedValue = URLDecoder.decode(value, UTF_8);
} catch (UnsupportedEncodingException ex) {
}
return decodedValue;
}
/**
* encode value
*
* @param value the value requires to encode
* @return the encoded value
*/
public static String encode(String value) {
String encodedValue = value;
try {
encodedValue = URLEncoder.encode(value, UTF_8);
} catch (UnsupportedEncodingException ex) {
}
return encodedValue;
}
private static void addParam(MultiValueMap<String, String> paramsMap, String name, String value) {
String paramValue = trimAllWhitespace(value);
if (!StringUtils.hasText(paramValue)) {
paramValue = EMPTY_VALUE;
}
paramsMap.add(trimAllWhitespace(name), paramValue);
}
private static final String UTF_8 = "UTF-8";
private static final String EQUAL = "=";
private static final String AND = "&";
private static final String SEMICOLON = ";";
private static final String QUESTION_MASK = "?";
/**
* The empty value
*/
private static final String EMPTY_VALUE = "";
/**
* Normalize path:
* <ol>
* <li>To remove query string if presents</li>
* <li>To remove duplicated slash("/") if exists</li>
* </ol>
*
* @param path path to be normalized
* @return a normalized path if required
*/
public static String normalizePath(String path) {
if (!hasText(path)) {
return path;
}
String normalizedPath = path;
int index = normalizedPath.indexOf(QUESTION_MASK);
if (index > -1) {
normalizedPath = normalizedPath.substring(0, index);
}
return StringUtils.replace(normalizedPath, "//", "/");
}
/**
* Get Parameters from the specified {@link HttpRequest request}
*
* @param request the specified {@link HttpRequest request}
* @return
*/
public static MultiValueMap<String, String> getParameters(HttpRequest request) {
URI uri = request.getURI();
return getParameters(uri.getQuery());
}
/**
* Get Parameters from the specified query string.
* <p>
*
* @param queryString The query string
* @return The query parameters
*/
public static MultiValueMap<String, String> getParameters(String queryString) {
return getParameters(delimitedListToStringArray(queryString, AND));
}
/**
* Get Parameters from the specified pairs of name-value.
* <p>
*
* @param pairs The pairs of name-value
* @return The query parameters
*/
public static MultiValueMap<String, String> getParameters(Iterable<String> pairs) {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
if (pairs != null) {
for (String pair : pairs) {
String[] nameAndValue = delimitedListToStringArray(pair, EQUAL);
String name = decode(nameAndValue[0]);
String value = nameAndValue.length < 2 ? null : nameAndValue[1];
value = decode(value);
addParam(parameters, name, value);
}
}
return parameters;
}
/**
* Get Parameters from the specified pairs of name-value.
* <p>
*
* @param pairs The pairs of name-value
* @return The query parameters
*/
public static MultiValueMap<String, String> getParameters(String... pairs) {
return getParameters(Arrays.asList(pairs));
}
// /**
// * Parse a read-only {@link MultiValueMap} of {@link HttpCookie} from {@link
// HttpHeaders}
// *
// * @param httpHeaders {@link HttpHeaders}
// * @return non-null, the key is a cookie name , the value is {@link HttpCookie}
// */
// public static MultiValueMap<String, HttpCookie> parseCookies(HttpHeaders
// httpHeaders) {
//
// String cookie = httpHeaders.getFirst(COOKIE);
//
// String[] cookieNameAndValues = StringUtils.delimitedListToStringArray(cookie,
// SEMICOLON);
//
// MultiValueMap<String, HttpCookie> cookies = new
// LinkedMultiValueMap<>(cookieNameAndValues.length);
//
// for (String cookeNameAndValue : cookieNameAndValues) {
// String[] nameAndValue =
// delimitedListToStringArray(trimWhitespace(cookeNameAndValue), EQUAL);
// String name = nameAndValue[0];
// String value = nameAndValue.length < 2 ? null : nameAndValue[1];
// HttpCookie httpCookie = new HttpCookie(name, value);
// cookies.add(name, httpCookie);
// }
//
// return cookies;
// }
/**
* To the name and value line sets
*
* @param nameAndValuesMap {@link MultiValueMap} the map of name and values
* @return non-null
*/
public static Set<String> toNameAndValuesSet(
Map<String, List<String>> nameAndValuesMap) {
Set<String> nameAndValues = new LinkedHashSet<>();
for (Map.Entry<String, List<String>> entry : nameAndValuesMap.entrySet()) {
String name = entry.getKey();
List<String> values = entry.getValue();
for (String value : values) {
String nameAndValue = name + EQUAL + value;
nameAndValues.add(nameAndValue);
}
}
return nameAndValues;
}
public static String[] toNameAndValues(Map<String, List<String>> nameAndValuesMap) {
return toNameAndValuesSet(nameAndValuesMap).toArray(new String[0]);
}
/**
* Generate a string of query string from the specified request parameters {@link Map}
*
* @param params the specified request parameters {@link Map}
* @return non-null
*/
public static String toQueryString(Map<String, List<String>> params) {
StringBuilder builder = new StringBuilder();
for (String line : toNameAndValuesSet(params)) {
builder.append(line).append(AND);
}
return builder.toString();
}
/**
* Decode value
*
* @param value the value requires to decode
* @return the decoded value
*/
public static String decode(String value) {
if (value == null) {
return value;
}
String decodedValue = value;
try {
decodedValue = URLDecoder.decode(value, UTF_8);
}
catch (UnsupportedEncodingException ex) {
}
return decodedValue;
}
/**
* encode value
*
* @param value the value requires to encode
* @return the encoded value
*/
public static String encode(String value) {
String encodedValue = value;
try {
encodedValue = URLEncoder.encode(value, UTF_8);
}
catch (UnsupportedEncodingException ex) {
}
return encodedValue;
}
private static void addParam(MultiValueMap<String, String> paramsMap, String name,
String value) {
String paramValue = trimAllWhitespace(value);
if (!StringUtils.hasText(paramValue)) {
paramValue = EMPTY_VALUE;
}
paramsMap.add(trimAllWhitespace(name), paramValue);
}
}

@ -16,16 +16,16 @@
*/
package com.alibaba.cloud.dubbo.metadata;
import org.apache.dubbo.config.ProtocolConfig;
import org.springframework.beans.factory.ObjectProvider;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_PROTOCOL;
import static org.springframework.util.CollectionUtils.isEmpty;
import java.util.Collection;
import java.util.Iterator;
import java.util.function.Supplier;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_PROTOCOL;
import static org.springframework.util.CollectionUtils.isEmpty;
import org.apache.dubbo.config.ProtocolConfig;
import org.springframework.beans.factory.ObjectProvider;
/**
* Dubbo's {@link ProtocolConfig} {@link Supplier}
@ -34,38 +34,40 @@ import static org.springframework.util.CollectionUtils.isEmpty;
*/
public class DubboProtocolConfigSupplier implements Supplier<ProtocolConfig> {
private final ObjectProvider<Collection<ProtocolConfig>> protocols;
private final ObjectProvider<Collection<ProtocolConfig>> protocols;
public DubboProtocolConfigSupplier(ObjectProvider<Collection<ProtocolConfig>> protocols) {
this.protocols = protocols;
}
public DubboProtocolConfigSupplier(
ObjectProvider<Collection<ProtocolConfig>> protocols) {
this.protocols = protocols;
}
@Override
public ProtocolConfig get() {
ProtocolConfig protocolConfig = null;
Collection<ProtocolConfig> protocols = this.protocols.getIfAvailable();
@Override
public ProtocolConfig get() {
ProtocolConfig protocolConfig = null;
Collection<ProtocolConfig> protocols = this.protocols.getIfAvailable();
if (!isEmpty(protocols)) {
for (ProtocolConfig protocol : protocols) {
String protocolName = protocol.getName();
if (DEFAULT_PROTOCOL.equals(protocolName)) {
protocolConfig = protocol;
break;
}
}
if (!isEmpty(protocols)) {
for (ProtocolConfig protocol : protocols) {
String protocolName = protocol.getName();
if (DEFAULT_PROTOCOL.equals(protocolName)) {
protocolConfig = protocol;
break;
}
}
if (protocolConfig == null) { // If The ProtocolConfig bean named "dubbo" is absent, take first one of them
Iterator<ProtocolConfig> iterator = protocols.iterator();
protocolConfig = iterator.hasNext() ? iterator.next() : null;
}
}
if (protocolConfig == null) { // If The ProtocolConfig bean named "dubbo" is
// absent, take first one of them
Iterator<ProtocolConfig> iterator = protocols.iterator();
protocolConfig = iterator.hasNext() ? iterator.next() : null;
}
}
if (protocolConfig == null) {
protocolConfig = new ProtocolConfig();
protocolConfig.setName(DEFAULT_PROTOCOL);
protocolConfig.setPort(-1);
}
if (protocolConfig == null) {
protocolConfig = new ProtocolConfig();
protocolConfig.setName(DEFAULT_PROTOCOL);
protocolConfig.setPort(-1);
}
return protocolConfig;
}
return protocolConfig;
}
}

@ -25,38 +25,39 @@ import java.util.Objects;
*/
public class DubboRestServiceMetadata {
private final ServiceRestMetadata serviceRestMetadata;
private final RestMethodMetadata restMethodMetadata;
public DubboRestServiceMetadata(ServiceRestMetadata serviceRestMetadata, RestMethodMetadata restMethodMetadata) {
this.serviceRestMetadata = serviceRestMetadata;
this.restMethodMetadata = restMethodMetadata;
}
public ServiceRestMetadata getServiceRestMetadata() {
return serviceRestMetadata;
}
public RestMethodMetadata getRestMethodMetadata() {
return restMethodMetadata;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DubboRestServiceMetadata)) {
return false;
}
DubboRestServiceMetadata that = (DubboRestServiceMetadata) o;
return Objects.equals(serviceRestMetadata, that.serviceRestMetadata) &&
Objects.equals(restMethodMetadata, that.restMethodMetadata);
}
@Override
public int hashCode() {
return Objects.hash(serviceRestMetadata, restMethodMetadata);
}
private final ServiceRestMetadata serviceRestMetadata;
private final RestMethodMetadata restMethodMetadata;
public DubboRestServiceMetadata(ServiceRestMetadata serviceRestMetadata,
RestMethodMetadata restMethodMetadata) {
this.serviceRestMetadata = serviceRestMetadata;
this.restMethodMetadata = restMethodMetadata;
}
public ServiceRestMetadata getServiceRestMetadata() {
return serviceRestMetadata;
}
public RestMethodMetadata getRestMethodMetadata() {
return restMethodMetadata;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DubboRestServiceMetadata)) {
return false;
}
DubboRestServiceMetadata that = (DubboRestServiceMetadata) o;
return Objects.equals(serviceRestMetadata, that.serviceRestMetadata)
&& Objects.equals(restMethodMetadata, that.restMethodMetadata);
}
@Override
public int hashCode() {
return Objects.hash(serviceRestMetadata, restMethodMetadata);
}
}

@ -16,13 +16,13 @@
*/
package com.alibaba.cloud.dubbo.metadata;
import com.alibaba.cloud.dubbo.annotation.DubboTransported;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.alibaba.cloud.dubbo.annotation.DubboTransported;
/**
* {@link MethodMetadata} annotated {@link DubboTransported @DubboTransported}
*
@ -30,66 +30,66 @@ import java.util.Objects;
*/
public class DubboTransportedMethodMetadata {
private final MethodMetadata methodMetadata;
private final Map<String, Object> attributes;
public DubboTransportedMethodMetadata(Method method, Map<String, Object> attributes) {
this.methodMetadata = new MethodMetadata(method);
this.attributes = attributes;
}
public String getName() {
return methodMetadata.getName();
}
public void setName(String name) {
methodMetadata.setName(name);
}
public String getReturnType() {
return methodMetadata.getReturnType();
}
public void setReturnType(String returnType) {
methodMetadata.setReturnType(returnType);
}
public List<MethodParameterMetadata> getParams() {
return methodMetadata.getParams();
}
public void setParams(List<MethodParameterMetadata> params) {
methodMetadata.setParams(params);
}
public Method getMethod() {
return methodMetadata.getMethod();
}
public MethodMetadata getMethodMetadata() {
return methodMetadata;
}
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DubboTransportedMethodMetadata)) {
return false;
}
DubboTransportedMethodMetadata that = (DubboTransportedMethodMetadata) o;
return Objects.equals(methodMetadata, that.methodMetadata) &&
Objects.equals(attributes, that.attributes);
}
@Override
public int hashCode() {
return Objects.hash(methodMetadata, attributes);
}
private final MethodMetadata methodMetadata;
private final Map<String, Object> attributes;
public DubboTransportedMethodMetadata(Method method, Map<String, Object> attributes) {
this.methodMetadata = new MethodMetadata(method);
this.attributes = attributes;
}
public String getName() {
return methodMetadata.getName();
}
public void setName(String name) {
methodMetadata.setName(name);
}
public String getReturnType() {
return methodMetadata.getReturnType();
}
public void setReturnType(String returnType) {
methodMetadata.setReturnType(returnType);
}
public List<MethodParameterMetadata> getParams() {
return methodMetadata.getParams();
}
public void setParams(List<MethodParameterMetadata> params) {
methodMetadata.setParams(params);
}
public Method getMethod() {
return methodMetadata.getMethod();
}
public MethodMetadata getMethodMetadata() {
return methodMetadata;
}
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DubboTransportedMethodMetadata)) {
return false;
}
DubboTransportedMethodMetadata that = (DubboTransportedMethodMetadata) o;
return Objects.equals(methodMetadata, that.methodMetadata)
&& Objects.equals(attributes, that.attributes);
}
@Override
public int hashCode() {
return Objects.hash(methodMetadata, attributes);
}
}

@ -16,10 +16,6 @@
*/
package com.alibaba.cloud.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
@ -28,6 +24,10 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* {@link Method} Metadata
*
@ -36,104 +36,101 @@ import java.util.Objects;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MethodMetadata {
private String name;
@JsonProperty("return-type")
private String returnType;
private List<MethodParameterMetadata> params;
@JsonIgnore
private Method method;
public MethodMetadata() {
this.params = new LinkedList<>();
}
public MethodMetadata(Method method) {
this.name = method.getName();
this.returnType = method.getReturnType().getName();
this.params = initParameters(method);
this.method = method;
}
private List<MethodParameterMetadata> initParameters(Method method) {
int parameterCount = method.getParameterCount();
if (parameterCount < 1) {
return Collections.emptyList();
}
List<MethodParameterMetadata> params = new ArrayList<>(parameterCount);
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameterCount; i++) {
Parameter parameter = parameters[i];
MethodParameterMetadata param = toMethodParameterMetadata(i, parameter);
params.add(param);
}
return params;
}
private MethodParameterMetadata toMethodParameterMetadata(int index, Parameter parameter) {
MethodParameterMetadata metadata = new MethodParameterMetadata();
metadata.setIndex(index);
metadata.setName(parameter.getName());
metadata.setType(parameter.getType().getTypeName());
return metadata;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
public List<MethodParameterMetadata> getParams() {
return params;
}
public void setParams(List<MethodParameterMetadata> params) {
this.params = params;
}
public Method getMethod() {
return method;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MethodMetadata that = (MethodMetadata) o;
return Objects.equals(name, that.name) &&
Objects.equals(returnType, that.returnType) &&
Objects.equals(params, that.params);
}
@Override
public int hashCode() {
return Objects.hash(name, returnType, params);
}
@Override
public String toString() {
return "MethodMetadata{" +
"name='" + name + '\'' +
", returnType='" + returnType + '\'' +
", params=" + params +
", method=" + method +
'}';
}
private String name;
@JsonProperty("return-type")
private String returnType;
private List<MethodParameterMetadata> params;
@JsonIgnore
private Method method;
public MethodMetadata() {
this.params = new LinkedList<>();
}
public MethodMetadata(Method method) {
this.name = method.getName();
this.returnType = method.getReturnType().getName();
this.params = initParameters(method);
this.method = method;
}
private List<MethodParameterMetadata> initParameters(Method method) {
int parameterCount = method.getParameterCount();
if (parameterCount < 1) {
return Collections.emptyList();
}
List<MethodParameterMetadata> params = new ArrayList<>(parameterCount);
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameterCount; i++) {
Parameter parameter = parameters[i];
MethodParameterMetadata param = toMethodParameterMetadata(i, parameter);
params.add(param);
}
return params;
}
private MethodParameterMetadata toMethodParameterMetadata(int index,
Parameter parameter) {
MethodParameterMetadata metadata = new MethodParameterMetadata();
metadata.setIndex(index);
metadata.setName(parameter.getName());
metadata.setType(parameter.getType().getTypeName());
return metadata;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
public List<MethodParameterMetadata> getParams() {
return params;
}
public void setParams(List<MethodParameterMetadata> params) {
this.params = params;
}
public Method getMethod() {
return method;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MethodMetadata that = (MethodMetadata) o;
return Objects.equals(name, that.name)
&& Objects.equals(returnType, that.returnType)
&& Objects.equals(params, that.params);
}
@Override
public int hashCode() {
return Objects.hash(name, returnType, params);
}
@Override
public String toString() {
return "MethodMetadata{" + "name='" + name + '\'' + ", returnType='" + returnType
+ '\'' + ", params=" + params + ", method=" + method + '}';
}
}

@ -16,11 +16,11 @@
*/
package com.alibaba.cloud.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.lang.reflect.Method;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* {@link Method} Parameter Metadata
*
@ -29,61 +29,57 @@ import java.util.Objects;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MethodParameterMetadata {
private int index;
private int index;
private String name;
private String name;
private String type;
private String type;
public int getIndex() {
return index;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public void setIndex(int index) {
this.index = index;
}
public String getName() {
return name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public void setType(String type) {
this.type = type;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MethodParameterMetadata that = (MethodParameterMetadata) o;
return index == that.index &&
Objects.equals(name, that.name) &&
Objects.equals(type, that.type);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MethodParameterMetadata that = (MethodParameterMetadata) o;
return index == that.index && Objects.equals(name, that.name)
&& Objects.equals(type, that.type);
}
@Override
public int hashCode() {
return Objects.hash(index, name, type);
}
@Override
public int hashCode() {
return Objects.hash(index, name, type);
}
@Override
public String toString() {
return "MethodParameterMetadata{" +
"index=" + index +
", name='" + name + '\'' +
", type='" + type + '\'' +
'}';
}
@Override
public String toString() {
return "MethodParameterMetadata{" + "index=" + index + ", name='" + name + '\''
+ ", type='" + type + '\'' + '}';
}
}

@ -16,14 +16,8 @@
*/
package com.alibaba.cloud.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import feign.RequestTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static com.alibaba.cloud.dubbo.http.util.HttpUtils.normalizePath;
import static org.springframework.http.MediaType.parseMediaTypes;
import java.util.ArrayList;
import java.util.Collection;
@ -37,8 +31,16 @@ import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import static com.alibaba.cloud.dubbo.http.util.HttpUtils.normalizePath;
import static org.springframework.http.MediaType.parseMediaTypes;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import feign.RequestTemplate;
/**
* Request Metadata
@ -47,226 +49,226 @@ import static org.springframework.http.MediaType.parseMediaTypes;
*/
public class RequestMetadata {
private String method;
private String path;
@JsonProperty("params")
private MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
@JsonProperty("headers")
private HttpHeaders headers = new HttpHeaders();
private Set<String> consumes = new LinkedHashSet<>();
private Set<String> produces = new LinkedHashSet<>();
public RequestMetadata() {
}
public RequestMetadata(RequestTemplate requestTemplate) {
setMethod(requestTemplate.method());
setPath(requestTemplate.url());
params(requestTemplate.queries());
headers(requestTemplate.headers());
}
/**
* Get the best matched {@link RequestMetadata} via specified {@link RequestMetadata}
*
* @param requestMetadataMap the source of {@link NavigableMap}
* @param requestMetadata the match object
* @return if not matched, return <code>null</code>
*/
public static RequestMetadata getBestMatch(NavigableMap<RequestMetadata, RequestMetadata> requestMetadataMap,
RequestMetadata requestMetadata) {
RequestMetadata key = requestMetadata;
RequestMetadata result = requestMetadataMap.get(key);
if (result == null) {
SortedMap<RequestMetadata, RequestMetadata> headMap = requestMetadataMap.headMap(key, true);
result = headMap.isEmpty() ? null : requestMetadataMap.get(headMap.lastKey());
}
return result;
}
private static void add(String key, String value, MultiValueMap<String, String> destination) {
destination.add(key, value);
}
private static <T extends Collection<String>> void addAll(Map<String, T> source,
MultiValueMap<String, String> destination) {
for (Map.Entry<String, T> entry : source.entrySet()) {
String key = entry.getKey();
for (String value : entry.getValue()) {
add(key, value, destination);
}
}
}
private static void mediaTypes(HttpHeaders httpHeaders, String headerName, Collection<String> destination) {
List<String> value = httpHeaders.get(headerName);
List<MediaType> mediaTypes = parseMediaTypes(value);
destination.addAll(toMediaTypeValues(mediaTypes));
}
private static List<String> toMediaTypeValues(List<MediaType> mediaTypes) {
List<String> list = new ArrayList<>(mediaTypes.size());
for (MediaType mediaType : mediaTypes) {
list.add(mediaType.toString());
}
return list;
}
private static List<MediaType> toMediaTypes(Collection<String> mediaTypeValues) {
if (mediaTypeValues.isEmpty()) {
return Collections.singletonList(MediaType.ALL);
}
return parseMediaTypes(new LinkedList<>(mediaTypeValues));
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method.toUpperCase();
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = normalizePath(path);
}
public MultiValueMap<String, String> getParams() {
return params;
}
public void setParams(Map<String, List<String>> params) {
params(params);
}
public Map<String, List<String>> getHeaders() {
return headers;
}
public void setHeaders(Map<String, List<String>> headers) {
headers(headers);
}
public Set<String> getConsumes() {
return consumes;
}
public void setConsumes(Set<String> consumes) {
this.consumes = consumes;
}
public Set<String> getProduces() {
return produces;
}
public void setProduces(Set<String> produces) {
this.produces = produces;
}
// @JsonIgnore properties
@JsonIgnore
public Set<String> getParamNames() {
return params.keySet();
}
@JsonIgnore
public Set<String> getHeaderNames() {
return headers.keySet();
}
@JsonIgnore
public List<MediaType> getConsumeMediaTypes() {
return toMediaTypes(consumes);
}
@JsonIgnore
public List<MediaType> getProduceMediaTypes() {
return toMediaTypes(produces);
}
public String getParameter(String name) {
return this.params.getFirst(name);
}
public String getHeader(String name) {
return this.headers.getFirst(name);
}
public RequestMetadata addParam(String name, String value) {
add(name, value, this.params);
return this;
}
public RequestMetadata addHeader(String name, String value) {
add(name, value, this.headers);
return this;
}
private <T extends Collection<String>> RequestMetadata params(Map<String, T> params) {
addAll(params, this.params);
return this;
}
private <T extends Collection<String>> RequestMetadata headers(Map<String, T> headers) {
if (!CollectionUtils.isEmpty(headers)) {
HttpHeaders httpHeaders = new HttpHeaders();
// Add all headers
addAll(headers, httpHeaders);
// Handles "Content-Type" and "Accept" headers if present
mediaTypes(httpHeaders, HttpHeaders.CONTENT_TYPE, this.consumes);
mediaTypes(httpHeaders, HttpHeaders.ACCEPT, this.produces);
this.headers.putAll(httpHeaders);
}
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RequestMetadata)) {
return false;
}
RequestMetadata that = (RequestMetadata) o;
return Objects.equals(method, that.method) &&
Objects.equals(path, that.path) &&
Objects.equals(consumes, that.consumes) &&
Objects.equals(produces, that.produces) &&
// Metadata should not compare the values
Objects.equals(getParamNames(), that.getParamNames()) &&
Objects.equals(getHeaderNames(), that.getHeaderNames());
}
@Override
public int hashCode() {
// The values of metadata should not use for the hashCode() method
return Objects.hash(method, path, consumes, produces, getParamNames(), getHeaderNames());
}
@Override
public String toString() {
return "RequestMetadata{" +
"method='" + method + '\'' +
", path='" + path + '\'' +
", params=" + params +
", headers=" + headers +
", consumes=" + consumes +
", produces=" + produces +
'}';
}
private String method;
private String path;
@JsonProperty("params")
private MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
@JsonProperty("headers")
private HttpHeaders headers = new HttpHeaders();
private Set<String> consumes = new LinkedHashSet<>();
private Set<String> produces = new LinkedHashSet<>();
public RequestMetadata() {
}
public RequestMetadata(RequestTemplate requestTemplate) {
setMethod(requestTemplate.method());
setPath(requestTemplate.url());
params(requestTemplate.queries());
headers(requestTemplate.headers());
}
/**
* Get the best matched {@link RequestMetadata} via specified {@link RequestMetadata}
*
* @param requestMetadataMap the source of {@link NavigableMap}
* @param requestMetadata the match object
* @return if not matched, return <code>null</code>
*/
public static RequestMetadata getBestMatch(
NavigableMap<RequestMetadata, RequestMetadata> requestMetadataMap,
RequestMetadata requestMetadata) {
RequestMetadata key = requestMetadata;
RequestMetadata result = requestMetadataMap.get(key);
if (result == null) {
SortedMap<RequestMetadata, RequestMetadata> headMap = requestMetadataMap
.headMap(key, true);
result = headMap.isEmpty() ? null : requestMetadataMap.get(headMap.lastKey());
}
return result;
}
private static void add(String key, String value,
MultiValueMap<String, String> destination) {
destination.add(key, value);
}
private static <T extends Collection<String>> void addAll(Map<String, T> source,
MultiValueMap<String, String> destination) {
for (Map.Entry<String, T> entry : source.entrySet()) {
String key = entry.getKey();
for (String value : entry.getValue()) {
add(key, value, destination);
}
}
}
private static void mediaTypes(HttpHeaders httpHeaders, String headerName,
Collection<String> destination) {
List<String> value = httpHeaders.get(headerName);
List<MediaType> mediaTypes = parseMediaTypes(value);
destination.addAll(toMediaTypeValues(mediaTypes));
}
private static List<String> toMediaTypeValues(List<MediaType> mediaTypes) {
List<String> list = new ArrayList<>(mediaTypes.size());
for (MediaType mediaType : mediaTypes) {
list.add(mediaType.toString());
}
return list;
}
private static List<MediaType> toMediaTypes(Collection<String> mediaTypeValues) {
if (mediaTypeValues.isEmpty()) {
return Collections.singletonList(MediaType.ALL);
}
return parseMediaTypes(new LinkedList<>(mediaTypeValues));
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method.toUpperCase();
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = normalizePath(path);
}
public MultiValueMap<String, String> getParams() {
return params;
}
public void setParams(Map<String, List<String>> params) {
params(params);
}
public Map<String, List<String>> getHeaders() {
return headers;
}
public void setHeaders(Map<String, List<String>> headers) {
headers(headers);
}
public Set<String> getConsumes() {
return consumes;
}
public void setConsumes(Set<String> consumes) {
this.consumes = consumes;
}
public Set<String> getProduces() {
return produces;
}
public void setProduces(Set<String> produces) {
this.produces = produces;
}
// @JsonIgnore properties
@JsonIgnore
public Set<String> getParamNames() {
return params.keySet();
}
@JsonIgnore
public Set<String> getHeaderNames() {
return headers.keySet();
}
@JsonIgnore
public List<MediaType> getConsumeMediaTypes() {
return toMediaTypes(consumes);
}
@JsonIgnore
public List<MediaType> getProduceMediaTypes() {
return toMediaTypes(produces);
}
public String getParameter(String name) {
return this.params.getFirst(name);
}
public String getHeader(String name) {
return this.headers.getFirst(name);
}
public RequestMetadata addParam(String name, String value) {
add(name, value, this.params);
return this;
}
public RequestMetadata addHeader(String name, String value) {
add(name, value, this.headers);
return this;
}
private <T extends Collection<String>> RequestMetadata params(Map<String, T> params) {
addAll(params, this.params);
return this;
}
private <T extends Collection<String>> RequestMetadata headers(
Map<String, T> headers) {
if (!CollectionUtils.isEmpty(headers)) {
HttpHeaders httpHeaders = new HttpHeaders();
// Add all headers
addAll(headers, httpHeaders);
// Handles "Content-Type" and "Accept" headers if present
mediaTypes(httpHeaders, HttpHeaders.CONTENT_TYPE, this.consumes);
mediaTypes(httpHeaders, HttpHeaders.ACCEPT, this.produces);
this.headers.putAll(httpHeaders);
}
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RequestMetadata)) {
return false;
}
RequestMetadata that = (RequestMetadata) o;
return Objects.equals(method, that.method) && Objects.equals(path, that.path)
&& Objects.equals(consumes, that.consumes)
&& Objects.equals(produces, that.produces) &&
// Metadata should not compare the values
Objects.equals(getParamNames(), that.getParamNames())
&& Objects.equals(getHeaderNames(), that.getHeaderNames());
}
@Override
public int hashCode() {
// The values of metadata should not use for the hashCode() method
return Objects.hash(method, path, consumes, produces, getParamNames(),
getHeaderNames());
}
@Override
public String toString() {
return "RequestMetadata{" + "method='" + method + '\'' + ", path='" + path + '\''
+ ", params=" + params + ", headers=" + headers + ", consumes=" + consumes
+ ", produces=" + produces + '}';
}
}

@ -16,16 +16,17 @@
*/
package com.alibaba.cloud.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.core.ResolvableType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.springframework.core.ResolvableType;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Method Request Metadata
*
@ -34,205 +35,199 @@ import java.util.Objects;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RestMethodMetadata {
private MethodMetadata method;
private MethodMetadata method;
private RequestMetadata request;
private RequestMetadata request;
@JsonProperty("url-index")
private Integer urlIndex;
@JsonProperty("url-index")
private Integer urlIndex;
@JsonProperty("setBody-index")
private Integer bodyIndex;
@JsonProperty("setBody-index")
private Integer bodyIndex;
@JsonProperty("header-map-index")
private Integer headerMapIndex;
@JsonProperty("header-map-index")
private Integer headerMapIndex;
@JsonProperty("query-map-index")
private Integer queryMapIndex;
@JsonProperty("query-map-index")
private Integer queryMapIndex;
@JsonProperty("query-map-encoded")
private boolean queryMapEncoded;
@JsonProperty("query-map-encoded")
private boolean queryMapEncoded;
@JsonProperty("return-type")
private String returnType;
@JsonProperty("return-type")
private String returnType;
@JsonProperty("setBody-type")
private String bodyType;
@JsonProperty("setBody-type")
private String bodyType;
@JsonProperty("index-to-name")
private Map<Integer, Collection<String>> indexToName;
@JsonProperty("index-to-name")
private Map<Integer, Collection<String>> indexToName;
@JsonProperty("form-params")
private List<String> formParams;
@JsonProperty("form-params")
private List<String> formParams;
@JsonProperty("index-to-encoded")
private Map<Integer, Boolean> indexToEncoded;
@JsonProperty("index-to-encoded")
private Map<Integer, Boolean> indexToEncoded;
public RestMethodMetadata() {
}
public RestMethodMetadata() {
}
public RestMethodMetadata(feign.MethodMetadata methodMetadata) {
this.request = new RequestMetadata(methodMetadata.template());
this.urlIndex = methodMetadata.urlIndex();
this.bodyIndex = methodMetadata.bodyIndex();
this.headerMapIndex = methodMetadata.headerMapIndex();
this.queryMapEncoded = methodMetadata.queryMapEncoded();
this.queryMapEncoded = methodMetadata.queryMapEncoded();
this.returnType = getClassName(methodMetadata.returnType());
this.bodyType = getClassName(methodMetadata.bodyType());
this.indexToName = methodMetadata.indexToName();
this.formParams = methodMetadata.formParams();
this.indexToEncoded = methodMetadata.indexToEncoded();
}
public RestMethodMetadata(feign.MethodMetadata methodMetadata) {
this.request = new RequestMetadata(methodMetadata.template());
this.urlIndex = methodMetadata.urlIndex();
this.bodyIndex = methodMetadata.bodyIndex();
this.headerMapIndex = methodMetadata.headerMapIndex();
this.queryMapEncoded = methodMetadata.queryMapEncoded();
this.queryMapEncoded = methodMetadata.queryMapEncoded();
this.returnType = getClassName(methodMetadata.returnType());
this.bodyType = getClassName(methodMetadata.bodyType());
this.indexToName = methodMetadata.indexToName();
this.formParams = methodMetadata.formParams();
this.indexToEncoded = methodMetadata.indexToEncoded();
}
public MethodMetadata getMethod() {
return method;
}
public MethodMetadata getMethod() {
return method;
}
public void setMethod(MethodMetadata method) {
this.method = method;
}
public RequestMetadata getRequest() {
return request;
}
public void setRequest(RequestMetadata request) {
this.request = request;
}
public Map<Integer, Collection<String>> getIndexToName() {
return indexToName;
}
public void setIndexToName(Map<Integer, Collection<String>> indexToName) {
this.indexToName = indexToName;
}
public Integer getUrlIndex() {
return urlIndex;
}
public void setUrlIndex(Integer urlIndex) {
this.urlIndex = urlIndex;
}
public Integer getBodyIndex() {
return bodyIndex;
}
public void setBodyIndex(Integer bodyIndex) {
this.bodyIndex = bodyIndex;
}
public Integer getHeaderMapIndex() {
return headerMapIndex;
}
public void setHeaderMapIndex(Integer headerMapIndex) {
this.headerMapIndex = headerMapIndex;
}
public Integer getQueryMapIndex() {
return queryMapIndex;
}
public void setQueryMapIndex(Integer queryMapIndex) {
this.queryMapIndex = queryMapIndex;
}
public boolean isQueryMapEncoded() {
return queryMapEncoded;
}
public void setQueryMapEncoded(boolean queryMapEncoded) {
this.queryMapEncoded = queryMapEncoded;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
public String getBodyType() {
return bodyType;
}
public void setBodyType(String bodyType) {
this.bodyType = bodyType;
}
public List<String> getFormParams() {
return formParams;
}
public void setFormParams(List<String> formParams) {
this.formParams = formParams;
}
public Map<Integer, Boolean> getIndexToEncoded() {
return indexToEncoded;
}
public void setIndexToEncoded(Map<Integer, Boolean> indexToEncoded) {
this.indexToEncoded = indexToEncoded;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RestMethodMetadata)) {
return false;
}
RestMethodMetadata that = (RestMethodMetadata) o;
return queryMapEncoded == that.queryMapEncoded &&
Objects.equals(method, that.method) &&
Objects.equals(request, that.request) &&
Objects.equals(urlIndex, that.urlIndex) &&
Objects.equals(bodyIndex, that.bodyIndex) &&
Objects.equals(headerMapIndex, that.headerMapIndex) &&
Objects.equals(queryMapIndex, that.queryMapIndex) &&
Objects.equals(returnType, that.returnType) &&
Objects.equals(bodyType, that.bodyType) &&
Objects.equals(indexToName, that.indexToName) &&
Objects.equals(formParams, that.formParams) &&
Objects.equals(indexToEncoded, that.indexToEncoded);
}
@Override
public int hashCode() {
return Objects.hash(method, request, urlIndex, bodyIndex, headerMapIndex, queryMapIndex, queryMapEncoded,
returnType, bodyType, indexToName, formParams, indexToEncoded);
}
private String getClassName(Type type) {
if (type == null) {
return null;
}
ResolvableType resolvableType = ResolvableType.forType(type);
return resolvableType.resolve().getName();
}
@Override
public String toString() {
return "RestMethodMetadata{" +
"method=" + method +
", request=" + request +
", urlIndex=" + urlIndex +
", bodyIndex=" + bodyIndex +
", headerMapIndex=" + headerMapIndex +
", queryMapIndex=" + queryMapIndex +
", queryMapEncoded=" + queryMapEncoded +
", returnType='" + returnType + '\'' +
", bodyType='" + bodyType + '\'' +
", indexToName=" + indexToName +
", formParams=" + formParams +
", indexToEncoded=" + indexToEncoded +
'}';
}
public void setMethod(MethodMetadata method) {
this.method = method;
}
public RequestMetadata getRequest() {
return request;
}
public void setRequest(RequestMetadata request) {
this.request = request;
}
public Map<Integer, Collection<String>> getIndexToName() {
return indexToName;
}
public void setIndexToName(Map<Integer, Collection<String>> indexToName) {
this.indexToName = indexToName;
}
public Integer getUrlIndex() {
return urlIndex;
}
public void setUrlIndex(Integer urlIndex) {
this.urlIndex = urlIndex;
}
public Integer getBodyIndex() {
return bodyIndex;
}
public void setBodyIndex(Integer bodyIndex) {
this.bodyIndex = bodyIndex;
}
public Integer getHeaderMapIndex() {
return headerMapIndex;
}
public void setHeaderMapIndex(Integer headerMapIndex) {
this.headerMapIndex = headerMapIndex;
}
public Integer getQueryMapIndex() {
return queryMapIndex;
}
public void setQueryMapIndex(Integer queryMapIndex) {
this.queryMapIndex = queryMapIndex;
}
public boolean isQueryMapEncoded() {
return queryMapEncoded;
}
public void setQueryMapEncoded(boolean queryMapEncoded) {
this.queryMapEncoded = queryMapEncoded;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
public String getBodyType() {
return bodyType;
}
public void setBodyType(String bodyType) {
this.bodyType = bodyType;
}
public List<String> getFormParams() {
return formParams;
}
public void setFormParams(List<String> formParams) {
this.formParams = formParams;
}
public Map<Integer, Boolean> getIndexToEncoded() {
return indexToEncoded;
}
public void setIndexToEncoded(Map<Integer, Boolean> indexToEncoded) {
this.indexToEncoded = indexToEncoded;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RestMethodMetadata)) {
return false;
}
RestMethodMetadata that = (RestMethodMetadata) o;
return queryMapEncoded == that.queryMapEncoded
&& Objects.equals(method, that.method)
&& Objects.equals(request, that.request)
&& Objects.equals(urlIndex, that.urlIndex)
&& Objects.equals(bodyIndex, that.bodyIndex)
&& Objects.equals(headerMapIndex, that.headerMapIndex)
&& Objects.equals(queryMapIndex, that.queryMapIndex)
&& Objects.equals(returnType, that.returnType)
&& Objects.equals(bodyType, that.bodyType)
&& Objects.equals(indexToName, that.indexToName)
&& Objects.equals(formParams, that.formParams)
&& Objects.equals(indexToEncoded, that.indexToEncoded);
}
@Override
public int hashCode() {
return Objects.hash(method, request, urlIndex, bodyIndex, headerMapIndex,
queryMapIndex, queryMapEncoded, returnType, bodyType, indexToName,
formParams, indexToEncoded);
}
private String getClassName(Type type) {
if (type == null) {
return null;
}
ResolvableType resolvableType = ResolvableType.forType(type);
return resolvableType.resolve().getName();
}
@Override
public String toString() {
return "RestMethodMetadata{" + "method=" + method + ", request=" + request
+ ", urlIndex=" + urlIndex + ", bodyIndex=" + bodyIndex
+ ", headerMapIndex=" + headerMapIndex + ", queryMapIndex="
+ queryMapIndex + ", queryMapEncoded=" + queryMapEncoded
+ ", returnType='" + returnType + '\'' + ", bodyType='" + bodyType + '\''
+ ", indexToName=" + indexToName + ", formParams=" + formParams
+ ", indexToEncoded=" + indexToEncoded + '}';
}
}

@ -16,11 +16,11 @@
*/
package com.alibaba.cloud.dubbo.metadata;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Objects;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* Service Rest Metadata
*
@ -30,41 +30,40 @@ import java.util.Set;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ServiceRestMetadata {
private String url;
private String url;
private Set<RestMethodMetadata> meta;
private Set<RestMethodMetadata> meta;
public String getUrl() {
return url;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public void setUrl(String url) {
this.url = url;
}
public Set<RestMethodMetadata> getMeta() {
return meta;
}
public Set<RestMethodMetadata> getMeta() {
return meta;
}
public void setMeta(Set<RestMethodMetadata> meta) {
this.meta = meta;
}
public void setMeta(Set<RestMethodMetadata> meta) {
this.meta = meta;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ServiceRestMetadata)) {
return false;
}
ServiceRestMetadata that = (ServiceRestMetadata) o;
return Objects.equals(url, that.url) &&
Objects.equals(meta, that.meta);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ServiceRestMetadata)) {
return false;
}
ServiceRestMetadata that = (ServiceRestMetadata) o;
return Objects.equals(url, that.url) && Objects.equals(meta, that.meta);
}
@Override
public int hashCode() {
return Objects.hash(url, meta);
}
@Override
public int hashCode() {
return Objects.hash(url, meta);
}
}

@ -16,21 +16,6 @@
*/
package com.alibaba.cloud.dubbo.metadata.resolver;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.spring.ServiceBean;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import com.alibaba.cloud.dubbo.metadata.ServiceRestMetadata;
import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
import feign.Util;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
@ -42,155 +27,179 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.spring.ServiceBean;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.util.ClassUtils;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import com.alibaba.cloud.dubbo.metadata.ServiceRestMetadata;
import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
import feign.Util;
/**
* The metadata resolver for {@link Feign} for {@link ServiceBean Dubbo Service Bean} in the provider side.
* The metadata resolver for {@link Feign} for {@link ServiceBean Dubbo Service Bean} in
* the provider side.
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboServiceBeanMetadataResolver implements BeanClassLoaderAware, SmartInitializingSingleton,
MetadataResolver {
public class DubboServiceBeanMetadataResolver
implements BeanClassLoaderAware, SmartInitializingSingleton, MetadataResolver {
private static final String[] CONTRACT_CLASS_NAMES = {
"feign.jaxrs2.JAXRS2Contract",
"org.springframework.cloud.openfeign.support.SpringMvcContract",
};
private final ObjectProvider<Contract> contractObjectProvider;
private ClassLoader classLoader;
/**
* Feign Contracts
*/
private Collection<Contract> contracts;
public DubboServiceBeanMetadataResolver(ObjectProvider<Contract> contractObjectProvider) {
this.contractObjectProvider = contractObjectProvider;
}
@Override
public void afterSingletonsInstantiated() {
LinkedList<Contract> contracts = new LinkedList<>();
// Add injected Contract if available, for example SpringMvcContract Bean under Spring Cloud Open Feign
Contract contract = contractObjectProvider.getIfAvailable();
if (contract != null) {
contracts.add(contract);
}
Stream.of(CONTRACT_CLASS_NAMES)
.filter(this::isClassPresent) // filter the existed classes
.map(this::loadContractClass) // load Contract Class
.map(this::createContract) // createServiceInstance instance by the specified class
.forEach(contracts::add); // add the Contract instance into contracts
this.contracts = Collections.unmodifiableCollection(contracts);
}
private Contract createContract(Class<?> contractClassName) {
return (Contract) BeanUtils.instantiateClass(contractClassName);
}
private Class<?> loadContractClass(String contractClassName) {
return ClassUtils.resolveClassName(contractClassName, classLoader);
}
private boolean isClassPresent(String className) {
return ClassUtils.isPresent(className, classLoader);
}
@Override
public Set<ServiceRestMetadata> resolveServiceRestMetadata(ServiceBean serviceBean) {
Object bean = serviceBean.getRef();
Class<?> beanType = bean.getClass();
Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>();
Set<RestMethodMetadata> methodRestMetadata = resolveMethodRestMetadata(beanType);
List<URL> urls = serviceBean.getExportedUrls();
urls.stream()
.map(URL::toString)
.forEach(url -> {
ServiceRestMetadata metadata = new ServiceRestMetadata();
metadata.setUrl(url);
metadata.setMeta(methodRestMetadata);
serviceRestMetadata.add(metadata);
});
return serviceRestMetadata;
}
@Override
public Set<RestMethodMetadata> resolveMethodRestMetadata(Class<?> targetType) {
List<Method> feignContractMethods = selectFeignContractMethods(targetType);
return contracts.stream()
.map(contract -> parseAndValidateMetadata(contract, targetType))
.flatMap(v -> v.stream())
.map(methodMetadata -> resolveMethodRestMetadata(methodMetadata, targetType, feignContractMethods))
.collect(Collectors.toSet());
}
private List<MethodMetadata> parseAndValidateMetadata(Contract contract, Class<?> targetType) {
List<MethodMetadata> methodMetadataList = Collections.emptyList();
try {
methodMetadataList = contract.parseAndValidatateMetadata(targetType);
} catch (Throwable ignored) {
// ignore
}
return methodMetadataList;
}
/**
* Select feign contract methods
* <p>
* extract some code from {@link Contract.BaseContract#parseAndValidatateMetadata(java.lang.Class)}
*
* @param targetType
* @return non-null
*/
private List<Method> selectFeignContractMethods(Class<?> targetType) {
List<Method> methods = new LinkedList<>();
for (Method method : targetType.getMethods()) {
if (method.getDeclaringClass() == Object.class ||
(method.getModifiers() & Modifier.STATIC) != 0 ||
Util.isDefault(method)) {
continue;
}
methods.add(method);
}
return methods;
}
protected RestMethodMetadata resolveMethodRestMetadata(MethodMetadata methodMetadata, Class<?> targetType,
List<Method> feignContractMethods) {
String configKey = methodMetadata.configKey();
Method feignContractMethod = getMatchedFeignContractMethod(targetType, feignContractMethods, configKey);
RestMethodMetadata metadata = new RestMethodMetadata(methodMetadata);
metadata.setMethod(new com.alibaba.cloud.dubbo.metadata.MethodMetadata(feignContractMethod));
return metadata;
}
private Method getMatchedFeignContractMethod(Class<?> targetType, List<Method> methods, String expectedConfigKey) {
Method matchedMethod = null;
for (Method method : methods) {
String configKey = Feign.configKey(targetType, method);
if (expectedConfigKey.equals(configKey)) {
matchedMethod = method;
break;
}
}
return matchedMethod;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
private static final String[] CONTRACT_CLASS_NAMES = { "feign.jaxrs2.JAXRS2Contract",
"org.springframework.cloud.openfeign.support.SpringMvcContract", };
private final ObjectProvider<Contract> contractObjectProvider;
private ClassLoader classLoader;
/**
* Feign Contracts
*/
private Collection<Contract> contracts;
public DubboServiceBeanMetadataResolver(
ObjectProvider<Contract> contractObjectProvider) {
this.contractObjectProvider = contractObjectProvider;
}
@Override
public void afterSingletonsInstantiated() {
LinkedList<Contract> contracts = new LinkedList<>();
// Add injected Contract if available, for example SpringMvcContract Bean under
// Spring Cloud Open Feign
Contract contract = contractObjectProvider.getIfAvailable();
if (contract != null) {
contracts.add(contract);
}
Stream.of(CONTRACT_CLASS_NAMES).filter(this::isClassPresent) // filter the existed
// classes
.map(this::loadContractClass) // load Contract Class
.map(this::createContract) // createServiceInstance instance by the
// specified class
.forEach(contracts::add); // add the Contract instance into contracts
this.contracts = Collections.unmodifiableCollection(contracts);
}
private Contract createContract(Class<?> contractClassName) {
return (Contract) BeanUtils.instantiateClass(contractClassName);
}
private Class<?> loadContractClass(String contractClassName) {
return ClassUtils.resolveClassName(contractClassName, classLoader);
}
private boolean isClassPresent(String className) {
return ClassUtils.isPresent(className, classLoader);
}
@Override
public Set<ServiceRestMetadata> resolveServiceRestMetadata(ServiceBean serviceBean) {
Object bean = serviceBean.getRef();
Class<?> beanType = bean.getClass();
Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>();
Set<RestMethodMetadata> methodRestMetadata = resolveMethodRestMetadata(beanType);
List<URL> urls = serviceBean.getExportedUrls();
urls.stream().map(URL::toString).forEach(url -> {
ServiceRestMetadata metadata = new ServiceRestMetadata();
metadata.setUrl(url);
metadata.setMeta(methodRestMetadata);
serviceRestMetadata.add(metadata);
});
return serviceRestMetadata;
}
@Override
public Set<RestMethodMetadata> resolveMethodRestMetadata(Class<?> targetType) {
List<Method> feignContractMethods = selectFeignContractMethods(targetType);
return contracts.stream()
.map(contract -> parseAndValidateMetadata(contract, targetType))
.flatMap(v -> v.stream())
.map(methodMetadata -> resolveMethodRestMetadata(methodMetadata,
targetType, feignContractMethods))
.collect(Collectors.toSet());
}
private List<MethodMetadata> parseAndValidateMetadata(Contract contract,
Class<?> targetType) {
List<MethodMetadata> methodMetadataList = Collections.emptyList();
try {
methodMetadataList = contract.parseAndValidatateMetadata(targetType);
}
catch (Throwable ignored) {
// ignore
}
return methodMetadataList;
}
/**
* Select feign contract methods
* <p>
* extract some code from
* {@link Contract.BaseContract#parseAndValidatateMetadata(java.lang.Class)}
*
* @param targetType
* @return non-null
*/
private List<Method> selectFeignContractMethods(Class<?> targetType) {
List<Method> methods = new LinkedList<>();
for (Method method : targetType.getMethods()) {
if (method.getDeclaringClass() == Object.class
|| (method.getModifiers() & Modifier.STATIC) != 0
|| Util.isDefault(method)) {
continue;
}
methods.add(method);
}
return methods;
}
protected RestMethodMetadata resolveMethodRestMetadata(MethodMetadata methodMetadata,
Class<?> targetType, List<Method> feignContractMethods) {
String configKey = methodMetadata.configKey();
Method feignContractMethod = getMatchedFeignContractMethod(targetType,
feignContractMethods, configKey);
RestMethodMetadata metadata = new RestMethodMetadata(methodMetadata);
metadata.setMethod(
new com.alibaba.cloud.dubbo.metadata.MethodMetadata(feignContractMethod));
return metadata;
}
private Method getMatchedFeignContractMethod(Class<?> targetType,
List<Method> methods, String expectedConfigKey) {
Method matchedMethod = null;
for (Method method : methods) {
String configKey = Feign.configKey(targetType, method);
if (expectedConfigKey.equals(configKey)) {
matchedMethod = method;
break;
}
}
return matchedMethod;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}

@ -16,13 +16,14 @@
*/
package com.alibaba.cloud.dubbo.metadata.resolver;
import com.alibaba.cloud.dubbo.annotation.DubboTransported;
import org.springframework.core.env.PropertyResolver;
import static org.springframework.core.annotation.AnnotationUtils.getAnnotationAttributes;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.springframework.core.annotation.AnnotationUtils.getAnnotationAttributes;
import org.springframework.core.env.PropertyResolver;
import com.alibaba.cloud.dubbo.annotation.DubboTransported;
/**
* {@link DubboTransported} annotation attributes resolver
@ -31,26 +32,26 @@ import static org.springframework.core.annotation.AnnotationUtils.getAnnotationA
*/
public class DubboTransportedAttributesResolver {
private final PropertyResolver propertyResolver;
public DubboTransportedAttributesResolver(PropertyResolver propertyResolver) {
this.propertyResolver = propertyResolver;
}
public Map<String, Object> resolve(DubboTransported dubboTransported) {
Map<String, Object> attributes = getAnnotationAttributes(dubboTransported);
return resolve(attributes);
}
public Map<String, Object> resolve(Map<String, Object> attributes) {
Map<String, Object> resolvedAttributes = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
value = propertyResolver.resolvePlaceholders(value.toString());
}
resolvedAttributes.put(entry.getKey(), value);
}
return resolvedAttributes;
}
private final PropertyResolver propertyResolver;
public DubboTransportedAttributesResolver(PropertyResolver propertyResolver) {
this.propertyResolver = propertyResolver;
}
public Map<String, Object> resolve(DubboTransported dubboTransported) {
Map<String, Object> attributes = getAnnotationAttributes(dubboTransported);
return resolve(attributes);
}
public Map<String, Object> resolve(Map<String, Object> attributes) {
Map<String, Object> resolvedAttributes = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
value = propertyResolver.resolvePlaceholders(value.toString());
}
resolvedAttributes.put(entry.getKey(), value);
}
return resolvedAttributes;
}
}

@ -16,13 +16,7 @@
*/
package com.alibaba.cloud.dubbo.metadata.resolver;
import com.alibaba.cloud.dubbo.annotation.DubboTransported;
import com.alibaba.cloud.dubbo.metadata.DubboTransportedMethodMetadata;
import com.alibaba.cloud.dubbo.metadata.MethodMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import feign.Contract;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.PropertyResolver;
import static feign.Feign.configKey;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
@ -30,79 +24,96 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static feign.Feign.configKey;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.PropertyResolver;
import com.alibaba.cloud.dubbo.annotation.DubboTransported;
import com.alibaba.cloud.dubbo.metadata.DubboTransportedMethodMetadata;
import com.alibaba.cloud.dubbo.metadata.MethodMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import feign.Contract;
/**
* {@link MethodMetadata} Resolver for the {@link DubboTransported} annotated classes or methods in client side.
* {@link MethodMetadata} Resolver for the {@link DubboTransported} annotated classes or
* methods in client side.
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see DubboTransportedMethodMetadata
*/
public class DubboTransportedMethodMetadataResolver {
private static final Class<DubboTransported> DUBBO_TRANSPORTED_CLASS = DubboTransported.class;
private final DubboTransportedAttributesResolver attributesResolver;
private final Contract contract;
public DubboTransportedMethodMetadataResolver(PropertyResolver propertyResolver, Contract contract) {
this.attributesResolver = new DubboTransportedAttributesResolver(propertyResolver);
this.contract = contract;
}
public Map<DubboTransportedMethodMetadata, RestMethodMetadata> resolve(Class<?> targetType) {
Set<DubboTransportedMethodMetadata> dubboTransportedMethodMetadataSet =
resolveDubboTransportedMethodMetadataSet(targetType);
Map<String, RestMethodMetadata> restMethodMetadataMap = resolveRestRequestMetadataMap(targetType);
return dubboTransportedMethodMetadataSet
.stream()
.collect(Collectors.toMap(methodMetadata -> methodMetadata, methodMetadata -> {
RestMethodMetadata restMethodMetadata = restMethodMetadataMap.get(configKey(targetType, methodMetadata.getMethod()));
restMethodMetadata.setMethod(methodMetadata.getMethodMetadata());
return restMethodMetadata;
}
));
}
protected Set<DubboTransportedMethodMetadata> resolveDubboTransportedMethodMetadataSet(Class<?> targetType) {
// The public methods of target interface
Method[] methods = targetType.getMethods();
Set<DubboTransportedMethodMetadata> methodMetadataSet = new LinkedHashSet<>();
for (Method method : methods) {
DubboTransported dubboTransported = resolveDubboTransported(method);
if (dubboTransported != null) {
DubboTransportedMethodMetadata methodMetadata = createDubboTransportedMethodMetadata(method, dubboTransported);
methodMetadataSet.add(methodMetadata);
}
}
return methodMetadataSet;
}
private Map<String, RestMethodMetadata> resolveRestRequestMetadataMap(Class<?> targetType) {
return contract.parseAndValidatateMetadata(targetType)
.stream().collect(Collectors.toMap(feign.MethodMetadata::configKey, this::restMethodMetadata));
}
private RestMethodMetadata restMethodMetadata(feign.MethodMetadata methodMetadata) {
return new RestMethodMetadata(methodMetadata);
}
private DubboTransportedMethodMetadata createDubboTransportedMethodMetadata(Method method,
DubboTransported dubboTransported) {
Map<String, Object> attributes = attributesResolver.resolve(dubboTransported);
return new DubboTransportedMethodMetadata(method, attributes);
}
private DubboTransported resolveDubboTransported(Method method) {
DubboTransported dubboTransported = AnnotationUtils.findAnnotation(method, DUBBO_TRANSPORTED_CLASS);
if (dubboTransported == null) { // Attempt to find @DubboTransported in the declaring class
Class<?> declaringClass = method.getDeclaringClass();
dubboTransported = AnnotationUtils.findAnnotation(declaringClass, DUBBO_TRANSPORTED_CLASS);
}
return dubboTransported;
}
private static final Class<DubboTransported> DUBBO_TRANSPORTED_CLASS = DubboTransported.class;
private final DubboTransportedAttributesResolver attributesResolver;
private final Contract contract;
public DubboTransportedMethodMetadataResolver(PropertyResolver propertyResolver,
Contract contract) {
this.attributesResolver = new DubboTransportedAttributesResolver(
propertyResolver);
this.contract = contract;
}
public Map<DubboTransportedMethodMetadata, RestMethodMetadata> resolve(
Class<?> targetType) {
Set<DubboTransportedMethodMetadata> dubboTransportedMethodMetadataSet = resolveDubboTransportedMethodMetadataSet(
targetType);
Map<String, RestMethodMetadata> restMethodMetadataMap = resolveRestRequestMetadataMap(
targetType);
return dubboTransportedMethodMetadataSet.stream().collect(
Collectors.toMap(methodMetadata -> methodMetadata, methodMetadata -> {
RestMethodMetadata restMethodMetadata = restMethodMetadataMap
.get(configKey(targetType, methodMetadata.getMethod()));
restMethodMetadata.setMethod(methodMetadata.getMethodMetadata());
return restMethodMetadata;
}));
}
protected Set<DubboTransportedMethodMetadata> resolveDubboTransportedMethodMetadataSet(
Class<?> targetType) {
// The public methods of target interface
Method[] methods = targetType.getMethods();
Set<DubboTransportedMethodMetadata> methodMetadataSet = new LinkedHashSet<>();
for (Method method : methods) {
DubboTransported dubboTransported = resolveDubboTransported(method);
if (dubboTransported != null) {
DubboTransportedMethodMetadata methodMetadata = createDubboTransportedMethodMetadata(
method, dubboTransported);
methodMetadataSet.add(methodMetadata);
}
}
return methodMetadataSet;
}
private Map<String, RestMethodMetadata> resolveRestRequestMetadataMap(
Class<?> targetType) {
return contract.parseAndValidatateMetadata(targetType).stream().collect(Collectors
.toMap(feign.MethodMetadata::configKey, this::restMethodMetadata));
}
private RestMethodMetadata restMethodMetadata(feign.MethodMetadata methodMetadata) {
return new RestMethodMetadata(methodMetadata);
}
private DubboTransportedMethodMetadata createDubboTransportedMethodMetadata(
Method method, DubboTransported dubboTransported) {
Map<String, Object> attributes = attributesResolver.resolve(dubboTransported);
return new DubboTransportedMethodMetadata(method, attributes);
}
private DubboTransported resolveDubboTransported(Method method) {
DubboTransported dubboTransported = AnnotationUtils.findAnnotation(method,
DUBBO_TRANSPORTED_CLASS);
if (dubboTransported == null) { // Attempt to find @DubboTransported in the
// declaring class
Class<?> declaringClass = method.getDeclaringClass();
dubboTransported = AnnotationUtils.findAnnotation(declaringClass,
DUBBO_TRANSPORTED_CLASS);
}
return dubboTransported;
}
}

@ -16,13 +16,13 @@
*/
package com.alibaba.cloud.dubbo.metadata.resolver;
import java.util.Set;
import org.apache.dubbo.config.spring.ServiceBean;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import com.alibaba.cloud.dubbo.metadata.ServiceRestMetadata;
import java.util.Set;
/**
* The REST metadata resolver
*
@ -30,19 +30,19 @@ import java.util.Set;
*/
public interface MetadataResolver {
/**
* Resolve the {@link ServiceRestMetadata} {@link Set set} from {@link ServiceBean}
*
* @param serviceBean {@link ServiceBean}
* @return non-null {@link Set}
*/
Set<ServiceRestMetadata> resolveServiceRestMetadata(ServiceBean serviceBean);
/**
* Resolve the {@link ServiceRestMetadata} {@link Set set} from {@link ServiceBean}
*
* @param serviceBean {@link ServiceBean}
* @return non-null {@link Set}
*/
Set<ServiceRestMetadata> resolveServiceRestMetadata(ServiceBean serviceBean);
/**
* Resolve {@link RestMethodMetadata} {@link Set set} from {@link Class target type}
*
* @param targetType {@link Class target type}
* @return non-null {@link Set}
*/
Set<RestMethodMetadata> resolveMethodRestMetadata(Class<?> targetType);
/**
* Resolve {@link RestMethodMetadata} {@link Set set} from {@link Class target type}
*
* @param targetType {@link Class target type}
* @return non-null {@link Set}
*/
Set<RestMethodMetadata> resolveMethodRestMetadata(Class<?> targetType);
}

@ -16,18 +16,19 @@
*/
package com.alibaba.cloud.dubbo.openfeign;
import org.apache.dubbo.rpc.service.GenericService;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContext;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import org.springframework.util.ClassUtils;
import static org.apache.dubbo.common.utils.PojoUtils.realize;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import static org.apache.dubbo.common.utils.PojoUtils.realize;
import org.apache.dubbo.rpc.service.GenericService;
import org.springframework.util.ClassUtils;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContext;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
/**
* Dubbo {@link GenericService} for {@link InvocationHandler}
@ -36,52 +37,56 @@ import static org.apache.dubbo.common.utils.PojoUtils.realize;
*/
public class DubboInvocationHandler implements InvocationHandler {
private final Map<Method, FeignMethodMetadata> feignMethodMetadataMap;
private final Map<Method, FeignMethodMetadata> feignMethodMetadataMap;
private final InvocationHandler defaultInvocationHandler;
private final InvocationHandler defaultInvocationHandler;
private final DubboGenericServiceExecutionContextFactory contextFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
private final ClassLoader classLoader;
private final ClassLoader classLoader;
public DubboInvocationHandler(Map<Method, FeignMethodMetadata> feignMethodMetadataMap,
InvocationHandler defaultInvocationHandler,
ClassLoader classLoader,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.feignMethodMetadataMap = feignMethodMetadataMap;
this.defaultInvocationHandler = defaultInvocationHandler;
this.classLoader = classLoader;
this.contextFactory = contextFactory;
}
public DubboInvocationHandler(Map<Method, FeignMethodMetadata> feignMethodMetadataMap,
InvocationHandler defaultInvocationHandler, ClassLoader classLoader,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.feignMethodMetadataMap = feignMethodMetadataMap;
this.defaultInvocationHandler = defaultInvocationHandler;
this.classLoader = classLoader;
this.contextFactory = contextFactory;
}
@Override
public Object invoke(Object proxy, Method feignMethod, Object[] args) throws Throwable {
@Override
public Object invoke(Object proxy, Method feignMethod, Object[] args)
throws Throwable {
FeignMethodMetadata feignMethodMetadata = feignMethodMetadataMap.get(feignMethod);
FeignMethodMetadata feignMethodMetadata = feignMethodMetadataMap.get(feignMethod);
if (feignMethodMetadata == null) {
return defaultInvocationHandler.invoke(proxy, feignMethod, args);
}
if (feignMethodMetadata == null) {
return defaultInvocationHandler.invoke(proxy, feignMethod, args);
}
GenericService dubboGenericService = feignMethodMetadata.getDubboGenericService();
RestMethodMetadata dubboRestMethodMetadata = feignMethodMetadata.getDubboRestMethodMetadata();
RestMethodMetadata feignRestMethodMetadata = feignMethodMetadata.getFeignMethodMetadata();
GenericService dubboGenericService = feignMethodMetadata.getDubboGenericService();
RestMethodMetadata dubboRestMethodMetadata = feignMethodMetadata
.getDubboRestMethodMetadata();
RestMethodMetadata feignRestMethodMetadata = feignMethodMetadata
.getFeignMethodMetadata();
DubboGenericServiceExecutionContext context = contextFactory.create(dubboRestMethodMetadata, feignRestMethodMetadata, args);
DubboGenericServiceExecutionContext context = contextFactory
.create(dubboRestMethodMetadata, feignRestMethodMetadata, args);
String methodName = context.getMethodName();
String[] parameterTypes = context.getParameterTypes();
Object[] parameters = context.getParameters();
String methodName = context.getMethodName();
String[] parameterTypes = context.getParameterTypes();
Object[] parameters = context.getParameters();
Object result = dubboGenericService.$invoke(methodName, parameterTypes, parameters);
Object result = dubboGenericService.$invoke(methodName, parameterTypes,
parameters);
Class<?> returnType = getReturnType(dubboRestMethodMetadata);
Class<?> returnType = getReturnType(dubboRestMethodMetadata);
return realize(result, returnType);
}
return realize(result, returnType);
}
private Class<?> getReturnType(RestMethodMetadata dubboRestMethodMetadata) {
String returnType = dubboRestMethodMetadata.getReturnType();
return ClassUtils.resolveClassName(returnType, classLoader);
}
private Class<?> getReturnType(RestMethodMetadata dubboRestMethodMetadata) {
String returnType = dubboRestMethodMetadata.getReturnType();
return ClassUtils.resolveClassName(returnType, classLoader);
}
}

@ -16,12 +16,12 @@
*/
package com.alibaba.cloud.dubbo.openfeign;
import java.lang.reflect.Method;
import org.apache.dubbo.rpc.service.GenericService;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import java.lang.reflect.Method;
/**
* Feign {@link Method} Metadata
*
@ -29,29 +29,29 @@ import java.lang.reflect.Method;
*/
class FeignMethodMetadata {
private final GenericService dubboGenericService;
private final RestMethodMetadata dubboRestMethodMetadata;
private final GenericService dubboGenericService;
private final RestMethodMetadata feignMethodMetadata;
private final RestMethodMetadata dubboRestMethodMetadata;
private final RestMethodMetadata feignMethodMetadata;
FeignMethodMetadata(GenericService dubboGenericService, RestMethodMetadata dubboRestMethodMetadata,
RestMethodMetadata feignMethodMetadata) {
this.dubboGenericService = dubboGenericService;
this.dubboRestMethodMetadata = dubboRestMethodMetadata;
this.feignMethodMetadata = feignMethodMetadata;
}
FeignMethodMetadata(GenericService dubboGenericService,
RestMethodMetadata dubboRestMethodMetadata,
RestMethodMetadata feignMethodMetadata) {
this.dubboGenericService = dubboGenericService;
this.dubboRestMethodMetadata = dubboRestMethodMetadata;
this.feignMethodMetadata = feignMethodMetadata;
}
GenericService getDubboGenericService() {
return dubboGenericService;
}
GenericService getDubboGenericService() {
return dubboGenericService;
}
RestMethodMetadata getDubboRestMethodMetadata() {
return dubboRestMethodMetadata;
}
RestMethodMetadata getDubboRestMethodMetadata() {
return dubboRestMethodMetadata;
}
RestMethodMetadata getFeignMethodMetadata() {
return feignMethodMetadata;
}
RestMethodMetadata getFeignMethodMetadata() {
return feignMethodMetadata;
}
}

@ -16,68 +16,73 @@
*/
package com.alibaba.cloud.dubbo.openfeign;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.env.Environment;
import static com.alibaba.cloud.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration.TARGETER_CLASS_NAME;
import static java.lang.reflect.Proxy.newProxyInstance;
import static org.springframework.util.ClassUtils.getUserClass;
import static org.springframework.util.ClassUtils.isPresent;
import static org.springframework.util.ClassUtils.resolveClassName;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.env.Environment;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
/**
* org.springframework.cloud.openfeign.Targeter {@link BeanPostProcessor}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class TargeterBeanPostProcessor implements BeanPostProcessor, BeanClassLoaderAware {
public class TargeterBeanPostProcessor
implements BeanPostProcessor, BeanClassLoaderAware {
private final Environment environment;
private final Environment environment;
private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
private final DubboGenericServiceFactory dubboGenericServiceFactory;
private final DubboGenericServiceFactory dubboGenericServiceFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
private ClassLoader classLoader;
private ClassLoader classLoader;
public TargeterBeanPostProcessor(Environment environment,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboGenericServiceFactory dubboGenericServiceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.environment = environment;
this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
this.dubboGenericServiceFactory = dubboGenericServiceFactory;
this.contextFactory = contextFactory;
}
public TargeterBeanPostProcessor(Environment environment,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboGenericServiceFactory dubboGenericServiceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.environment = environment;
this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
this.dubboGenericServiceFactory = dubboGenericServiceFactory;
this.contextFactory = contextFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
if (isPresent(TARGETER_CLASS_NAME, classLoader)) {
Class<?> beanClass = getUserClass(bean.getClass());
Class<?> targetClass = resolveClassName(TARGETER_CLASS_NAME, classLoader);
if (targetClass.isAssignableFrom(beanClass)) {
return newProxyInstance(classLoader, new Class[]{targetClass},
new TargeterInvocationHandler(bean, environment, classLoader, dubboServiceMetadataRepository,
dubboGenericServiceFactory, contextFactory));
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName)
throws BeansException {
if (isPresent(TARGETER_CLASS_NAME, classLoader)) {
Class<?> beanClass = getUserClass(bean.getClass());
Class<?> targetClass = resolveClassName(TARGETER_CLASS_NAME, classLoader);
if (targetClass.isAssignableFrom(beanClass)) {
return newProxyInstance(classLoader, new Class[] { targetClass },
new TargeterInvocationHandler(bean, environment, classLoader,
dubboServiceMetadataRepository,
dubboGenericServiceFactory, contextFactory));
}
}
return bean;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}

@ -16,9 +16,21 @@
*/
package com.alibaba.cloud.dubbo.openfeign;
import static java.lang.reflect.Proxy.newProxyInstance;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import org.apache.dubbo.rpc.service.GenericService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.core.env.Environment;
import com.alibaba.cloud.dubbo.annotation.DubboTransported;
import com.alibaba.cloud.dubbo.metadata.DubboRestServiceMetadata;
import com.alibaba.cloud.dubbo.metadata.DubboTransportedMethodMetadata;
@ -29,20 +41,9 @@ import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepositor
import com.alibaba.cloud.dubbo.metadata.resolver.DubboTransportedMethodMetadataResolver;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceExecutionContextFactory;
import com.alibaba.cloud.dubbo.service.DubboGenericServiceFactory;
import feign.Contract;
import feign.Target;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.core.env.Environment;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import static java.lang.reflect.Proxy.newProxyInstance;
/**
* org.springframework.cloud.openfeign.Targeter {@link InvocationHandler}
@ -51,125 +52,137 @@ import static java.lang.reflect.Proxy.newProxyInstance;
*/
class TargeterInvocationHandler implements InvocationHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Object bean;
private final Object bean;
private final Environment environment;
private final Environment environment;
private final ClassLoader classLoader;
private final ClassLoader classLoader;
private final DubboServiceMetadataRepository repository;
private final DubboServiceMetadataRepository repository;
private final DubboGenericServiceFactory dubboGenericServiceFactory;
private final DubboGenericServiceFactory dubboGenericServiceFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
private final DubboGenericServiceExecutionContextFactory contextFactory;
TargeterInvocationHandler(Object bean, Environment environment,
ClassLoader classLoader,
DubboServiceMetadataRepository repository,
DubboGenericServiceFactory dubboGenericServiceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.bean = bean;
this.environment = environment;
this.classLoader = classLoader;
this.repository = repository;
this.dubboGenericServiceFactory = dubboGenericServiceFactory;
this.contextFactory = contextFactory;
}
private static <T> T cast(Object object) {
return (T) object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* args[0]: FeignClientFactoryBean factory
* args[1]: Feign.Builder feign
* args[2]: FeignContext context
* args[3]: Target.HardCodedTarget<T> target
*/
FeignContext feignContext = cast(args[2]);
Target.HardCodedTarget<?> target = cast(args[3]);
// Execute Targeter#target method first
method.setAccessible(true);
// Get the default proxy object
Object defaultProxy = method.invoke(bean, args);
// Create Dubbo Proxy if required
return createDubboProxyIfRequired(feignContext, target, defaultProxy);
}
private Object createDubboProxyIfRequired(FeignContext feignContext, Target target, Object defaultProxy) {
DubboInvocationHandler dubboInvocationHandler = createDubboInvocationHandler(feignContext, target, defaultProxy);
if (dubboInvocationHandler == null) {
return defaultProxy;
}
return newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, dubboInvocationHandler);
}
private DubboInvocationHandler createDubboInvocationHandler(FeignContext feignContext, Target target,
Object defaultFeignClientProxy) {
// Service name equals @FeignClient.name()
String serviceName = target.name();
Class<?> targetType = target.type();
// Get Contract Bean from FeignContext
Contract contract = feignContext.getInstance(serviceName, Contract.class);
DubboTransportedMethodMetadataResolver resolver =
new DubboTransportedMethodMetadataResolver(environment, contract);
Map<DubboTransportedMethodMetadata, RestMethodMetadata> feignRestMethodMetadataMap = resolver.resolve(targetType);
if (feignRestMethodMetadataMap.isEmpty()) { // @DubboTransported method was not found from the Client interface
if (logger.isDebugEnabled()) {
logger.debug("@{} method was not found in the Feign target type[{}]",
DubboTransported.class.getSimpleName(), targetType.getName());
}
return null;
}
// Update Metadata
repository.initializeMetadata(serviceName);
Map<Method, FeignMethodMetadata> feignMethodMetadataMap = getFeignMethodMetadataMap(serviceName, feignRestMethodMetadataMap);
InvocationHandler defaultFeignClientInvocationHandler = Proxy.getInvocationHandler(defaultFeignClientProxy);
DubboInvocationHandler dubboInvocationHandler = new DubboInvocationHandler(feignMethodMetadataMap,
defaultFeignClientInvocationHandler, classLoader, contextFactory);
return dubboInvocationHandler;
}
private Map<Method, FeignMethodMetadata> getFeignMethodMetadataMap(String serviceName,
Map<DubboTransportedMethodMetadata, RestMethodMetadata>
feignRestMethodMetadataMap) {
Map<Method, FeignMethodMetadata> feignMethodMetadataMap = new HashMap<>();
for (Map.Entry<DubboTransportedMethodMetadata, RestMethodMetadata> entry : feignRestMethodMetadataMap.entrySet()) {
RestMethodMetadata feignRestMethodMetadata = entry.getValue();
RequestMetadata feignRequestMetadata = feignRestMethodMetadata.getRequest();
DubboRestServiceMetadata metadata = repository.get(serviceName, feignRequestMetadata);
if (metadata != null) {
DubboTransportedMethodMetadata dubboTransportedMethodMetadata = entry.getKey();
Map<String, Object> dubboTranslatedAttributes = dubboTransportedMethodMetadata.getAttributes();
Method method = dubboTransportedMethodMetadata.getMethod();
GenericService dubboGenericService = dubboGenericServiceFactory.create(metadata, dubboTranslatedAttributes);
RestMethodMetadata dubboRestMethodMetadata = metadata.getRestMethodMetadata();
MethodMetadata methodMetadata = dubboTransportedMethodMetadata.getMethodMetadata();
FeignMethodMetadata feignMethodMetadata = new FeignMethodMetadata(dubboGenericService,
dubboRestMethodMetadata, feignRestMethodMetadata);
feignMethodMetadataMap.put(method, feignMethodMetadata);
}
}
return feignMethodMetadataMap;
}
TargeterInvocationHandler(Object bean, Environment environment,
ClassLoader classLoader, DubboServiceMetadataRepository repository,
DubboGenericServiceFactory dubboGenericServiceFactory,
DubboGenericServiceExecutionContextFactory contextFactory) {
this.bean = bean;
this.environment = environment;
this.classLoader = classLoader;
this.repository = repository;
this.dubboGenericServiceFactory = dubboGenericServiceFactory;
this.contextFactory = contextFactory;
}
private static <T> T cast(Object object) {
return (T) object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* args[0]: FeignClientFactoryBean factory args[1]: Feign.Builder feign args[2]:
* FeignContext context args[3]: Target.HardCodedTarget<T> target
*/
FeignContext feignContext = cast(args[2]);
Target.HardCodedTarget<?> target = cast(args[3]);
// Execute Targeter#target method first
method.setAccessible(true);
// Get the default proxy object
Object defaultProxy = method.invoke(bean, args);
// Create Dubbo Proxy if required
return createDubboProxyIfRequired(feignContext, target, defaultProxy);
}
private Object createDubboProxyIfRequired(FeignContext feignContext, Target target,
Object defaultProxy) {
DubboInvocationHandler dubboInvocationHandler = createDubboInvocationHandler(
feignContext, target, defaultProxy);
if (dubboInvocationHandler == null) {
return defaultProxy;
}
return newProxyInstance(target.type().getClassLoader(),
new Class<?>[] { target.type() }, dubboInvocationHandler);
}
private DubboInvocationHandler createDubboInvocationHandler(FeignContext feignContext,
Target target, Object defaultFeignClientProxy) {
// Service name equals @FeignClient.name()
String serviceName = target.name();
Class<?> targetType = target.type();
// Get Contract Bean from FeignContext
Contract contract = feignContext.getInstance(serviceName, Contract.class);
DubboTransportedMethodMetadataResolver resolver = new DubboTransportedMethodMetadataResolver(
environment, contract);
Map<DubboTransportedMethodMetadata, RestMethodMetadata> feignRestMethodMetadataMap = resolver
.resolve(targetType);
if (feignRestMethodMetadataMap.isEmpty()) { // @DubboTransported method was not
// found from the Client interface
if (logger.isDebugEnabled()) {
logger.debug("@{} method was not found in the Feign target type[{}]",
DubboTransported.class.getSimpleName(), targetType.getName());
}
return null;
}
// Update Metadata
repository.initializeMetadata(serviceName);
Map<Method, FeignMethodMetadata> feignMethodMetadataMap = getFeignMethodMetadataMap(
serviceName, feignRestMethodMetadataMap);
InvocationHandler defaultFeignClientInvocationHandler = Proxy
.getInvocationHandler(defaultFeignClientProxy);
DubboInvocationHandler dubboInvocationHandler = new DubboInvocationHandler(
feignMethodMetadataMap, defaultFeignClientInvocationHandler, classLoader,
contextFactory);
return dubboInvocationHandler;
}
private Map<Method, FeignMethodMetadata> getFeignMethodMetadataMap(String serviceName,
Map<DubboTransportedMethodMetadata, RestMethodMetadata> feignRestMethodMetadataMap) {
Map<Method, FeignMethodMetadata> feignMethodMetadataMap = new HashMap<>();
for (Map.Entry<DubboTransportedMethodMetadata, RestMethodMetadata> entry : feignRestMethodMetadataMap
.entrySet()) {
RestMethodMetadata feignRestMethodMetadata = entry.getValue();
RequestMetadata feignRequestMetadata = feignRestMethodMetadata.getRequest();
DubboRestServiceMetadata metadata = repository.get(serviceName,
feignRequestMetadata);
if (metadata != null) {
DubboTransportedMethodMetadata dubboTransportedMethodMetadata = entry
.getKey();
Map<String, Object> dubboTranslatedAttributes = dubboTransportedMethodMetadata
.getAttributes();
Method method = dubboTransportedMethodMetadata.getMethod();
GenericService dubboGenericService = dubboGenericServiceFactory
.create(metadata, dubboTranslatedAttributes);
RestMethodMetadata dubboRestMethodMetadata = metadata
.getRestMethodMetadata();
MethodMetadata methodMetadata = dubboTransportedMethodMetadata
.getMethodMetadata();
FeignMethodMetadata feignMethodMetadata = new FeignMethodMetadata(
dubboGenericService, dubboRestMethodMetadata,
feignRestMethodMetadata);
feignMethodMetadataMap.put(method, feignMethodMetadata);
}
}
return feignMethodMetadataMap;
}
}

@ -16,16 +16,31 @@
*/
package com.alibaba.cloud.dubbo.registry;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.apache.dubbo.common.URLBuilder.from;
import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
import static org.apache.dubbo.registry.Constants.ADMIN_PROTOCOL;
import static org.springframework.util.StringUtils.hasText;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.NotifyListener;
import org.apache.dubbo.registry.RegistryFactory;
import org.apache.dubbo.registry.support.FailbackRegistry;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancesChangedEvent;
import com.alibaba.cloud.dubbo.service.DubboMetadataService;
import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy;
import com.alibaba.cloud.dubbo.util.JSONUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
@ -34,301 +49,316 @@ import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.apache.dubbo.common.URLBuilder.from;
import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
import static org.apache.dubbo.registry.Constants.ADMIN_PROTOCOL;
import static org.springframework.util.StringUtils.hasText;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancesChangedEvent;
import com.alibaba.cloud.dubbo.service.DubboMetadataService;
import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy;
import com.alibaba.cloud.dubbo.util.JSONUtils;
/**
* Abstract Dubbo {@link RegistryFactory} uses Spring Cloud Service Registration abstraction, whose protocol is "spring-cloud"
* Abstract Dubbo {@link RegistryFactory} uses Spring Cloud Service Registration
* abstraction, whose protocol is "spring-cloud"
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class AbstractSpringCloudRegistry extends FailbackRegistry {
/**
* The parameter name of {@link #servicesLookupInterval}
*/
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();
/**
* Caches the IDs of {@link ApplicationListener}
*/
private static final Set<String> registerListeners = new HashSet<>();
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* The interval in second of lookup service names(only for Dubbo-OPS)
*/
private final long servicesLookupInterval;
private final DiscoveryClient discoveryClient;
private final DubboServiceMetadataRepository repository;
private final DubboMetadataServiceProxy dubboMetadataConfigServiceProxy;
private final JSONUtils jsonUtils;
private final ConfigurableApplicationContext applicationContext;
public AbstractSpringCloudRegistry(URL url,
DiscoveryClient discoveryClient,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboMetadataServiceProxy dubboMetadataConfigServiceProxy,
JSONUtils jsonUtils,
ConfigurableApplicationContext applicationContext) {
super(url);
this.servicesLookupInterval = url.getParameter(SERVICES_LOOKUP_INTERVAL_PARAM_NAME, 60L);
this.discoveryClient = discoveryClient;
this.repository = dubboServiceMetadataRepository;
this.dubboMetadataConfigServiceProxy = dubboMetadataConfigServiceProxy;
this.jsonUtils = jsonUtils;
this.applicationContext = applicationContext;
}
protected boolean shouldRegister(URL url) {
String side = url.getParameter(SIDE_KEY);
boolean should = PROVIDER_SIDE.equals(side); // Only register the Provider.
if (!should) {
if (logger.isDebugEnabled()) {
logger.debug("The URL[{}] should not be registered.", url.toString());
}
}
return should;
}
@Override
public final void doRegister(URL url) {
if (!shouldRegister(url)) {
return;
}
doRegister0(url);
}
/**
* The sub-type should implement to register
*
* @param url {@link URL}
*/
protected abstract void doRegister0(URL url);
@Override
public final void doUnregister(URL url) {
if (!shouldRegister(url)) {
return;
}
doUnregister0(url);
}
/**
* The sub-type should implement to unregister
*
* @param url {@link URL}
*/
protected abstract void doUnregister0(URL url);
@Override
public final void doSubscribe(URL url, NotifyListener listener) {
if (isAdminURL(url)) {
// TODO in future
} else if (isDubboMetadataServiceURL(url)) { // for DubboMetadataService
subscribeDubboMetadataServiceURLs(url, listener);
} else { // for general Dubbo Services
subscribeDubboServiceURLs(url, listener);
}
}
protected void subscribeDubboServiceURLs(URL url, NotifyListener listener) {
doSubscribeDubboServiceURLs(url, listener);
registerServiceInstancesChangedEventListener(url, listener);
}
/**
* Register a {@link ApplicationListener listener} for {@link ServiceInstancesChangedEvent}
*
* @param url {@link URL}
* @param listener {@link NotifyListener}
*/
private void registerServiceInstancesChangedEventListener(URL url, NotifyListener listener) {
String listenerId = generateId(url);
if (registerListeners.add(listenerId)) {
applicationContext.addApplicationListener(new ApplicationListener<ServiceInstancesChangedEvent>() {
@Override
public void onApplicationEvent(ServiceInstancesChangedEvent event) {
String serviceName = event.getServiceName();
Collection<ServiceInstance> serviceInstances = event.getServiceInstances();
subscribeDubboServiceURL(url, listener, serviceName, s -> serviceInstances);
}
});
}
}
private void doSubscribeDubboServiceURLs(URL url, NotifyListener listener) {
Set<String> subscribedServices = repository.getSubscribedServices();
// Sync
subscribedServices.forEach(service -> subscribeDubboServiceURL(url, listener, service, this::getServiceInstances));
}
protected void subscribeDubboServiceURL(URL url, NotifyListener listener, String serviceName,
Function<String, Collection<ServiceInstance>> serviceInstancesFunction) {
if (logger.isInfoEnabled()) {
logger.info("The Dubbo Service URL[ID : {}] is being subscribed for service[name : {}]",
generateId(url), serviceName);
}
DubboMetadataService dubboMetadataService = dubboMetadataConfigServiceProxy.getProxy(serviceName);
if (dubboMetadataService == null) { // If not found, try to initialize
if (logger.isInfoEnabled()) {
logger.info("The metadata of Dubbo service[key : {}] can't be found when the subscribed service[name : {}], " +
"and then try to initialize it", url.getServiceKey(), serviceName);
}
repository.initializeMetadata(serviceName);
dubboMetadataService = dubboMetadataConfigServiceProxy.getProxy(serviceName);
}
if (dubboMetadataService == null) { // It makes sure not-found, return immediately
if (logger.isWarnEnabled()) {
logger.warn("The metadata of Dubbo service[key : {}] still can't be found, it could effect the further " +
"Dubbo service invocation", url.getServiceKey());
}
return;
}
Collection<ServiceInstance> serviceInstances = serviceInstancesFunction.apply(serviceName);
List<URL> allSubscribedURLs = new LinkedList<>();
if (CollectionUtils.isEmpty(serviceInstances)) {
if (logger.isWarnEnabled()) {
logger.warn("There is no instance from service[name : {}], and then Dubbo Service[key : {}] will not be " +
"available , please make sure the further impact", serviceName, url.getServiceKey());
}
/**
* URLs with {@link RegistryConstants#EMPTY_PROTOCOL}
*/
allSubscribedURLs.addAll(emptyURLs(url));
} else {
List<URL> exportedURLs = getExportedURLs(dubboMetadataService, url);
for (URL exportedURL : exportedURLs) {
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);
}
});
allSubscribedURLs.addAll(subscribedURLs);
}
}
if (logger.isDebugEnabled()) {
logger.debug("The subscribed URL[{}] will notify all URLs : {}", url, allSubscribedURLs);
}
listener.notify(allSubscribedURLs);
}
private String generateId(URL url) {
return url.toString(VERSION_KEY, GROUP_KEY, PROTOCOL_KEY);
}
private List<URL> emptyURLs(URL url) {
return asList(from(url).setProtocol(EMPTY_PROTOCOL).build());
}
private List<ServiceInstance> getServiceInstances(String serviceName) {
return hasText(serviceName) ? doGetServiceInstances(serviceName) : emptyList();
}
private List<ServiceInstance> doGetServiceInstances(String serviceName) {
List<ServiceInstance> serviceInstances = emptyList();
try {
serviceInstances = discoveryClient.getInstances(serviceName);
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
}
return serviceInstances;
}
private List<URL> getExportedURLs(DubboMetadataService dubboMetadataService, URL url) {
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) {
String serviceInterface = url.getServiceInterface();
String group = url.getParameter(GROUP_KEY);
String version = url.getParameter(VERSION_KEY);
String protocol = url.getParameter(PROTOCOL_KEY);
List<URL> urls = repository.findSubscribedDubboMetadataServiceURLs(serviceInterface, group, version, protocol);
listener.notify(urls);
}
@Override
public final void doUnsubscribe(URL url, NotifyListener listener) {
if (isAdminURL(url)) {
}
}
@Override
public boolean isAvailable() {
return !discoveryClient.getServices().isEmpty();
}
protected boolean isAdminURL(URL url) {
return ADMIN_PROTOCOL.equals(url.getProtocol());
}
protected boolean isDubboMetadataServiceURL(URL url) {
return DUBBO_METADATA_SERVICE_CLASS_NAME.equals(url.getServiceInterface());
}
/**
* The parameter name of {@link #servicesLookupInterval}
*/
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();
/**
* Caches the IDs of {@link ApplicationListener}
*/
private static final Set<String> registerListeners = new HashSet<>();
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* The interval in second of lookup service names(only for Dubbo-OPS)
*/
private final long servicesLookupInterval;
private final DiscoveryClient discoveryClient;
private final DubboServiceMetadataRepository repository;
private final DubboMetadataServiceProxy dubboMetadataConfigServiceProxy;
private final JSONUtils jsonUtils;
private final ConfigurableApplicationContext applicationContext;
public AbstractSpringCloudRegistry(URL url, DiscoveryClient discoveryClient,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboMetadataServiceProxy dubboMetadataConfigServiceProxy,
JSONUtils jsonUtils, ConfigurableApplicationContext applicationContext) {
super(url);
this.servicesLookupInterval = url
.getParameter(SERVICES_LOOKUP_INTERVAL_PARAM_NAME, 60L);
this.discoveryClient = discoveryClient;
this.repository = dubboServiceMetadataRepository;
this.dubboMetadataConfigServiceProxy = dubboMetadataConfigServiceProxy;
this.jsonUtils = jsonUtils;
this.applicationContext = applicationContext;
}
protected boolean shouldRegister(URL url) {
String side = url.getParameter(SIDE_KEY);
boolean should = PROVIDER_SIDE.equals(side); // Only register the Provider.
if (!should) {
if (logger.isDebugEnabled()) {
logger.debug("The URL[{}] should not be registered.", url.toString());
}
}
return should;
}
@Override
public final void doRegister(URL url) {
if (!shouldRegister(url)) {
return;
}
doRegister0(url);
}
/**
* The sub-type should implement to register
*
* @param url {@link URL}
*/
protected abstract void doRegister0(URL url);
@Override
public final void doUnregister(URL url) {
if (!shouldRegister(url)) {
return;
}
doUnregister0(url);
}
/**
* The sub-type should implement to unregister
*
* @param url {@link URL}
*/
protected abstract void doUnregister0(URL url);
@Override
public final void doSubscribe(URL url, NotifyListener listener) {
if (isAdminURL(url)) {
// TODO in future
}
else if (isDubboMetadataServiceURL(url)) { // for DubboMetadataService
subscribeDubboMetadataServiceURLs(url, listener);
}
else { // for general Dubbo Services
subscribeDubboServiceURLs(url, listener);
}
}
protected void subscribeDubboServiceURLs(URL url, NotifyListener listener) {
doSubscribeDubboServiceURLs(url, listener);
registerServiceInstancesChangedEventListener(url, listener);
}
/**
* Register a {@link ApplicationListener listener} for
* {@link ServiceInstancesChangedEvent}
*
* @param url {@link URL}
* @param listener {@link NotifyListener}
*/
private void registerServiceInstancesChangedEventListener(URL url,
NotifyListener listener) {
String listenerId = generateId(url);
if (registerListeners.add(listenerId)) {
applicationContext.addApplicationListener(
new ApplicationListener<ServiceInstancesChangedEvent>() {
@Override
public void onApplicationEvent(
ServiceInstancesChangedEvent event) {
String serviceName = event.getServiceName();
Collection<ServiceInstance> serviceInstances = event
.getServiceInstances();
subscribeDubboServiceURL(url, listener, serviceName,
s -> serviceInstances);
}
});
}
}
private void doSubscribeDubboServiceURLs(URL url, NotifyListener listener) {
Set<String> subscribedServices = repository.getSubscribedServices();
// Sync
subscribedServices.forEach(service -> subscribeDubboServiceURL(url, listener,
service, this::getServiceInstances));
}
protected void subscribeDubboServiceURL(URL url, NotifyListener listener,
String serviceName,
Function<String, Collection<ServiceInstance>> serviceInstancesFunction) {
if (logger.isInfoEnabled()) {
logger.info(
"The Dubbo Service URL[ID : {}] is being subscribed for service[name : {}]",
generateId(url), serviceName);
}
DubboMetadataService dubboMetadataService = dubboMetadataConfigServiceProxy
.getProxy(serviceName);
if (dubboMetadataService == null) { // If not found, try to initialize
if (logger.isInfoEnabled()) {
logger.info(
"The metadata of Dubbo service[key : {}] can't be found when the subscribed service[name : {}], "
+ "and then try to initialize it",
url.getServiceKey(), serviceName);
}
repository.initializeMetadata(serviceName);
dubboMetadataService = dubboMetadataConfigServiceProxy.getProxy(serviceName);
}
if (dubboMetadataService == null) { // It makes sure not-found, return immediately
if (logger.isWarnEnabled()) {
logger.warn(
"The metadata of Dubbo service[key : {}] still can't be found, it could effect the further "
+ "Dubbo service invocation",
url.getServiceKey());
}
return;
}
Collection<ServiceInstance> serviceInstances = serviceInstancesFunction
.apply(serviceName);
List<URL> allSubscribedURLs = new LinkedList<>();
if (CollectionUtils.isEmpty(serviceInstances)) {
if (logger.isWarnEnabled()) {
logger.warn(
"There is no instance from service[name : {}], and then Dubbo Service[key : {}] will not be "
+ "available , please make sure the further impact",
serviceName, url.getServiceKey());
}
/**
* URLs with {@link RegistryConstants#EMPTY_PROTOCOL}
*/
allSubscribedURLs.addAll(emptyURLs(url));
}
else {
List<URL> exportedURLs = getExportedURLs(dubboMetadataService, url);
for (URL exportedURL : exportedURLs) {
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);
}
});
allSubscribedURLs.addAll(subscribedURLs);
}
}
if (logger.isDebugEnabled()) {
logger.debug("The subscribed URL[{}] will notify all URLs : {}", url,
allSubscribedURLs);
}
listener.notify(allSubscribedURLs);
}
private String generateId(URL url) {
return url.toString(VERSION_KEY, GROUP_KEY, PROTOCOL_KEY);
}
private List<URL> emptyURLs(URL url) {
return asList(from(url).setProtocol(EMPTY_PROTOCOL).build());
}
private List<ServiceInstance> getServiceInstances(String serviceName) {
return hasText(serviceName) ? doGetServiceInstances(serviceName) : emptyList();
}
private List<ServiceInstance> doGetServiceInstances(String serviceName) {
List<ServiceInstance> serviceInstances = emptyList();
try {
serviceInstances = discoveryClient.getInstances(serviceName);
}
catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
}
return serviceInstances;
}
private List<URL> getExportedURLs(DubboMetadataService dubboMetadataService,
URL url) {
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) {
String serviceInterface = url.getServiceInterface();
String group = url.getParameter(GROUP_KEY);
String version = url.getParameter(VERSION_KEY);
String protocol = url.getParameter(PROTOCOL_KEY);
List<URL> urls = repository.findSubscribedDubboMetadataServiceURLs(
serviceInterface, group, version, protocol);
listener.notify(urls);
}
@Override
public final void doUnsubscribe(URL url, NotifyListener listener) {
if (isAdminURL(url)) {
}
}
@Override
public boolean isAvailable() {
return !discoveryClient.getServices().isEmpty();
}
protected boolean isAdminURL(URL url) {
return ADMIN_PROTOCOL.equals(url.getProtocol());
}
protected boolean isDubboMetadataServiceURL(URL url) {
return DUBBO_METADATA_SERVICE_CLASS_NAME.equals(url.getServiceInterface());
}
}

@ -16,57 +16,58 @@
*/
package com.alibaba.cloud.dubbo.registry;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.serviceregistry.Registration;
import java.net.URI;
import java.util.Map;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.serviceregistry.Registration;
/**
* The {@link Registration} of Dubbo uses an external of {@link ServiceInstance} instance as the delegate.
* The {@link Registration} of Dubbo uses an external of {@link ServiceInstance} instance
* as the delegate.
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
class DelegatingRegistration implements Registration {
private final ServiceInstance delegate;
private final ServiceInstance delegate;
public DelegatingRegistration(ServiceInstance delegate) {
this.delegate = delegate;
}
public DelegatingRegistration(ServiceInstance delegate) {
this.delegate = delegate;
}
@Override
public String getServiceId() {
return delegate.getServiceId();
}
@Override
public String getServiceId() {
return delegate.getServiceId();
}
@Override
public String getHost() {
return delegate.getHost();
}
@Override
public String getHost() {
return delegate.getHost();
}
@Override
public int getPort() {
return delegate.getPort();
}
@Override
public int getPort() {
return delegate.getPort();
}
@Override
public boolean isSecure() {
return delegate.isSecure();
}
@Override
public boolean isSecure() {
return delegate.isSecure();
}
@Override
public URI getUri() {
return delegate.getUri();
}
@Override
public URI getUri() {
return delegate.getUri();
}
@Override
public Map<String, String> getMetadata() {
return delegate.getMetadata();
}
@Override
public Map<String, String> getMetadata() {
return delegate.getMetadata();
}
@Override
public String getScheme() {
return delegate.getScheme();
}
@Override
public String getScheme() {
return delegate.getScheme();
}
}

@ -16,8 +16,6 @@
*/
package com.alibaba.cloud.dubbo.registry;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancePreRegisteredEvent;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstanceRegisteredEvent;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@ -26,6 +24,9 @@ import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstancePreRegisteredEvent;
import com.alibaba.cloud.dubbo.registry.event.ServiceInstanceRegisteredEvent;
/**
* Dubbo Service Registration Event-Publishing Aspect
*
@ -34,28 +35,31 @@ import org.springframework.context.ApplicationEventPublisherAware;
* @see ServiceInstanceRegisteredEvent
*/
@Aspect
public class DubboServiceRegistrationEventPublishingAspect implements ApplicationEventPublisherAware {
/**
* The pointcut expression for {@link ServiceRegistry#register(Registration)}
*/
public static final String REGISTER_POINTCUT_EXPRESSION =
"execution(* org.springframework.cloud.client.serviceregistry.ServiceRegistry.register(*)) && args(registration)";
private ApplicationEventPublisher applicationEventPublisher;
@Before(REGISTER_POINTCUT_EXPRESSION)
public void beforeRegister(Registration registration) {
applicationEventPublisher.publishEvent(new ServiceInstancePreRegisteredEvent(registration));
}
@After(REGISTER_POINTCUT_EXPRESSION)
public void afterRegister(Registration registration) {
applicationEventPublisher.publishEvent(new ServiceInstanceRegisteredEvent(registration));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public class DubboServiceRegistrationEventPublishingAspect
implements ApplicationEventPublisherAware {
/**
* The pointcut expression for {@link ServiceRegistry#register(Registration)}
*/
public static final String REGISTER_POINTCUT_EXPRESSION = "execution(* org.springframework.cloud.client.serviceregistry.ServiceRegistry.register(*)) && args(registration)";
private ApplicationEventPublisher applicationEventPublisher;
@Before(REGISTER_POINTCUT_EXPRESSION)
public void beforeRegister(Registration registration) {
applicationEventPublisher
.publishEvent(new ServiceInstancePreRegisteredEvent(registration));
}
@After(REGISTER_POINTCUT_EXPRESSION)
public void afterRegister(Registration registration) {
applicationEventPublisher
.publishEvent(new ServiceInstanceRegisteredEvent(registration));
}
@Override
public void setApplicationEventPublisher(
ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}

@ -19,37 +19,39 @@ package com.alibaba.cloud.dubbo.registry;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.RegistryFactory;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy;
import com.alibaba.cloud.dubbo.util.JSONUtils;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 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"
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class SpringCloudRegistry extends AbstractSpringCloudRegistry {
private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
public SpringCloudRegistry(URL url, DiscoveryClient discoveryClient,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboMetadataServiceProxy dubboMetadataConfigServiceProxy,
JSONUtils jsonUtils,
ConfigurableApplicationContext applicationContext) {
super(url, discoveryClient, dubboServiceMetadataRepository, dubboMetadataConfigServiceProxy, jsonUtils, applicationContext);
this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
}
@Override
protected void doRegister0(URL url) {
dubboServiceMetadataRepository.exportURL(url);
}
@Override
protected void doUnregister0(URL url) {
dubboServiceMetadataRepository.unexportURL(url);
}
private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
public SpringCloudRegistry(URL url, DiscoveryClient discoveryClient,
DubboServiceMetadataRepository dubboServiceMetadataRepository,
DubboMetadataServiceProxy dubboMetadataConfigServiceProxy,
JSONUtils jsonUtils, ConfigurableApplicationContext applicationContext) {
super(url, discoveryClient, dubboServiceMetadataRepository,
dubboMetadataConfigServiceProxy, jsonUtils, applicationContext);
this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
}
@Override
protected void doRegister0(URL url) {
dubboServiceMetadataRepository.exportURL(url);
}
@Override
protected void doUnregister0(URL url) {
dubboServiceMetadataRepository.unexportURL(url);
}
}

@ -16,20 +16,22 @@
*/
package com.alibaba.cloud.dubbo.registry;
import static java.lang.System.getProperty;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.registry.RegistryFactory;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy;
import com.alibaba.cloud.dubbo.util.JSONUtils;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
import static java.lang.System.getProperty;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.service.DubboMetadataServiceProxy;
import com.alibaba.cloud.dubbo.util.JSONUtils;
/**
* 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"
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see RegistryFactory
@ -37,46 +39,51 @@ import static java.lang.System.getProperty;
*/
public class SpringCloudRegistryFactory implements RegistryFactory {
public static String PROTOCOL = "spring-cloud";
public static String PROTOCOL = "spring-cloud";
public static String ADDRESS = "localhost";
public static String ADDRESS = "localhost";
private static String SERVICES_LOOKUP_SCHEDULER_THREAD_NAME_PREFIX =
getProperty("dubbo.services.lookup.scheduler.thread.name.prefix ", "dubbo-services-lookup-");
private static String SERVICES_LOOKUP_SCHEDULER_THREAD_NAME_PREFIX = getProperty(
"dubbo.services.lookup.scheduler.thread.name.prefix ",
"dubbo-services-lookup-");
private static ConfigurableApplicationContext applicationContext;
private static ConfigurableApplicationContext applicationContext;
private DiscoveryClient discoveryClient;
private DiscoveryClient discoveryClient;
private DubboServiceMetadataRepository dubboServiceMetadataRepository;
private DubboServiceMetadataRepository dubboServiceMetadataRepository;
private DubboMetadataServiceProxy dubboMetadataConfigServiceProxy;
private DubboMetadataServiceProxy dubboMetadataConfigServiceProxy;
private JSONUtils jsonUtils;
private JSONUtils jsonUtils;
private volatile boolean initialized = false;
private volatile boolean initialized = false;
public SpringCloudRegistryFactory() {
}
public SpringCloudRegistryFactory() {
}
public static void setApplicationContext(ConfigurableApplicationContext applicationContext) {
SpringCloudRegistryFactory.applicationContext = applicationContext;
}
public static void setApplicationContext(
ConfigurableApplicationContext applicationContext) {
SpringCloudRegistryFactory.applicationContext = applicationContext;
}
protected void init() {
if (initialized || applicationContext == null) {
return;
}
this.discoveryClient = applicationContext.getBean(DiscoveryClient.class);
this.dubboServiceMetadataRepository = applicationContext.getBean(DubboServiceMetadataRepository.class);
this.dubboMetadataConfigServiceProxy = applicationContext.getBean(DubboMetadataServiceProxy.class);
this.jsonUtils = applicationContext.getBean(JSONUtils.class);
}
protected void init() {
if (initialized || applicationContext == null) {
return;
}
this.discoveryClient = applicationContext.getBean(DiscoveryClient.class);
this.dubboServiceMetadataRepository = applicationContext
.getBean(DubboServiceMetadataRepository.class);
this.dubboMetadataConfigServiceProxy = applicationContext
.getBean(DubboMetadataServiceProxy.class);
this.jsonUtils = applicationContext.getBean(JSONUtils.class);
}
@Override
public Registry getRegistry(URL url) {
init();
return new SpringCloudRegistry(url, discoveryClient, dubboServiceMetadataRepository,
dubboMetadataConfigServiceProxy, jsonUtils, applicationContext);
}
@Override
public Registry getRegistry(URL url) {
init();
return new SpringCloudRegistry(url, discoveryClient,
dubboServiceMetadataRepository, dubboMetadataConfigServiceProxy,
jsonUtils, applicationContext);
}
}

@ -22,18 +22,19 @@ import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.ApplicationEvent;
/**
* The before-{@link ServiceRegistry#register(Registration) register} event for {@link ServiceInstance}
* The before-{@link ServiceRegistry#register(Registration) register} event for
* {@link ServiceInstance}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ServiceInstancePreRegisteredEvent extends ApplicationEvent {
public ServiceInstancePreRegisteredEvent(Registration source) {
super(source);
}
public ServiceInstancePreRegisteredEvent(Registration source) {
super(source);
}
@Override
public Registration getSource() {
return (Registration) super.getSource();
}
@Override
public Registration getSource() {
return (Registration) super.getSource();
}
}

@ -21,18 +21,19 @@ import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.ApplicationEvent;
/**
* The after-{@link ServiceRegistry#register(Registration) register} event for {@link Registration}
* The after-{@link ServiceRegistry#register(Registration) register} event for
* {@link Registration}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ServiceInstanceRegisteredEvent extends ApplicationEvent {
public ServiceInstanceRegisteredEvent(Registration source) {
super(source);
}
public ServiceInstanceRegisteredEvent(Registration source) {
super(source);
}
@Override
public Registration getSource() {
return (Registration) super.getSource();
}
@Override
public Registration getSource() {
return (Registration) super.getSource();
}
}

@ -16,72 +16,74 @@
*/
package com.alibaba.cloud.dubbo.registry.event;
import static java.util.Collections.unmodifiableCollection;
import java.util.Collection;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import java.util.Collection;
import static java.util.Collections.unmodifiableCollection;
/**
* An event raised after the {@link ServiceInstance instances} of one service has been changed.
* An event raised after the {@link ServiceInstance instances} of one service has been
* changed.
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ServiceInstancesChangedEvent extends ApplicationEvent {
private final String serviceName;
private final String serviceName;
private final Collection<ServiceInstance> serviceInstances;
private final Collection<ServiceInstance> serviceInstances;
/**
* Current event has been processed or not.
* Typically, Spring Event was based on sync {@link ApplicationEventMulticaster}
*
* @see SimpleApplicationEventMulticaster
*/
private boolean processed = false;
/**
* Current event has been processed or not. Typically, Spring Event was based on sync
* {@link ApplicationEventMulticaster}
*
* @see SimpleApplicationEventMulticaster
*/
private boolean processed = false;
/**
* @param serviceName The name of service that was changed
* @param serviceInstances all {@link ServiceInstance service instances}
* @throws IllegalArgumentException if source is null.
*/
public ServiceInstancesChangedEvent(String serviceName, Collection<ServiceInstance> serviceInstances) {
super(serviceName);
this.serviceName = serviceName;
this.serviceInstances = unmodifiableCollection(serviceInstances);
}
/**
* @param serviceName The name of service that was changed
* @param serviceInstances all {@link ServiceInstance service instances}
* @throws IllegalArgumentException if source is null.
*/
public ServiceInstancesChangedEvent(String serviceName,
Collection<ServiceInstance> serviceInstances) {
super(serviceName);
this.serviceName = serviceName;
this.serviceInstances = unmodifiableCollection(serviceInstances);
}
/**
* @return The name of service that was changed
*/
public String getServiceName() {
return serviceName;
}
/**
* @return The name of service that was changed
*/
public String getServiceName() {
return serviceName;
}
/**
* @return all {@link ServiceInstance service instances}
*/
public Collection<ServiceInstance> getServiceInstances() {
return serviceInstances;
}
/**
* @return all {@link ServiceInstance service instances}
*/
public Collection<ServiceInstance> getServiceInstances() {
return serviceInstances;
}
/**
* Mark current event being processed
*/
public void processed() {
processed = true;
}
/**
* Mark current event being processed
*/
public void processed() {
processed = true;
}
/**
* Current event has been processed or not
*
* @return if processed, return <code>true</code>, or <code>false</code>
*/
public boolean isProcessed() {
return processed;
}
/**
* Current event has been processed or not
*
* @return if processed, return <code>true</code>, or <code>false</code>
*/
public boolean isProcessed() {
return processed;
}
}

@ -16,12 +16,12 @@
*/
package com.alibaba.cloud.dubbo.registry.event;
import org.springframework.context.ApplicationEvent;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import org.springframework.context.ApplicationEvent;
/**
* {@link ApplicationEvent Event} raised when the subscribed services are changed
* <p>
@ -31,35 +31,36 @@ import java.util.Set;
*/
public class SubscribedServicesChangedEvent extends ApplicationEvent {
private final Set<String> oldSubscribedServices;
private final Set<String> oldSubscribedServices;
private final Set<String> newSubscribedServices;
private final Set<String> newSubscribedServices;
private final boolean changed;
private final boolean changed;
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
* @param oldSubscribedServices the subscribed services before changed
* @param newSubscribedServices the subscribed services after changed
*/
public SubscribedServicesChangedEvent(Object source, Set<String> oldSubscribedServices, Set<String> newSubscribedServices) {
super(source);
this.oldSubscribedServices = new LinkedHashSet<>(oldSubscribedServices);
this.newSubscribedServices = new LinkedHashSet<>(newSubscribedServices);
this.changed = !Objects.equals(oldSubscribedServices, newSubscribedServices);
}
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
* @param oldSubscribedServices the subscribed services before changed
* @param newSubscribedServices the subscribed services after changed
*/
public SubscribedServicesChangedEvent(Object source,
Set<String> oldSubscribedServices, Set<String> newSubscribedServices) {
super(source);
this.oldSubscribedServices = new LinkedHashSet<>(oldSubscribedServices);
this.newSubscribedServices = new LinkedHashSet<>(newSubscribedServices);
this.changed = !Objects.equals(oldSubscribedServices, newSubscribedServices);
}
public Set<String> getOldSubscribedServices() {
return oldSubscribedServices;
}
public Set<String> getOldSubscribedServices() {
return oldSubscribedServices;
}
public Set<String> getNewSubscribedServices() {
return newSubscribedServices;
}
public Set<String> getNewSubscribedServices() {
return newSubscribedServices;
}
public boolean isChanged() {
return changed;
}
public boolean isChanged() {
return changed;
}
}

@ -25,27 +25,28 @@ import org.apache.dubbo.rpc.service.GenericService;
*/
public class DubboGenericServiceExecutionContext {
private final String methodName;
private final String methodName;
private final String[] parameterTypes;
private final String[] parameterTypes;
private final Object[] parameters;
private final Object[] parameters;
public DubboGenericServiceExecutionContext(String methodName, String[] parameterTypes, Object[] parameters) {
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.parameters = parameters;
}
public DubboGenericServiceExecutionContext(String methodName, String[] parameterTypes,
Object[] parameters) {
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.parameters = parameters;
}
public String getMethodName() {
return methodName;
}
public String getMethodName() {
return methodName;
}
public String[] getParameterTypes() {
return parameterTypes;
}
public String[] getParameterTypes() {
return parameterTypes;
}
public Object[] getParameters() {
return parameters;
}
public Object[] getParameters() {
return parameters;
}
}

@ -16,19 +16,21 @@
*/
package com.alibaba.cloud.dubbo.service;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
import com.alibaba.cloud.dubbo.metadata.MethodMetadata;
import com.alibaba.cloud.dubbo.metadata.MethodParameterMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import com.alibaba.cloud.dubbo.service.parameter.DubboGenericServiceParameterResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* {@link DubboGenericServiceExecutionContext} Factory
@ -38,110 +40,119 @@ import java.util.Map;
*/
public class DubboGenericServiceExecutionContextFactory {
@Autowired(required = false)
private final List<DubboGenericServiceParameterResolver> resolvers = Collections.emptyList();
@Autowired(required = false)
private final List<DubboGenericServiceParameterResolver> resolvers = Collections
.emptyList();
@PostConstruct
public void init() {
AnnotationAwareOrderComparator.sort(resolvers);
}
@PostConstruct
public void init() {
AnnotationAwareOrderComparator.sort(resolvers);
}
public DubboGenericServiceExecutionContext create(RestMethodMetadata dubboRestMethodMetadata,
RestMethodMetadata clientMethodMetadata, Object[] arguments) {
public DubboGenericServiceExecutionContext create(
RestMethodMetadata dubboRestMethodMetadata,
RestMethodMetadata clientMethodMetadata, Object[] arguments) {
MethodMetadata dubboMethodMetadata = dubboRestMethodMetadata.getMethod();
MethodMetadata dubboMethodMetadata = dubboRestMethodMetadata.getMethod();
String methodName = dubboMethodMetadata.getName();
String methodName = dubboMethodMetadata.getName();
String[] parameterTypes = resolveParameterTypes(dubboMethodMetadata);
String[] parameterTypes = resolveParameterTypes(dubboMethodMetadata);
Object[] parameters = resolveParameters(dubboRestMethodMetadata, clientMethodMetadata, arguments);
Object[] parameters = resolveParameters(dubboRestMethodMetadata,
clientMethodMetadata, arguments);
return new DubboGenericServiceExecutionContext(methodName, parameterTypes, parameters);
}
return new DubboGenericServiceExecutionContext(methodName, parameterTypes,
parameters);
}
public DubboGenericServiceExecutionContext create(RestMethodMetadata dubboRestMethodMetadata,
HttpServerRequest request) {
MethodMetadata methodMetadata = dubboRestMethodMetadata.getMethod();
public DubboGenericServiceExecutionContext create(
RestMethodMetadata dubboRestMethodMetadata, HttpServerRequest request) {
MethodMetadata methodMetadata = dubboRestMethodMetadata.getMethod();
String methodName = methodMetadata.getName();
String methodName = methodMetadata.getName();
String[] parameterTypes = resolveParameterTypes(methodMetadata);
String[] parameterTypes = resolveParameterTypes(methodMetadata);
Object[] parameters = resolveParameters(dubboRestMethodMetadata, request);
Object[] parameters = resolveParameters(dubboRestMethodMetadata, request);
return new DubboGenericServiceExecutionContext(methodName, parameterTypes, parameters);
}
return new DubboGenericServiceExecutionContext(methodName, parameterTypes,
parameters);
}
private Map<String, Integer> buildParamNameToIndex(List<MethodParameterMetadata> params) {
Map<String, Integer> paramNameToIndex = new LinkedHashMap<>();
for (MethodParameterMetadata param : params) {
paramNameToIndex.put(param.getName(), param.getIndex());
}
return paramNameToIndex;
}
private Map<String, Integer> buildParamNameToIndex(
List<MethodParameterMetadata> params) {
Map<String, Integer> paramNameToIndex = new LinkedHashMap<>();
for (MethodParameterMetadata param : params) {
paramNameToIndex.put(param.getName(), param.getIndex());
}
return paramNameToIndex;
}
protected String[] resolveParameterTypes(MethodMetadata methodMetadata) {
protected String[] resolveParameterTypes(MethodMetadata methodMetadata) {
List<MethodParameterMetadata> params = methodMetadata.getParams();
List<MethodParameterMetadata> params = methodMetadata.getParams();
String[] parameterTypes = new String[params.size()];
String[] parameterTypes = new String[params.size()];
for (MethodParameterMetadata parameterMetadata : params) {
int index = parameterMetadata.getIndex();
String parameterType = parameterMetadata.getType();
parameterTypes[index] = parameterType;
}
for (MethodParameterMetadata parameterMetadata : params) {
int index = parameterMetadata.getIndex();
String parameterType = parameterMetadata.getType();
parameterTypes[index] = parameterType;
}
return parameterTypes;
}
return parameterTypes;
}
protected Object[] resolveParameters(RestMethodMetadata dubboRestMethodMetadata, HttpServerRequest request) {
protected Object[] resolveParameters(RestMethodMetadata dubboRestMethodMetadata,
HttpServerRequest request) {
MethodMetadata dubboMethodMetadata = dubboRestMethodMetadata.getMethod();
MethodMetadata dubboMethodMetadata = dubboRestMethodMetadata.getMethod();
List<MethodParameterMetadata> params = dubboMethodMetadata.getParams();
List<MethodParameterMetadata> params = dubboMethodMetadata.getParams();
Object[] parameters = new Object[params.size()];
Object[] parameters = new Object[params.size()];
for (MethodParameterMetadata parameterMetadata : params) {
for (MethodParameterMetadata parameterMetadata : params) {
int index = parameterMetadata.getIndex();
int index = parameterMetadata.getIndex();
for (DubboGenericServiceParameterResolver resolver : resolvers) {
Object parameter = resolver.resolve(dubboRestMethodMetadata, parameterMetadata, request);
if (parameter != null) {
parameters[index] = parameter;
break;
}
}
}
for (DubboGenericServiceParameterResolver resolver : resolvers) {
Object parameter = resolver.resolve(dubboRestMethodMetadata,
parameterMetadata, request);
if (parameter != null) {
parameters[index] = parameter;
break;
}
}
}
return parameters;
}
return parameters;
}
protected Object[] resolveParameters(RestMethodMetadata dubboRestMethodMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments) {
protected Object[] resolveParameters(RestMethodMetadata dubboRestMethodMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments) {
MethodMetadata dubboMethodMetadata = dubboRestMethodMetadata.getMethod();
MethodMetadata dubboMethodMetadata = dubboRestMethodMetadata.getMethod();
List<MethodParameterMetadata> params = dubboMethodMetadata.getParams();
List<MethodParameterMetadata> params = dubboMethodMetadata.getParams();
Object[] parameters = new Object[params.size()];
Object[] parameters = new Object[params.size()];
for (MethodParameterMetadata parameterMetadata : params) {
for (MethodParameterMetadata parameterMetadata : params) {
int index = parameterMetadata.getIndex();
int index = parameterMetadata.getIndex();
for (DubboGenericServiceParameterResolver resolver : resolvers) {
Object parameter = resolver.resolve(dubboRestMethodMetadata, parameterMetadata, clientRestMethodMetadata, arguments);
if (parameter != null) {
parameters[index] = parameter;
break;
}
}
}
for (DubboGenericServiceParameterResolver resolver : resolvers) {
Object parameter = resolver.resolve(dubboRestMethodMetadata,
parameterMetadata, clientRestMethodMetadata, arguments);
if (parameter != null) {
parameters[index] = parameter;
break;
}
}
}
return parameters;
}
return parameters;
}
}

@ -16,14 +16,27 @@
*/
package com.alibaba.cloud.dubbo.service;
import static java.util.Collections.emptyMap;
import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
import static org.springframework.util.StringUtils.commaDelimitedListToStringArray;
import java.beans.PropertyEditorSupport;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.PreDestroy;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.rpc.service.GenericService;
import com.alibaba.cloud.dubbo.metadata.DubboRestServiceMetadata;
import com.alibaba.cloud.dubbo.metadata.ServiceRestMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
@ -33,19 +46,8 @@ import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.util.StringUtils;
import org.springframework.validation.DataBinder;
import javax.annotation.PreDestroy;
import java.beans.PropertyEditorSupport;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static java.util.Collections.emptyMap;
import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
import static org.springframework.util.StringUtils.commaDelimitedListToStringArray;
import com.alibaba.cloud.dubbo.metadata.DubboRestServiceMetadata;
import com.alibaba.cloud.dubbo.metadata.ServiceRestMetadata;
/**
* Dubbo {@link GenericService} Factory
@ -54,104 +56,114 @@ import static org.springframework.util.StringUtils.commaDelimitedListToStringArr
*/
public class DubboGenericServiceFactory {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ConcurrentMap<Integer, ReferenceBean<GenericService>> cache = new ConcurrentHashMap<>();
@Autowired
private ObjectProvider<List<RegistryConfig>> registryConfigs;
public GenericService create(DubboRestServiceMetadata dubboServiceMetadata,
Map<String, Object> dubboTranslatedAttributes) {
ReferenceBean<GenericService> referenceBean = build(dubboServiceMetadata.getServiceRestMetadata(), dubboTranslatedAttributes);
return referenceBean == null ? null : referenceBean.get();
}
public GenericService create(String serviceName, Class<?> serviceClass, String version) {
String interfaceName = serviceClass.getName();
ReferenceBean<GenericService> referenceBean = build(interfaceName, version, serviceName, emptyMap());
return referenceBean.get();
}
private ReferenceBean<GenericService> build(ServiceRestMetadata serviceRestMetadata,
Map<String, Object> dubboTranslatedAttributes) {
String urlValue = serviceRestMetadata.getUrl();
URL url = URL.valueOf(urlValue);
String interfaceName = url.getServiceInterface();
String version = url.getParameter(VERSION_KEY);
String group = url.getParameter(GROUP_KEY);
return build(interfaceName, version, group, dubboTranslatedAttributes);
}
private ReferenceBean<GenericService> build(String interfaceName, String version, String group,
Map<String, Object> dubboTranslatedAttributes) {
Integer key = Objects.hash(interfaceName, version, group, dubboTranslatedAttributes);
return cache.computeIfAbsent(key, k -> {
ReferenceBean<GenericService> referenceBean = new ReferenceBean<>();
referenceBean.setGeneric(true);
referenceBean.setInterface(interfaceName);
referenceBean.setVersion(version);
referenceBean.setGroup(group);
bindReferenceBean(referenceBean, dubboTranslatedAttributes);
return referenceBean;
});
}
private void bindReferenceBean(ReferenceBean<GenericService> referenceBean, Map<String, Object> dubboTranslatedAttributes) {
DataBinder dataBinder = new DataBinder(referenceBean);
// Register CustomEditors for special fields
dataBinder.registerCustomEditor(String.class, "filter", new StringTrimmerEditor(true));
dataBinder.registerCustomEditor(String.class, "listener", new StringTrimmerEditor(true));
dataBinder.registerCustomEditor(Map.class, "parameters", new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws java.lang.IllegalArgumentException {
// Trim all whitespace
String content = StringUtils.trimAllWhitespace(text);
if (!StringUtils.hasText(content)) { // No content , ignore directly
return;
}
// replace "=" to ","
content = StringUtils.replace(content, "=", ",");
// replace ":" to ","
content = StringUtils.replace(content, ":", ",");
// String[] to Map
Map<String, String> parameters = CollectionUtils.toStringMap(commaDelimitedListToStringArray(content));
setValue(parameters);
}
});
// ignore "registries" field and then use RegistryConfig beans
dataBinder.setDisallowedFields("registries");
dataBinder.bind(new MutablePropertyValues(dubboTranslatedAttributes));
registryConfigs.ifAvailable(referenceBean::setRegistries);
}
@PreDestroy
public void destroy() {
destroyReferenceBeans();
cache.values();
}
private void destroyReferenceBeans() {
Collection<ReferenceBean<GenericService>> referenceBeans = cache.values();
if (logger.isInfoEnabled()) {
logger.info("The Dubbo GenericService ReferenceBeans are destroying...");
}
for (ReferenceBean referenceBean : referenceBeans) {
referenceBean.destroy(); // destroy ReferenceBean
if (logger.isInfoEnabled()) {
logger.info("Destroyed the ReferenceBean : {} ", referenceBean);
}
}
}
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ConcurrentMap<Integer, ReferenceBean<GenericService>> cache = new ConcurrentHashMap<>();
@Autowired
private ObjectProvider<List<RegistryConfig>> registryConfigs;
public GenericService create(DubboRestServiceMetadata dubboServiceMetadata,
Map<String, Object> dubboTranslatedAttributes) {
ReferenceBean<GenericService> referenceBean = build(
dubboServiceMetadata.getServiceRestMetadata(), dubboTranslatedAttributes);
return referenceBean == null ? null : referenceBean.get();
}
public GenericService create(String serviceName, Class<?> serviceClass,
String version) {
String interfaceName = serviceClass.getName();
ReferenceBean<GenericService> referenceBean = build(interfaceName, version,
serviceName, emptyMap());
return referenceBean.get();
}
private ReferenceBean<GenericService> build(ServiceRestMetadata serviceRestMetadata,
Map<String, Object> dubboTranslatedAttributes) {
String urlValue = serviceRestMetadata.getUrl();
URL url = URL.valueOf(urlValue);
String interfaceName = url.getServiceInterface();
String version = url.getParameter(VERSION_KEY);
String group = url.getParameter(GROUP_KEY);
return build(interfaceName, version, group, dubboTranslatedAttributes);
}
private ReferenceBean<GenericService> build(String interfaceName, String version,
String group, Map<String, Object> dubboTranslatedAttributes) {
Integer key = Objects.hash(interfaceName, version, group,
dubboTranslatedAttributes);
return cache.computeIfAbsent(key, k -> {
ReferenceBean<GenericService> referenceBean = new ReferenceBean<>();
referenceBean.setGeneric(true);
referenceBean.setInterface(interfaceName);
referenceBean.setVersion(version);
referenceBean.setGroup(group);
bindReferenceBean(referenceBean, dubboTranslatedAttributes);
return referenceBean;
});
}
private void bindReferenceBean(ReferenceBean<GenericService> referenceBean,
Map<String, Object> dubboTranslatedAttributes) {
DataBinder dataBinder = new DataBinder(referenceBean);
// Register CustomEditors for special fields
dataBinder.registerCustomEditor(String.class, "filter",
new StringTrimmerEditor(true));
dataBinder.registerCustomEditor(String.class, "listener",
new StringTrimmerEditor(true));
dataBinder.registerCustomEditor(Map.class, "parameters",
new PropertyEditorSupport() {
@Override
public void setAsText(String text)
throws java.lang.IllegalArgumentException {
// Trim all whitespace
String content = StringUtils.trimAllWhitespace(text);
if (!StringUtils.hasText(content)) { // No content , ignore
// directly
return;
}
// replace "=" to ","
content = StringUtils.replace(content, "=", ",");
// replace ":" to ","
content = StringUtils.replace(content, ":", ",");
// String[] to Map
Map<String, String> parameters = CollectionUtils
.toStringMap(commaDelimitedListToStringArray(content));
setValue(parameters);
}
});
// ignore "registries" field and then use RegistryConfig beans
dataBinder.setDisallowedFields("registries");
dataBinder.bind(new MutablePropertyValues(dubboTranslatedAttributes));
registryConfigs.ifAvailable(referenceBean::setRegistries);
}
@PreDestroy
public void destroy() {
destroyReferenceBeans();
cache.values();
}
private void destroyReferenceBeans() {
Collection<ReferenceBean<GenericService>> referenceBeans = cache.values();
if (logger.isInfoEnabled()) {
logger.info("The Dubbo GenericService ReferenceBeans are destroying...");
}
for (ReferenceBean referenceBean : referenceBeans) {
referenceBean.destroy(); // destroy ReferenceBean
if (logger.isInfoEnabled()) {
logger.info("Destroyed the ReferenceBean : {} ", referenceBean);
}
}
}
}

@ -16,61 +16,63 @@
*/
package com.alibaba.cloud.dubbo.service;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.annotation.Service;
import com.alibaba.cloud.dubbo.metadata.ServiceRestMetadata;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 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.
* 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
* 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>
*/
public interface DubboMetadataService {
/**
* 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
*/
String getServiceRestMetadata();
/**
* 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
*/
String getServiceRestMetadata();
/**
* Get all exported {@link URL#getServiceKey() service keys}
*
* @return non-null read-only {@link Set}
*/
Set<String> getAllServiceKeys();
/**
* 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 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);
/**
* 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,6 +16,11 @@
*/
package com.alibaba.cloud.dubbo.service;
import java.util.List;
import java.util.function.Supplier;
import javax.annotation.PreDestroy;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
@ -28,10 +33,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.function.Supplier;
/**
* {@link DubboMetadataService} exporter
*
@ -40,70 +41,71 @@ import java.util.function.Supplier;
@Component
public class DubboMetadataServiceExporter {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ApplicationConfig applicationConfig;
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectProvider<DubboMetadataService> dubboMetadataService;
@Autowired
private ApplicationConfig applicationConfig;
@Autowired
private Supplier<ProtocolConfig> protocolConfigSupplier;
@Autowired
private ObjectProvider<DubboMetadataService> dubboMetadataService;
@Value("${spring.application.name:${dubbo.application.name:application}}")
private String currentApplicationName;
@Autowired
private Supplier<ProtocolConfig> protocolConfigSupplier;
/**
* The ServiceConfig of DubboMetadataConfigService to be exported, can be nullable.
*/
private ServiceConfig<DubboMetadataService> serviceConfig;
@Value("${spring.application.name:${dubbo.application.name:application}}")
private String currentApplicationName;
/**
* export {@link DubboMetadataService} as Dubbo service
*
* @return the exported {@link URL URLs}
*/
public List<URL> export() {
/**
* The ServiceConfig of DubboMetadataConfigService to be exported, can be nullable.
*/
private ServiceConfig<DubboMetadataService> serviceConfig;
if (serviceConfig == null || !serviceConfig.isExported()) {
/**
* export {@link DubboMetadataService} as Dubbo service
*
* @return the exported {@link URL URLs}
*/
public List<URL> export() {
serviceConfig = new ServiceConfig<>();
if (serviceConfig == null || !serviceConfig.isExported()) {
serviceConfig.setInterface(DubboMetadataService.class);
// Use DubboMetadataService.VERSION as the Dubbo Service version
serviceConfig.setVersion(DubboMetadataService.VERSION);
// Use current Spring application name as the Dubbo Service group
serviceConfig.setGroup(currentApplicationName);
serviceConfig.setRef(dubboMetadataService.getIfAvailable());
serviceConfig.setApplication(applicationConfig);
serviceConfig.setProtocol(protocolConfigSupplier.get());
serviceConfig = new ServiceConfig<>();
serviceConfig.export();
serviceConfig.setInterface(DubboMetadataService.class);
// Use DubboMetadataService.VERSION as the Dubbo Service version
serviceConfig.setVersion(DubboMetadataService.VERSION);
// Use current Spring application name as the Dubbo Service group
serviceConfig.setGroup(currentApplicationName);
serviceConfig.setRef(dubboMetadataService.getIfAvailable());
serviceConfig.setApplication(applicationConfig);
serviceConfig.setProtocol(protocolConfigSupplier.get());
if (logger.isInfoEnabled()) {
logger.info("The Dubbo service[{}] has been exported.", serviceConfig.toString());
}
}
serviceConfig.export();
return serviceConfig.getExportedUrls();
}
if (logger.isInfoEnabled()) {
logger.info("The Dubbo service[{}] has been exported.",
serviceConfig.toString());
}
}
return serviceConfig.getExportedUrls();
}
/**
* unexport {@link DubboMetadataService}
*/
@PreDestroy
public void unexport() {
/**
* unexport {@link DubboMetadataService}
*/
@PreDestroy
public void unexport() {
if (serviceConfig == null || serviceConfig.isUnexported()) {
return;
}
if (serviceConfig == null || serviceConfig.isUnexported()) {
return;
}
serviceConfig.unexport();
serviceConfig.unexport();
if (logger.isInfoEnabled()) {
logger.info("The Dubbo service[{}] has been unexported.", serviceConfig.toString());
}
}
if (logger.isInfoEnabled()) {
logger.info("The Dubbo service[{}] has been unexported.",
serviceConfig.toString());
}
}
}

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

@ -16,13 +16,13 @@
*/
package com.alibaba.cloud.dubbo.service;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.DisposableBean;
import static java.lang.reflect.Proxy.newProxyInstance;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static java.lang.reflect.Proxy.newProxyInstance;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.DisposableBean;
/**
* The proxy of {@link DubboMetadataService}
@ -31,54 +31,58 @@ import static java.lang.reflect.Proxy.newProxyInstance;
*/
public class DubboMetadataServiceProxy implements BeanClassLoaderAware, DisposableBean {
private final DubboGenericServiceFactory dubboGenericServiceFactory;
private final Map<String, DubboMetadataService> dubboMetadataServiceCache = new ConcurrentHashMap<>();
private ClassLoader classLoader;
private final DubboGenericServiceFactory dubboGenericServiceFactory;
private final Map<String, DubboMetadataService> dubboMetadataServiceCache = new ConcurrentHashMap<>();
private ClassLoader classLoader;
public DubboMetadataServiceProxy(DubboGenericServiceFactory dubboGenericServiceFactory) {
this.dubboGenericServiceFactory = dubboGenericServiceFactory;
}
public DubboMetadataServiceProxy(
DubboGenericServiceFactory dubboGenericServiceFactory) {
this.dubboGenericServiceFactory = dubboGenericServiceFactory;
}
/**
* Initializes {@link DubboMetadataService}'s Proxy
*
* @param serviceName the service name
* @param version the service version
* @return a {@link DubboMetadataService} proxy
*/
public DubboMetadataService initProxy(String serviceName, String version) {
return dubboMetadataServiceCache.computeIfAbsent(serviceName, name -> newProxy(name, version));
}
/**
* Initializes {@link DubboMetadataService}'s Proxy
*
* @param serviceName the service name
* @param version the service version
* @return a {@link DubboMetadataService} proxy
*/
public DubboMetadataService initProxy(String serviceName, String version) {
return dubboMetadataServiceCache.computeIfAbsent(serviceName,
name -> newProxy(name, version));
}
/**
* 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);
}
/**
* 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
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void destroy() throws Exception {
dubboMetadataServiceCache.clear();
}
@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));
}
/**
* 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));
}
}

@ -16,15 +16,8 @@
*/
package com.alibaba.cloud.dubbo.service;
import org.apache.dubbo.common.URL;
import com.alibaba.cloud.dubbo.metadata.ServiceRestMetadata;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.util.JSONUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import static java.util.Collections.unmodifiableMap;
import static org.springframework.util.CollectionUtils.isEmpty;
import java.util.Collections;
import java.util.HashMap;
@ -32,8 +25,16 @@ 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;
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 com.alibaba.cloud.dubbo.metadata.ServiceRestMetadata;
import com.alibaba.cloud.dubbo.metadata.repository.DubboServiceMetadataRepository;
import com.alibaba.cloud.dubbo.util.JSONUtils;
/**
* Introspective {@link DubboMetadataService} implementation
@ -42,55 +43,57 @@ import static org.springframework.util.CollectionUtils.isEmpty;
*/
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();
}
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();
}
}

@ -16,67 +16,67 @@
*/
package com.alibaba.cloud.dubbo.service.parameter;
import static org.springframework.context.ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME;
import static org.springframework.util.ClassUtils.resolveClassName;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import static org.springframework.context.ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME;
import static org.springframework.util.ClassUtils.resolveClassName;
/**
* Abstract {@link DubboGenericServiceParameterResolver} implementation
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class AbstractDubboGenericServiceParameterResolver
implements DubboGenericServiceParameterResolver, BeanClassLoaderAware {
implements DubboGenericServiceParameterResolver, BeanClassLoaderAware {
private int order;
private int order;
@Autowired(required = false)
@Qualifier(CONVERSION_SERVICE_BEAN_NAME)
private ConversionService conversionService = new DefaultFormattingConversionService();
@Autowired(required = false)
@Qualifier(CONVERSION_SERVICE_BEAN_NAME)
private ConversionService conversionService = new DefaultFormattingConversionService();
private ClassLoader classLoader;
private ClassLoader classLoader;
public ConversionService getConversionService() {
return conversionService;
}
public ConversionService getConversionService() {
return conversionService;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public ClassLoader getClassLoader() {
return classLoader;
}
public ClassLoader getClassLoader() {
return classLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public int getOrder() {
return order;
}
@Override
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public void setOrder(int order) {
this.order = order;
}
protected Class<?> resolveClass(String className) {
return resolveClassName(className, classLoader);
}
protected Class<?> resolveClass(String className) {
return resolveClassName(className, classLoader);
}
protected Object resolveValue(Object parameterValue, String parameterType) {
Class<?> targetType = resolveClass(parameterType);
return resolveValue(parameterValue, targetType);
}
protected Object resolveValue(Object parameterValue, String parameterType) {
Class<?> targetType = resolveClass(parameterType);
return resolveValue(parameterValue, targetType);
}
protected Object resolveValue(Object parameterValue, Class<?> parameterType) {
return conversionService.convert(parameterValue, parameterType);
}
protected Object resolveValue(Object parameterValue, Class<?> parameterType) {
return conversionService.convert(parameterValue, parameterType);
}
}

@ -16,107 +16,117 @@
*/
package com.alibaba.cloud.dubbo.service.parameter;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
import com.alibaba.cloud.dubbo.metadata.MethodParameterMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import static org.springframework.util.ObjectUtils.isEmpty;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static org.springframework.util.ObjectUtils.isEmpty;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
import com.alibaba.cloud.dubbo.metadata.MethodParameterMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
/**
* Abstract HTTP Names Value {@link DubboGenericServiceParameterResolver Dubbo GenericService Parameter Resolver}
* Abstract HTTP Names Value {@link DubboGenericServiceParameterResolver Dubbo
* GenericService Parameter Resolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public abstract class AbstractNamedValueServiceParameterResolver extends AbstractDubboGenericServiceParameterResolver {
public abstract class AbstractNamedValueServiceParameterResolver
extends AbstractDubboGenericServiceParameterResolver {
/**
* Get the {@link MultiValueMap} of names and values
*
* @param request
* @return
*/
protected abstract MultiValueMap<String, String> getNameAndValuesMap(HttpServerRequest request);
/**
* Get the {@link MultiValueMap} of names and values
*
* @param request
* @return
*/
protected abstract MultiValueMap<String, String> getNameAndValuesMap(
HttpServerRequest request);
@Override
public Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
HttpServerRequest request) {
@Override
public Object resolve(RestMethodMetadata restMethodMetadata,
MethodParameterMetadata methodParameterMetadata, HttpServerRequest request) {
Collection<String> names = getNames(restMethodMetadata, methodParameterMetadata);
Collection<String> names = getNames(restMethodMetadata, methodParameterMetadata);
if (isEmpty(names)) { // index can't match
return null;
}
if (isEmpty(names)) { // index can't match
return null;
}
MultiValueMap<String, String> nameAndValues = getNameAndValuesMap(request);
MultiValueMap<String, String> nameAndValues = getNameAndValuesMap(request);
String targetName = null;
String targetName = null;
for (String name : names) {
if (nameAndValues.containsKey(name)) {
targetName = name;
break;
}
}
for (String name : names) {
if (nameAndValues.containsKey(name)) {
targetName = name;
break;
}
}
if (targetName == null) { // request parameter is abstract
return null;
}
if (targetName == null) { // request parameter is abstract
return null;
}
Class<?> parameterType = resolveClass(methodParameterMetadata.getType());
Class<?> parameterType = resolveClass(methodParameterMetadata.getType());
Object paramValue = null;
Object paramValue = null;
if (parameterType.isArray()) { // Array type
paramValue = nameAndValues.get(targetName);
} else {
paramValue = nameAndValues.getFirst(targetName);
}
if (parameterType.isArray()) { // Array type
paramValue = nameAndValues.get(targetName);
}
else {
paramValue = nameAndValues.getFirst(targetName);
}
return resolveValue(paramValue, parameterType);
}
return resolveValue(paramValue, parameterType);
}
@Override
public Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments) {
@Override
public Object resolve(RestMethodMetadata restMethodMetadata,
MethodParameterMetadata methodParameterMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments) {
Collection<String> names = getNames(restMethodMetadata, methodParameterMetadata);
Collection<String> names = getNames(restMethodMetadata, methodParameterMetadata);
if (isEmpty(names)) { // index can't match
return null;
}
if (isEmpty(names)) { // index can't match
return null;
}
Integer index = null;
Integer index = null;
Map<Integer, Collection<String>> clientIndexToName = clientRestMethodMetadata.getIndexToName();
Map<Integer, Collection<String>> clientIndexToName = clientRestMethodMetadata
.getIndexToName();
for (Map.Entry<Integer, Collection<String>> entry : clientIndexToName.entrySet()) {
for (Map.Entry<Integer, Collection<String>> entry : clientIndexToName
.entrySet()) {
Collection<String> clientParamNames = entry.getValue();
Collection<String> clientParamNames = entry.getValue();
if (CollectionUtils.containsAny(names, clientParamNames)) {
index = entry.getKey();
break;
}
}
if (CollectionUtils.containsAny(names, clientParamNames)) {
index = entry.getKey();
break;
}
}
return index > -1 ? arguments[index] : null;
}
return index > -1 ? arguments[index] : null;
}
protected Collection<String> getNames(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata) {
protected Collection<String> getNames(RestMethodMetadata restMethodMetadata,
MethodParameterMetadata methodParameterMetadata) {
Map<Integer, Collection<String>> indexToName = restMethodMetadata.getIndexToName();
Map<Integer, Collection<String>> indexToName = restMethodMetadata
.getIndexToName();
int index = methodParameterMetadata.getIndex();
int index = methodParameterMetadata.getIndex();
Collection<String> paramNames = indexToName.get(index);
Collection<String> paramNames = indexToName.get(index);
return paramNames == null ? Collections.emptyList() : paramNames;
}
return paramNames == null ? Collections.emptyList() : paramNames;
}
}

@ -18,10 +18,11 @@ package com.alibaba.cloud.dubbo.service.parameter;
import org.apache.dubbo.rpc.service.GenericService;
import org.springframework.core.Ordered;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
import com.alibaba.cloud.dubbo.metadata.MethodParameterMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import org.springframework.core.Ordered;
/**
* Dubbo {@link GenericService} Parameter Resolver
@ -30,14 +31,15 @@ import org.springframework.core.Ordered;
*/
public interface DubboGenericServiceParameterResolver extends Ordered {
/**
* Resolves a method parameter into an argument value from a given request.
*
* @return
*/
Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
HttpServerRequest request);
/**
* Resolves a method parameter into an argument value from a given request.
*
* @return
*/
Object resolve(RestMethodMetadata restMethodMetadata,
MethodParameterMetadata methodParameterMetadata, HttpServerRequest request);
Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments);
Object resolve(RestMethodMetadata restMethodMetadata,
MethodParameterMetadata methodParameterMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments);
}

@ -16,25 +16,29 @@
*/
package com.alibaba.cloud.dubbo.service.parameter;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
import org.springframework.util.MultiValueMap;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
/**
* HTTP Request Path Variable {@link DubboGenericServiceParameterResolver Dubbo GenericService Parameter Resolver}
* HTTP Request Path Variable {@link DubboGenericServiceParameterResolver Dubbo
* GenericService Parameter Resolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class PathVariableServiceParameterResolver extends AbstractNamedValueServiceParameterResolver {
public class PathVariableServiceParameterResolver
extends AbstractNamedValueServiceParameterResolver {
public static final int DEFAULT_ORDER = 3;
public static final int DEFAULT_ORDER = 3;
public PathVariableServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
public PathVariableServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
@Override
protected MultiValueMap<String, String> getNameAndValuesMap(HttpServerRequest request) {
return request.getQueryParams();
}
@Override
protected MultiValueMap<String, String> getNameAndValuesMap(
HttpServerRequest request) {
return request.getQueryParams();
}
}

@ -16,102 +16,112 @@
*/
package com.alibaba.cloud.dubbo.service.parameter;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
import com.alibaba.cloud.dubbo.http.converter.HttpMessageConverterHolder;
import com.alibaba.cloud.dubbo.http.util.HttpMessageConverterResolver;
import com.alibaba.cloud.dubbo.metadata.MethodParameterMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
import java.io.IOException;
import java.util.Collections;
import java.util.Objects;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Collections;
import java.util.Objects;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
import com.alibaba.cloud.dubbo.http.converter.HttpMessageConverterHolder;
import com.alibaba.cloud.dubbo.http.util.HttpMessageConverterResolver;
import com.alibaba.cloud.dubbo.metadata.MethodParameterMetadata;
import com.alibaba.cloud.dubbo.metadata.RestMethodMetadata;
/**
* HTTP Request Body {@link DubboGenericServiceParameterResolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class RequestBodyServiceParameterResolver extends AbstractDubboGenericServiceParameterResolver {
public class RequestBodyServiceParameterResolver
extends AbstractDubboGenericServiceParameterResolver {
public static final int DEFAULT_ORDER = 7;
public static final int DEFAULT_ORDER = 7;
@Autowired
private ObjectProvider<HttpMessageConverters> httpMessageConverters;
@Autowired
private ObjectProvider<HttpMessageConverters> httpMessageConverters;
private HttpMessageConverterResolver httpMessageConverterResolver;
private HttpMessageConverterResolver httpMessageConverterResolver;
public RequestBodyServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
public RequestBodyServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
@PostConstruct
public void init() {
HttpMessageConverters httpMessageConverters = this.httpMessageConverters.getIfAvailable();
@PostConstruct
public void init() {
HttpMessageConverters httpMessageConverters = this.httpMessageConverters
.getIfAvailable();
httpMessageConverterResolver = new HttpMessageConverterResolver(httpMessageConverters == null ?
Collections.emptyList() : httpMessageConverters.getConverters(),
getClassLoader());
}
httpMessageConverterResolver = new HttpMessageConverterResolver(
httpMessageConverters == null ? Collections.emptyList()
: httpMessageConverters.getConverters(),
getClassLoader());
}
private boolean supportParameter(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata) {
private boolean supportParameter(RestMethodMetadata restMethodMetadata,
MethodParameterMetadata methodParameterMetadata) {
Integer index = methodParameterMetadata.getIndex();
Integer index = methodParameterMetadata.getIndex();
Integer bodyIndex = restMethodMetadata.getBodyIndex();
Integer bodyIndex = restMethodMetadata.getBodyIndex();
if (!Objects.equals(index, bodyIndex)) {
return false;
}
if (!Objects.equals(index, bodyIndex)) {
return false;
}
Class<?> parameterType = resolveClass(methodParameterMetadata.getType());
Class<?> parameterType = resolveClass(methodParameterMetadata.getType());
Class<?> bodyType = resolveClass(restMethodMetadata.getBodyType());
Class<?> bodyType = resolveClass(restMethodMetadata.getBodyType());
return Objects.equals(parameterType, bodyType);
}
return Objects.equals(parameterType, bodyType);
}
@Override
public Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
HttpServerRequest request) {
@Override
public Object resolve(RestMethodMetadata restMethodMetadata,
MethodParameterMetadata methodParameterMetadata, HttpServerRequest request) {
if (!supportParameter(restMethodMetadata, methodParameterMetadata)) {
return null;
}
if (!supportParameter(restMethodMetadata, methodParameterMetadata)) {
return null;
}
Object result = null;
Object result = null;
Class<?> parameterType = resolveClass(methodParameterMetadata.getType());
Class<?> parameterType = resolveClass(methodParameterMetadata.getType());
HttpMessageConverterHolder holder = httpMessageConverterResolver.resolve(request, parameterType);
HttpMessageConverterHolder holder = httpMessageConverterResolver.resolve(request,
parameterType);
if (holder != null) {
HttpMessageConverter converter = holder.getConverter();
try {
result = converter.read(parameterType, request);
} catch (IOException e) {
throw new HttpMessageNotReadableException("I/O error while reading input message", e);
}
}
if (holder != null) {
HttpMessageConverter converter = holder.getConverter();
try {
result = converter.read(parameterType, request);
}
catch (IOException e) {
throw new HttpMessageNotReadableException(
"I/O error while reading input message", e);
}
}
return result;
}
return result;
}
@Override
public Object resolve(RestMethodMetadata restMethodMetadata, MethodParameterMetadata methodParameterMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments) {
@Override
public Object resolve(RestMethodMetadata restMethodMetadata,
MethodParameterMetadata methodParameterMetadata,
RestMethodMetadata clientRestMethodMetadata, Object[] arguments) {
if (!supportParameter(restMethodMetadata, methodParameterMetadata)) {
return null;
}
if (!supportParameter(restMethodMetadata, methodParameterMetadata)) {
return null;
}
Integer clientBodyIndex = clientRestMethodMetadata.getBodyIndex();
return arguments[clientBodyIndex];
}
Integer clientBodyIndex = clientRestMethodMetadata.getBodyIndex();
return arguments[clientBodyIndex];
}
}

@ -16,25 +16,29 @@
*/
package com.alibaba.cloud.dubbo.service.parameter;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
import org.springframework.util.MultiValueMap;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
/**
* HTTP Request Header {@link DubboGenericServiceParameterResolver Dubbo GenericService Parameter Resolver}
* HTTP Request Header {@link DubboGenericServiceParameterResolver Dubbo GenericService
* Parameter Resolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class RequestHeaderServiceParameterResolver extends AbstractNamedValueServiceParameterResolver {
public class RequestHeaderServiceParameterResolver
extends AbstractNamedValueServiceParameterResolver {
public static final int DEFAULT_ORDER = 9;
public static final int DEFAULT_ORDER = 9;
public RequestHeaderServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
public RequestHeaderServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
@Override
protected MultiValueMap<String, String> getNameAndValuesMap(HttpServerRequest request) {
return request.getHeaders();
}
@Override
protected MultiValueMap<String, String> getNameAndValuesMap(
HttpServerRequest request) {
return request.getHeaders();
}
}

@ -16,25 +16,29 @@
*/
package com.alibaba.cloud.dubbo.service.parameter;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
import org.springframework.util.MultiValueMap;
import com.alibaba.cloud.dubbo.http.HttpServerRequest;
/**
* HTTP Request Parameter {@link DubboGenericServiceParameterResolver Dubbo GenericService Parameter Resolver}
* HTTP Request Parameter {@link DubboGenericServiceParameterResolver Dubbo GenericService
* Parameter Resolver}
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class RequestParamServiceParameterResolver extends AbstractNamedValueServiceParameterResolver {
public class RequestParamServiceParameterResolver
extends AbstractNamedValueServiceParameterResolver {
public static final int DEFAULT_ORDER = 1;
public static final int DEFAULT_ORDER = 1;
public RequestParamServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
public RequestParamServiceParameterResolver() {
super();
setOrder(DEFAULT_ORDER);
}
@Override
protected MultiValueMap<String, String> getNameAndValuesMap(HttpServerRequest request) {
return request.getQueryParams();
}
@Override
protected MultiValueMap<String, String> getNameAndValuesMap(
HttpServerRequest request) {
return request.getQueryParams();
}
}

@ -16,21 +16,23 @@
*/
package com.alibaba.cloud.dubbo.util;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import org.apache.dubbo.common.URL;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* JSON Utilities class
@ -39,47 +41,49 @@ import java.util.stream.Collectors;
*/
public class JSONUtils {
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();
@PostConstruct
public void init() {
this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}
@PostConstruct
public void init() {
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(Collection<URL> urls) {
return toJSON(urls.stream().map(URL::toFullString).collect(Collectors.toSet()));
}
public String toJSON(Object object) {
String jsonContent = null;
try {
jsonContent = objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
}
return jsonContent;
}
public String toJSON(Object object) {
String jsonContent = null;
try {
jsonContent = objectMapper.writeValueAsString(object);
}
catch (JsonProcessingException e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
}
return jsonContent;
}
public List<URL> toURLs(String urlsJSON) {
List<String> list = toList(urlsJSON);
return list.stream().map(URL::valueOf).collect(Collectors.toList());
}
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) {
List<String> list = Collections.emptyList();
try {
if (StringUtils.hasText(json)) {
list = objectMapper.readValue(json, List.class);
}
} catch (IOException e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
}
return list;
}
public List<String> toList(String json) {
List<String> list = Collections.emptyList();
try {
if (StringUtils.hasText(json)) {
list = objectMapper.readValue(json, List.class);
}
}
catch (IOException e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
}
return list;
}
}

Loading…
Cancel
Save