Use Dubbo Proxy instead of Spring Proxy (#12439)
* Use Dubbo Proxy instead of Spring Proxy * Fix exception
This commit is contained in:
parent
1b5ff0fc9b
commit
f60ac914f4
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() + "]");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue