You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2023/06/01 10:46:36 UTC

[dubbo] branch 3.2 updated: Use Dubbo Proxy instead of Spring Proxy (#12439)

This is an automated email from the ASF dual-hosted git repository.

albumenj pushed a commit to branch 3.2
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.2 by this push:
     new f60ac914f4 Use Dubbo Proxy instead of Spring Proxy (#12439)
f60ac914f4 is described below

commit f60ac914f463034df4a50e6f871ddfb146c3b541
Author: Albumen Kevin <jh...@gmail.com>
AuthorDate: Thu Jun 1 18:46:29 2023 +0800

    Use Dubbo Proxy instead of Spring Proxy (#12439)
    
    * Use Dubbo Proxy instead of Spring Proxy
    
    * Fix exception
---
 .../apache/dubbo/config/spring/ReferenceBean.java  |  80 +++++++++++----
 .../spring/util/LazyTargetInvocationHandler.java   |  69 +++++++++++++
 .../dubbo/config/spring/util/LazyTargetSource.java |  21 ++++
 .../javaconfig/JavaConfigReferenceBeanTest.java    | 110 +++++++++++++++++++--
 4 files changed, 252 insertions(+), 28 deletions(-)

diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ReferenceBean.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ReferenceBean.java
index c22a280f0b..975df8a2b2 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ReferenceBean.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/ReferenceBean.java
@@ -16,6 +16,10 @@
  */
 package org.apache.dubbo.config.spring;
 
+import org.apache.dubbo.common.bytecode.Proxy;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.Assert;
 import org.apache.dubbo.common.utils.ClassUtils;
 import org.apache.dubbo.common.utils.StringUtils;
@@ -25,11 +29,11 @@ import org.apache.dubbo.config.spring.reference.ReferenceAttributes;
 import org.apache.dubbo.config.spring.reference.ReferenceBeanManager;
 import org.apache.dubbo.config.spring.reference.ReferenceBeanSupport;
 import org.apache.dubbo.config.spring.schema.DubboBeanDefinitionParser;
+import org.apache.dubbo.config.spring.util.LazyTargetInvocationHandler;
+import org.apache.dubbo.config.spring.util.LazyTargetSource;
 import org.apache.dubbo.config.support.Parameter;
 import org.apache.dubbo.rpc.proxy.AbstractProxyFactory;
 
-import org.springframework.aop.framework.ProxyFactory;
-import org.springframework.aop.target.AbstractLazyCreationTargetSource;
 import org.springframework.beans.MutablePropertyValues;
 import org.springframework.beans.factory.BeanClassLoaderAware;
 import org.springframework.beans.factory.BeanNameAware;
@@ -44,10 +48,13 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROXY_FAILED;
+
 
 /**
  * <p>
@@ -99,7 +106,7 @@ import java.util.Map;
  */
 public class ReferenceBean<T> implements FactoryBean<T>,
         ApplicationContextAware, BeanClassLoaderAware, BeanNameAware, InitializingBean, DisposableBean {
-
+    private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
     private transient ApplicationContext applicationContext;
 
     private ClassLoader beanClassLoader;
@@ -124,6 +131,9 @@ public class ReferenceBean<T> implements FactoryBean<T>,
     // 'interfaceName' field for compatible with seata-1.4.0: io.seata.rm.tcc.remoting.parser.DubboRemotingParser#getServiceDesc()
     private String interfaceName;
 
+    // proxy style
+    private String proxy;
+
     //from annotation attributes
     private Map<String, Object> referenceProps;
 
@@ -235,6 +245,10 @@ public class ReferenceBean<T> implements FactoryBean<T>,
                 propertyValues = beanDefinition.getPropertyValues();
             }
         }
+
+        if (referenceProps != null) {
+            this.proxy = (String) referenceProps.get(ReferenceAttributes.PROXY);
+        }
         Assert.notNull(this.interfaceName, "The interface name of ReferenceBean is not initialized");
 
         ReferenceBeanManager referenceBeanManager = beanFactory.getBean(ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class);
@@ -319,24 +333,56 @@ public class ReferenceBean<T> implements FactoryBean<T>,
 
         //set proxy interfaces
         //see also: org.apache.dubbo.rpc.proxy.AbstractProxyFactory.getProxy(org.apache.dubbo.rpc.Invoker<T>, boolean)
-        ProxyFactory proxyFactory = new ProxyFactory();
-        proxyFactory.setTargetSource(new DubboReferenceLazyInitTargetSource());
-        proxyFactory.addInterface(interfaceClass);
+        List<Class<?>> interfaces = new ArrayList<>();
+        interfaces.add(interfaceClass);
         Class<?>[] internalInterfaces = AbstractProxyFactory.getInternalInterfaces();
-        for (Class<?> anInterface : internalInterfaces) {
-            proxyFactory.addInterface(anInterface);
-        }
+        Collections.addAll(interfaces, internalInterfaces);
         if (!StringUtils.isEquals(interfaceClass.getName(), interfaceName)) {
             //add service interface
             try {
                 Class<?> serviceInterface = ClassUtils.forName(interfaceName, beanClassLoader);
-                proxyFactory.addInterface(serviceInterface);
+                interfaces.add(serviceInterface);
             } catch (ClassNotFoundException e) {
                 // generic call maybe without service interface class locally
             }
         }
 
-        this.lazyProxy = proxyFactory.getProxy(this.beanClassLoader);
+        if (StringUtils.isEmpty(this.proxy) || CommonConstants.DEFAULT_PROXY.equalsIgnoreCase(this.proxy)) {
+            generateFromJavassistFirst(interfaces);
+        }
+
+        if (this.lazyProxy == null) {
+            generateFromJdk(interfaces);
+        }
+    }
+
+    private void generateFromJavassistFirst(List<Class<?>> interfaces) {
+        try {
+            this.lazyProxy = Proxy.getProxy(interfaces.toArray(new Class[0])).newInstance(new LazyTargetInvocationHandler(new DubboReferenceLazyInitTargetSource()));
+        } catch (Throwable fromJavassist) {
+            // try fall back to JDK proxy factory
+            try {
+                this.lazyProxy = java.lang.reflect.Proxy.newProxyInstance(beanClassLoader, interfaces.toArray(new Class[0]), new LazyTargetInvocationHandler(new DubboReferenceLazyInitTargetSource()));
+                logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by Javassist failed. Fallback to use JDK proxy success. " +
+                    "Interfaces: " + interfaces, fromJavassist);
+            } catch (Throwable fromJdk) {
+                logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +
+                    "Interfaces: " + interfaces + " Javassist Error.", fromJavassist);
+                logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +
+                    "Interfaces: " + interfaces + " JDK Error.", fromJdk);
+                throw fromJavassist;
+            }
+        }
+    }
+
+    private void generateFromJdk(List<Class<?>> interfaces) {
+        try {
+            this.lazyProxy = java.lang.reflect.Proxy.newProxyInstance(beanClassLoader, interfaces.toArray(new Class[0]), new LazyTargetInvocationHandler(new DubboReferenceLazyInitTargetSource()));
+        } catch (Throwable fromJdk) {
+            logger.error(PROXY_FAILED, "", "", "Failed to generate proxy by Javassist failed. Fallback to use JDK proxy is also failed. " +
+                "Interfaces: " + interfaces + " JDK Error.", fromJdk);
+            throw fromJdk;
+        }
     }
 
     private Object getCallProxy() throws Exception {
@@ -344,7 +390,7 @@ public class ReferenceBean<T> implements FactoryBean<T>,
             throw new IllegalStateException("ReferenceBean is not ready yet, please make sure to call reference interface method after dubbo is started.");
         }
         //get reference proxy
-        //Subclasses should synchronize on the given Object if they perform any sort of extended singleton creation phase. 
+        //Subclasses should synchronize on the given Object if they perform any sort of extended singleton creation phase.
         // In particular, subclasses should not have their own mutexes involved in singleton creation, to avoid the potential for deadlocks in lazy-init situations.
         //The redundant type cast is to be compatible with earlier than spring-4.2
         synchronized (((DefaultSingletonBeanRegistry)getBeanFactory()).getSingletonMutex()) {
@@ -352,17 +398,11 @@ public class ReferenceBean<T> implements FactoryBean<T>,
         }
     }
 
-    private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource {
-
+    private class DubboReferenceLazyInitTargetSource implements LazyTargetSource {
         @Override
-        protected Object createObject() throws Exception {
+        public Object getTarget() throws Exception {
             return getCallProxy();
         }
-
-        @Override
-        public synchronized Class<?> getTargetClass() {
-            return getInterfaceClass();
-        }
     }
 
 }
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/LazyTargetInvocationHandler.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/LazyTargetInvocationHandler.java
new file mode 100644
index 0000000000..cb414f93f0
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/LazyTargetInvocationHandler.java
@@ -0,0 +1,69 @@
+/*
+ * 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.apache.dubbo.config.spring.util;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class LazyTargetInvocationHandler implements InvocationHandler {
+    private final LazyTargetSource lazyTargetSource;
+    private volatile Object target;
+
+    public LazyTargetInvocationHandler(LazyTargetSource lazyTargetSource) {
+        this.lazyTargetSource = lazyTargetSource;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        String methodName = method.getName();
+        Class<?>[] parameterTypes = method.getParameterTypes();
+        if (parameterTypes.length == 0) {
+            if ("toString".equals(methodName)) {
+                if (target != null) {
+                    return target.toString();
+                } else {
+                    return this.toString();
+                }
+            } else if ("hashCode".equals(methodName)) {
+                return this.hashCode();
+            }
+        } else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
+            return this.equals(args[0]);
+        }
+
+        if (target == null) {
+            synchronized (this) {
+                if (target == null) {
+                    target = lazyTargetSource.getTarget();
+                }
+            }
+        }
+        if (method.getDeclaringClass().isInstance(target)) {
+            try {
+                return method.invoke(target, args);
+            } catch (InvocationTargetException exception) {
+                Throwable targetException = exception.getTargetException();
+                if (targetException != null) {
+                    throw targetException;
+                }
+            }
+        }
+        throw new IllegalStateException("The proxied interface [" + method.getDeclaringClass() +
+            "] contains a method [" + method + "] that is not implemented by the proxy class [" + target.getClass() + "]");
+    }
+}
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/LazyTargetSource.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/LazyTargetSource.java
new file mode 100644
index 0000000000..57f58e1ecd
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/util/LazyTargetSource.java
@@ -0,0 +1,21 @@
+/*
+ * 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.apache.dubbo.config.spring.util;
+
+public interface LazyTargetSource {
+    Object getTarget() throws Exception;
+}
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/reference/javaconfig/JavaConfigReferenceBeanTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/reference/javaconfig/JavaConfigReferenceBeanTest.java
index af379ca7fe..d951dd03df 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/reference/javaconfig/JavaConfigReferenceBeanTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/reference/javaconfig/JavaConfigReferenceBeanTest.java
@@ -41,6 +41,7 @@ import org.springframework.context.annotation.PropertySource;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -97,6 +98,63 @@ class JavaConfigReferenceBeanTest {
         }
     }
 
+    @Test
+    void testLazyProxy1() {
+        AnnotationConfigApplicationContext context = null;
+        try {
+            context = new AnnotationConfigApplicationContext(CommonConfig.class,
+                LazyProxyConfiguration1.class);
+            HelloService helloServiceClient = context.getBean("helloServiceClient", HelloService.class);
+            Assertions.assertNotNull(helloServiceClient);
+            Assertions.assertInstanceOf(HelloService.class, helloServiceClient);
+            Class<? extends HelloService> clientClass = helloServiceClient.getClass();
+            Assertions.assertFalse(Modifier.isFinal(clientClass.getModifiers()));
+            Assertions.assertEquals("Hello, dubbo", helloServiceClient.sayHello("dubbo"));
+        } finally {
+            if (context != null) {
+                context.close();
+            }
+        }
+    }
+
+    @Test
+    void testLazyProxy2() {
+        AnnotationConfigApplicationContext context = null;
+        try {
+            context = new AnnotationConfigApplicationContext(CommonConfig.class,
+                LazyProxyConfiguration2.class);
+            HelloService helloServiceClient = context.getBean("helloServiceClient", HelloService.class);
+            Assertions.assertNotNull(helloServiceClient);
+            Assertions.assertInstanceOf(HelloService.class, helloServiceClient);
+            Class<? extends HelloService> clientClass = helloServiceClient.getClass();
+            Assertions.assertFalse(Modifier.isFinal(clientClass.getModifiers()));
+            Assertions.assertEquals("Hello, dubbo", helloServiceClient.sayHello("dubbo"));
+        } finally {
+            if (context != null) {
+                context.close();
+            }
+        }
+    }
+
+    @Test
+    void testLazyProxy3() {
+        AnnotationConfigApplicationContext context = null;
+        try {
+            context = new AnnotationConfigApplicationContext(CommonConfig.class,
+                LazyProxyConfiguration3.class);
+            HelloService helloServiceClient = context.getBean("helloServiceClient", HelloService.class);
+            Assertions.assertNotNull(helloServiceClient);
+            Assertions.assertInstanceOf(HelloService.class, helloServiceClient);
+            Class<? extends HelloService> clientClass = helloServiceClient.getClass();
+            Assertions.assertTrue(Modifier.isFinal(clientClass.getModifiers()));
+            Assertions.assertEquals("Hello, dubbo", helloServiceClient.sayHello("dubbo"));
+        } finally {
+            if (context != null) {
+                context.close();
+            }
+        }
+    }
+
     private String getStackTrace(Throwable ex) {
         StringWriter stringWriter = new StringWriter();
         ex.printStackTrace(new PrintWriter(stringWriter));
@@ -106,7 +164,7 @@ class JavaConfigReferenceBeanTest {
     @Test
     void testAnnotationBean() {
         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CommonConfig.class,
-                AnnotationBeanConfiguration.class);
+            AnnotationBeanConfiguration.class);
 
         try {
             Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
@@ -165,7 +223,7 @@ class JavaConfigReferenceBeanTest {
     @Test
     void testReferenceBean() {
         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CommonConfig.class,
-                ReferenceBeanConfiguration.class);
+            ReferenceBeanConfiguration.class);
 
         try {
             Map<String, HelloService> helloServiceMap = context.getBeansOfType(HelloService.class);
@@ -316,6 +374,42 @@ class JavaConfigReferenceBeanTest {
     }
 
 
+    @Configuration
+    public static class LazyProxyConfiguration1 {
+
+        @DubboReference(group = "${myapp.group}")
+        private HelloService helloService;
+
+        @Bean(name = "helloServiceClient")
+        public HelloService helloService() {
+            return helloService;
+        }
+    }
+
+    @Configuration
+    public static class LazyProxyConfiguration2 {
+
+        @DubboReference(group = "${myapp.group}", proxy = "javassist")
+        private HelloService helloService;
+
+        @Bean(name = "helloServiceClient")
+        public HelloService helloService() {
+            return helloService;
+        }
+    }
+
+    @Configuration
+    public static class LazyProxyConfiguration3 {
+
+        @DubboReference(group = "${myapp.group}", proxy = "jdk")
+        private HelloService helloService;
+
+        @Bean(name = "helloServiceClient")
+        public HelloService helloService() {
+            return helloService;
+        }
+    }
+
     @Configuration
     public static class AnnotationAtFieldConfiguration {
 
@@ -365,8 +459,8 @@ class JavaConfigReferenceBeanTest {
         @Bean
         public ReferenceBean<HelloService> helloService() {
             return new ReferenceBeanBuilder()
-                    .setGroup("${myapp.group}")
-                    .build();
+                .setGroup("${myapp.group}")
+                .build();
         }
 
         @Bean
@@ -394,9 +488,9 @@ class JavaConfigReferenceBeanTest {
         @Bean
         public ReferenceBean helloService() {
             return new ReferenceBeanBuilder()
-                    .setGroup("${myapp.group}")
-                    .setInterface(HelloService.class)
-                    .build();
+                .setGroup("${myapp.group}")
+                .setInterface(HelloService.class)
+                .build();
         }
 
     }
@@ -435,4 +529,4 @@ class JavaConfigReferenceBeanTest {
         }
     }
 
-}
\ No newline at end of file
+}