diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/annotation/DubboTransported.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/annotation/DubboTransported.java
new file mode 100644
index 00000000..54947a92
--- /dev/null
+++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/annotation/DubboTransported.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.alibaba.dubbo.annotation;
+
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.client.RestTemplate;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * {@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 FeignClient @FeignClient} annotated classes:
+ *
+ * If {@link DubboTransported @DubboTransported} annotated classes, the invocation of all methods of
+ * {@link FeignClient @FeignClient} annotated classes.
+ *
+ *
+ * If {@link DubboTransported @DubboTransported} annotated methods of {@link FeignClient @FeignClient} annotated classes.
+ *
+ *
+ * - {@link LoadBalanced @LoadBalanced} {@link RestTemplate} annotated field, method and parameters
+ *
+ *
+ *
+ * @see FeignClient
+ * @see LoadBalanced
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@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 protocol is "failover"
+ */
+ String cluster() default "${dubbo.transport.cluster:failover}";
+}
diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboOpenFeignAutoConfiguration.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboOpenFeignAutoConfiguration.java
index d37d8dc0..a3d208c9 100644
--- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboOpenFeignAutoConfiguration.java
+++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/autoconfigure/DubboOpenFeignAutoConfiguration.java
@@ -23,13 +23,14 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.cloud.alibaba.dubbo.metadata.resolver.FeignMetadataResolver;
+import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
+import org.springframework.cloud.alibaba.dubbo.metadata.resolver.DubboServiceBeanMetadataResolver;
import org.springframework.cloud.alibaba.dubbo.metadata.resolver.MetadataResolver;
-import org.springframework.cloud.alibaba.dubbo.openfeign.DubboFeignClientsConfiguration;
-import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.cloud.alibaba.dubbo.openfeign.TargeterBeanPostProcessor;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
/**
@@ -39,7 +40,6 @@ import org.springframework.context.annotation.Configuration;
*/
@ConditionalOnClass(value = Feign.class)
@AutoConfigureAfter(FeignAutoConfiguration.class)
-@EnableFeignClients(defaultConfiguration = DubboFeignClientsConfiguration.class)
@Configuration
public class DubboOpenFeignAutoConfiguration {
@@ -49,6 +49,13 @@ public class DubboOpenFeignAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MetadataResolver metadataJsonResolver(ObjectProvider contract) {
- return new FeignMetadataResolver(currentApplicationName, contract);
+ return new DubboServiceBeanMetadataResolver(currentApplicationName, contract);
}
+
+ @Bean
+ public TargeterBeanPostProcessor targeterBeanPostProcessor(Environment environment,
+ DubboServiceMetadataRepository dubboServiceMetadataRepository) {
+ return new TargeterBeanPostProcessor(environment, dubboServiceMetadataRepository);
+ }
+
}
\ No newline at end of file
diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboTransportedMethodMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboTransportedMethodMetadata.java
new file mode 100644
index 00000000..e12c0201
--- /dev/null
+++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboTransportedMethodMetadata.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.alibaba.dubbo.metadata;
+
+import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
+
+import java.lang.reflect.Method;
+
+/**
+ * {@link MethodMetadata} annotated {@link DubboTransported @DubboTransported}
+ *
+ * @author Mercy
+ */
+public class DubboTransportedMethodMetadata extends MethodMetadata {
+
+ private String protocol;
+
+ private String cluster;
+
+ public DubboTransportedMethodMetadata(Method method) {
+ super(method);
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ public String getCluster() {
+ return cluster;
+ }
+
+ public void setCluster(String cluster) {
+ this.cluster = cluster;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DubboTransportedMethodMetadata)) return false;
+ if (!super.equals(o)) return false;
+
+ DubboTransportedMethodMetadata that = (DubboTransportedMethodMetadata) o;
+
+ if (protocol != null ? !protocol.equals(that.protocol) : that.protocol != null) return false;
+ return cluster != null ? cluster.equals(that.cluster) : that.cluster == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (protocol != null ? protocol.hashCode() : 0);
+ result = 31 * result + (cluster != null ? cluster.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/repository/DubboServiceMetadataRepository.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/repository/DubboServiceMetadataRepository.java
index 57a0a133..3bdafef6 100644
--- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/repository/DubboServiceMetadataRepository.java
+++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/repository/DubboServiceMetadataRepository.java
@@ -18,8 +18,8 @@ package org.springframework.cloud.alibaba.dubbo.metadata.repository;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.rpc.service.GenericService;
+
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
import org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata;
@@ -46,24 +46,18 @@ public class DubboServiceMetadataRepository {
/**
* Key is application name
- * Value is Map
+ * Value is Map>
*/
- private Map> genericServicesRepository = new HashMap<>();
+ private Map>> referenceBeansRepository = new HashMap<>();
private Map> methodMetadataRepository = new HashMap<>();
@Autowired
private MetadataConfigService metadataConfigService;
- @Value("${dubbo.target.protocol:dubbo}")
- private String targetProtocol;
-
- @Value("${dubbo.target.cluster:failover}")
- private String targetCluster;
-
public void updateMetadata(String serviceName) {
- Map genericServicesMap = genericServicesRepository.computeIfAbsent(serviceName, k -> new HashMap<>());
+ Map> genericServicesMap = referenceBeansRepository.computeIfAbsent(serviceName, k -> new HashMap<>());
Map methodMetadataMap = methodMetadataRepository.computeIfAbsent(serviceName, k -> new HashMap<>());
@@ -75,14 +69,14 @@ public class DubboServiceMetadataRepository {
serviceRestMetadata.getMeta().forEach(restMethodMetadata -> {
RequestMetadata requestMetadata = restMethodMetadata.getRequest();
- genericServicesMap.put(requestMetadata, referenceBean.get());
+ genericServicesMap.put(requestMetadata, referenceBean);
methodMetadataMap.put(requestMetadata, restMethodMetadata.getMethod());
});
}
}
- public GenericService getGenericService(String serviceName, RequestMetadata requestMetadata) {
- return getGenericServicesMap(serviceName).get(requestMetadata);
+ public ReferenceBean getReferenceBean(String serviceName, RequestMetadata requestMetadata) {
+ return getReferenceBeansMap(serviceName).get(requestMetadata);
}
public MethodMetadata getMethodMetadata(String serviceName, RequestMetadata requestMetadata) {
@@ -101,18 +95,15 @@ public class DubboServiceMetadataRepository {
referenceBean.setInterface(interfaceName);
referenceBean.setVersion(version);
referenceBean.setGroup(group);
- referenceBean.setProtocol(targetProtocol);
- referenceBean.setCluster(targetCluster);
return referenceBean;
}
- private Map getGenericServicesMap(String serviceName) {
- return genericServicesRepository.getOrDefault(serviceName, Collections.emptyMap());
+ private Map> getReferenceBeansMap(String serviceName) {
+ return referenceBeansRepository.getOrDefault(serviceName, Collections.emptyMap());
}
private Map getMethodMetadataMap(String serviceName) {
return methodMetadataRepository.getOrDefault(serviceName, Collections.emptyMap());
}
-
}
diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/FeignMetadataResolver.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboServiceBeanMetadataResolver.java
similarity index 95%
rename from spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/FeignMetadataResolver.java
rename to spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboServiceBeanMetadataResolver.java
index e7f80dd4..2b6f0404 100644
--- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/FeignMetadataResolver.java
+++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboServiceBeanMetadataResolver.java
@@ -18,6 +18,7 @@ package org.springframework.cloud.alibaba.dubbo.metadata.resolver;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.config.spring.ServiceBean;
+
import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
@@ -44,11 +45,12 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
- * The metadata resolver for {@link Feign}
+ * The metadata resolver for {@link Feign} for {@link ServiceBean Dubbo Service Bean} in the provider side.
*
* @author Mercy
*/
-public class FeignMetadataResolver implements BeanClassLoaderAware, SmartInitializingSingleton, MetadataResolver {
+public class DubboServiceBeanMetadataResolver implements BeanClassLoaderAware, SmartInitializingSingleton,
+ MetadataResolver {
private static final String[] CONTRACT_CLASS_NAMES = {
"feign.jaxrs2.JAXRS2Contract",
@@ -66,7 +68,7 @@ public class FeignMetadataResolver implements BeanClassLoaderAware, SmartInitial
*/
private Collection contracts;
- public FeignMetadataResolver(String currentApplicationName, ObjectProvider contract) {
+ public DubboServiceBeanMetadataResolver(String currentApplicationName, ObjectProvider contract) {
this.currentApplicationName = currentApplicationName;
this.contract = contract;
}
diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboTransportedMethodMetadataResolver.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboTransportedMethodMetadataResolver.java
new file mode 100644
index 00000000..1d0d4557
--- /dev/null
+++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboTransportedMethodMetadataResolver.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.alibaba.dubbo.metadata.resolver;
+
+import feign.Contract;
+import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
+import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMethodMetadata;
+import org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata;
+import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.env.PropertyResolver;
+
+import java.lang.reflect.Method;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static feign.Feign.configKey;
+
+/**
+ * {@link MethodMetadata} Resolver for the {@link DubboTransported} annotated classes or methods in client side.
+ *
+ * @author Mercy
+ * @see DubboTransportedMethodMetadata
+ */
+public class DubboTransportedMethodMetadataResolver {
+
+ private static final Class DUBBO_TRANSPORTED_CLASS = DubboTransported.class;
+
+ private final PropertyResolver propertyResolver;
+
+ private final Contract contract;
+
+ public DubboTransportedMethodMetadataResolver(PropertyResolver propertyResolver, Contract contract) {
+ this.propertyResolver = propertyResolver;
+ this.contract = contract;
+ }
+
+ public Map resolve(Class> targetType) {
+ Set dubboTransportedMethodMetadataSet =
+ resolveDubboTransportedMethodMetadataSet(targetType);
+ Map requestMetadataMap = resolveRequestMetadataMap(targetType);
+ return dubboTransportedMethodMetadataSet
+ .stream()
+ .collect(Collectors.toMap(methodMetadata -> methodMetadata, methodMetadata ->
+ requestMetadataMap.get(configKey(targetType, methodMetadata.getMethod()))
+ ));
+ }
+
+ protected Set resolveDubboTransportedMethodMetadataSet(Class> targetType) {
+ // The public methods of target interface
+ Method[] methods = targetType.getMethods();
+
+ Set 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 resolveRequestMetadataMap(Class> targetType) {
+ return contract.parseAndValidatateMetadata(targetType)
+ .stream().collect(Collectors.toMap(feign.MethodMetadata::configKey, this::requestMetadata));
+ }
+
+ private RequestMetadata requestMetadata(feign.MethodMetadata methodMetadata) {
+ return new RequestMetadata(methodMetadata.template());
+ }
+
+ private DubboTransportedMethodMetadata createDubboTransportedMethodMetadata(Method method,
+ DubboTransported dubboTransported) {
+ DubboTransportedMethodMetadata methodMetadata = new DubboTransportedMethodMetadata(method);
+ String protocol = propertyResolver.resolvePlaceholders(dubboTransported.protocol());
+ String cluster = propertyResolver.resolvePlaceholders(dubboTransported.cluster());
+ methodMetadata.setProtocol(protocol);
+ methodMetadata.setCluster(cluster);
+ return methodMetadata;
+ }
+
+ 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;
+ }
+}
diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboFeignClientsConfiguration.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboFeignClientsConfiguration.java
deleted file mode 100644
index 0c4d7400..00000000
--- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboFeignClientsConfiguration.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.cloud.alibaba.dubbo.openfeign;
-
-import feign.Contract;
-import feign.Feign;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration;
-import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.cloud.openfeign.FeignClientsConfiguration;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import java.util.List;
-
-/**
- * Dubbo {@link Configuration} for {@link FeignClient FeignClients}
- *
- * @author Mercy
- * @see DubboOpenFeignAutoConfiguration
- * @see org.springframework.cloud.openfeign.FeignContext#setConfigurations(List)
- * @see FeignClientsConfiguration
- */
-@Configuration
-public class DubboFeignClientsConfiguration {
-
- @Autowired
- private Contract contract;
-
- @Autowired
- private DubboServiceMetadataRepository dubboServiceRepository;
-
- @Bean
- public BeanPostProcessor beanPostProcessor() {
- return new BeanPostProcessor() {
- @Override
- public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- if (bean instanceof Feign.Builder) {
- Feign.Builder builder = (Feign.Builder) bean;
- builder.invocationHandlerFactory(new DubboInvocationHandlerFactory(contract, dubboServiceRepository));
- }
- return bean;
- }
- };
- }
-
-
-}
diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboInvocationHandlerFactory.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboInvocationHandlerFactory.java
index 4fd67f40..4df43ed2 100644
--- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboInvocationHandlerFactory.java
+++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboInvocationHandlerFactory.java
@@ -16,7 +16,9 @@
*/
package org.springframework.cloud.alibaba.dubbo.openfeign;
+import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.rpc.service.GenericService;
+
import feign.Contract;
import feign.InvocationHandlerFactory;
import feign.MethodMetadata;
@@ -68,10 +70,10 @@ public class DubboInvocationHandlerFactory implements InvocationHandlerFactory {
Map methodMetadataMap = new HashMap<>();
methodRequestMetadataMap.forEach((method, requestMetadata) -> {
- GenericService genericService = dubboServiceRepository.getGenericService(serviceName, requestMetadata);
+ ReferenceBean referenceBean = dubboServiceRepository.getReferenceBean(serviceName, requestMetadata);
org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata methodMetadata =
dubboServiceRepository.getMethodMetadata(serviceName, requestMetadata);
- genericServicesMap.put(method, genericService);
+ genericServicesMap.put(method, referenceBean.get());
methodMetadataMap.put(method, methodMetadata);
});
diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterBeanPostProcessor.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterBeanPostProcessor.java
new file mode 100644
index 00000000..ecd17c77
--- /dev/null
+++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterBeanPostProcessor.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.alibaba.dubbo.openfeign;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
+import org.springframework.core.env.Environment;
+
+import static java.lang.reflect.Proxy.newProxyInstance;
+import static org.springframework.util.ClassUtils.getUserClass;
+import static org.springframework.util.ClassUtils.resolveClassName;
+
+/**
+ * org.springframework.cloud.openfeign.Targeter {@link BeanPostProcessor}
+ *
+ * @author Mercy
+ */
+public class TargeterBeanPostProcessor implements BeanPostProcessor, BeanClassLoaderAware {
+
+ private static final String TARGETER_CLASS_NAME = "org.springframework.cloud.openfeign.Targeter";
+
+ private final Environment environment;
+
+ private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
+
+ private ClassLoader classLoader;
+
+ public TargeterBeanPostProcessor(Environment environment,
+ DubboServiceMetadataRepository dubboServiceMetadataRepository) {
+ this.environment = environment;
+ this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
+ 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, dubboServiceMetadataRepository));
+ }
+ return bean;
+ }
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterInvocationHandler.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterInvocationHandler.java
new file mode 100644
index 00000000..22de894c
--- /dev/null
+++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterInvocationHandler.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.alibaba.dubbo.openfeign;
+
+
+import com.alibaba.dubbo.config.spring.ReferenceBean;
+import com.alibaba.dubbo.rpc.service.GenericService;
+
+import feign.Contract;
+import feign.Target;
+import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMethodMetadata;
+import org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata;
+import org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository;
+import org.springframework.cloud.alibaba.dubbo.metadata.resolver.DubboTransportedMethodMetadataResolver;
+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}
+ *
+ * @author Mercy
+ */
+class TargeterInvocationHandler implements InvocationHandler {
+
+ private final Object bean;
+
+ private final Environment environment;
+
+ private final DubboServiceMetadataRepository dubboServiceMetadataRepository;
+
+ TargeterInvocationHandler(Object bean, Environment environment,
+ DubboServiceMetadataRepository dubboServiceMetadataRepository) {
+ this.bean = bean;
+ this.environment = environment;
+ this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
+ }
+
+ @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 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 methodRequestMetadataMap = resolver.resolve(targetType);
+
+ if (methodRequestMetadataMap.isEmpty()) { // @DubboTransported method was not found
+ return null;
+ }
+
+ // Update Metadata
+ dubboServiceMetadataRepository.updateMetadata(serviceName);
+
+ Map methodMetadataMap = new HashMap<>();
+
+ Map genericServicesMap = new HashMap<>();
+
+ methodRequestMetadataMap.forEach((dubboTransportedMethodMetadata, requestMetadata) -> {
+ ReferenceBean referenceBean = dubboServiceMetadataRepository.getReferenceBean(serviceName, requestMetadata);
+ org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata methodMetadata =
+ dubboServiceMetadataRepository.getMethodMetadata(serviceName, requestMetadata);
+ referenceBean.setProtocol(dubboTransportedMethodMetadata.getProtocol());
+ referenceBean.setCluster(dubboTransportedMethodMetadata.getCluster());
+ genericServicesMap.put(dubboTransportedMethodMetadata.getMethod(), referenceBean.get());
+ methodMetadataMap.put(dubboTransportedMethodMetadata.getMethod(), methodMetadata);
+ });
+
+ InvocationHandler defaultFeignClientInvocationHandler = Proxy.getInvocationHandler(defaultFeignClientProxy);
+
+ DubboInvocationHandler dubboInvocationHandler = new DubboInvocationHandler(genericServicesMap, methodMetadataMap,
+ defaultFeignClientInvocationHandler);
+
+ return dubboInvocationHandler;
+ }
+
+ private static T cast(Object object) {
+ return (T) object;
+ }
+}
diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java
index d7b3d13d..8cf9be03 100644
--- a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java
+++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/bootstrap/DubboSpringCloudBootstrap.java
@@ -17,10 +17,12 @@
package org.springframework.cloud.alibaba.dubbo.bootstrap;
import com.alibaba.dubbo.config.annotation.Reference;
+
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
import org.springframework.cloud.alibaba.dubbo.service.EchoService;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@@ -47,16 +49,39 @@ public class DubboSpringCloudBootstrap {
@Lazy
private FeignEchoService feignEchoService;
- @GetMapping(value = "/call/echo")
- public String echo(@RequestParam("message") String message) {
+ @Autowired
+ @Lazy
+ private DubboFeignEchoService dubboFeignEchoService;
+
+ @GetMapping(value = "/dubbo/call/echo")
+ public String dubboEcho(@RequestParam("message") String message) {
+ return echoService.echo(message);
+ }
+
+ @GetMapping(value = "/feign/call/echo")
+ public String feignEcho(@RequestParam("message") String message) {
return feignEchoService.echo(message);
}
+ @GetMapping(value = "/feign-dubbo/call/echo")
+ public String feignDubboEcho(@RequestParam("message") String message) {
+ return dubboFeignEchoService.echo(message);
+ }
+
@FeignClient("spring-cloud-alibaba-dubbo")
public interface FeignEchoService {
@GetMapping(value = "/echo")
String echo(@RequestParam("message") String message);
+
+ }
+
+ @FeignClient("spring-cloud-alibaba-dubbo")
+ public interface DubboFeignEchoService {
+
+ @GetMapping(value = "/echo")
+ @DubboTransported
+ String echo(@RequestParam("message") String message);
}
@Bean
@@ -66,6 +91,8 @@ public class DubboSpringCloudBootstrap {
System.out.println(echoService.echo("mercyblitz"));
// Spring Cloud Open Feign REST Call
System.out.println(feignEchoService.echo("mercyblitz"));
+ // Spring Cloud Open Feign REST Call (Dubbo Transported)
+ System.out.println(dubboFeignEchoService.echo("mercyblitz"));
};
}
diff --git a/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboTransportedMethodMetadataResolverTest.java b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboTransportedMethodMetadataResolverTest.java
new file mode 100644
index 00000000..6e263a06
--- /dev/null
+++ b/spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboTransportedMethodMetadataResolverTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.alibaba.dubbo.metadata.resolver;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.cloud.alibaba.dubbo.annotation.DubboTransported;
+import org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMethodMetadata;
+import org.springframework.cloud.openfeign.support.SpringMvcContract;
+import org.springframework.mock.env.MockEnvironment;
+
+import java.util.Set;
+
+/**
+ * {@link DubboTransportedMethodMetadataResolver} Test
+ *
+ * @author Mercy
+ */
+public class DubboTransportedMethodMetadataResolverTest {
+
+ private DubboTransportedMethodMetadataResolver resolver;
+
+ private MockEnvironment environment;
+
+ @Before
+ public void init() {
+ environment = new MockEnvironment();
+ resolver = new DubboTransportedMethodMetadataResolver(environment, new SpringMvcContract());
+ }
+
+ @Test
+ public void testResolve() {
+ Set metadataSet = resolver.resolveDubboTransportedMethodMetadataSet(TestDefaultService.class);
+ Assert.assertEquals(1, metadataSet.size());
+ }
+
+
+ @DubboTransported
+ interface TestDefaultService {
+
+ String test(String message);
+
+ }
+}
diff --git a/spring-cloud-alibaba-dubbo/src/test/resources/application.yaml b/spring-cloud-alibaba-dubbo/src/test/resources/application.yaml
index afdfbfa7..e3867ace 100644
--- a/spring-cloud-alibaba-dubbo/src/test/resources/application.yaml
+++ b/spring-cloud-alibaba-dubbo/src/test/resources/application.yaml
@@ -12,5 +12,9 @@ dubbo:
registry:
address: spring-cloud://nacos
+feign:
+ hystrix:
+ enabled: true
+
server:
port: 8080
\ No newline at end of file