Use Dubbo Proxy instead of Spring Proxy (#12439)

* Use Dubbo Proxy instead of Spring Proxy

* Fix exception
This commit is contained in:
Albumen Kevin 2023-06-01 18:46:29 +08:00 committed by GitHub
parent 1b5ff0fc9b
commit f60ac914f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 252 additions and 28 deletions

View File

@ -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();
}
}
}

View File

@ -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() + "]");
}
}

View File

@ -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;
}

View File

@ -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 {
}
}
}
}