From 0a4cd85255bfb81878eff354ad320b534b0da216 Mon Sep 17 00:00:00 2001 From: mercyblitz Date: Wed, 13 Feb 2019 15:21:14 +0800 Subject: [PATCH] Polish spring-cloud-incubator/spring-cloud-alibaba#348 : [Feature] Add @DubboTransported Annotation --- .../dubbo/annotation/DubboTransported.java | 67 +++++++++ .../DubboOpenFeignAutoConfiguration.java | 17 ++- .../DubboTransportedMethodMetadata.java | 73 ++++++++++ .../DubboServiceMetadataRepository.java | 27 ++-- ... => DubboServiceBeanMetadataResolver.java} | 8 +- ...ubboTransportedMethodMetadataResolver.java | 109 ++++++++++++++ .../DubboFeignClientsConfiguration.java | 65 --------- .../DubboInvocationHandlerFactory.java | 6 +- .../openfeign/TargeterBeanPostProcessor.java | 70 +++++++++ .../openfeign/TargeterInvocationHandler.java | 137 ++++++++++++++++++ .../bootstrap/DubboSpringCloudBootstrap.java | 31 +++- ...TransportedMethodMetadataResolverTest.java | 59 ++++++++ .../src/test/resources/application.yaml | 4 + 13 files changed, 578 insertions(+), 95 deletions(-) create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/annotation/DubboTransported.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboTransportedMethodMetadata.java rename spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/{FeignMetadataResolver.java => DubboServiceBeanMetadataResolver.java} (95%) create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboTransportedMethodMetadataResolver.java delete mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/DubboFeignClientsConfiguration.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterBeanPostProcessor.java create mode 100644 spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/openfeign/TargeterInvocationHandler.java create mode 100644 spring-cloud-alibaba-dubbo/src/test/java/org/springframework/cloud/alibaba/dubbo/metadata/resolver/DubboTransportedMethodMetadataResolverTest.java 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: + *
    + *
  1. {@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. + *
    + *
  2. + *
  3. {@link LoadBalanced @LoadBalanced} {@link RestTemplate} annotated field, method and parameters
  4. + *
+ *

+ * + * @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