资源描述
Dubbo服务调用动态选择版本
问题说明
Dubbo是阿里巴巴SOA服务化治理方案的核心框架,致力于高性能和透明化的远程服务调用方案和服务治理方案。官方文档见:http://alibaba.github.io/dubbo-doc-static/Home-zh.htm。Dubbo可以和Spring无缝集成,示例如下:
1. 服务提供端
Provider.xml
<context:component-scan base-package="com.telecom.**" />
<dubbo:application name="wuchangzheng-demo-app" />
<dubbo:registry address="zookeeper://132.228.12.40:2181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:service interface="com.telecom.dubbo.intf.IHelloService" ref="helloService" version="1.0"/>
HelloServiceImpl.java
package com.telecom.dubbo.provider;
import org.springframework.stereotype.Service;
import com.telecom.dubbo.intf.IHelloService;
@Service("helloService")
public class HelloServiceImpl implements IHelloService{
@Override
public String sayHello(String name) {
return "Hello "+name;
}
}
2. 客户端调用
Consumer.xml
<context:annotation-config />
<context:component-scan base-package="com.telecom.**" />
<dubbo:application name="wuchangzheng-demo-app-consumer" />
<dubbo:registry address="zookeeper://132.228.12.40:2181" />
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="remoteHelloService" interface="com.telecom.dubbo.intf.IHelloService" check="false" version="1.0"/>
服务端 dubbo:service 和 客户端 dubbo:reference 分别有1个version属性。说明:服务端发布provider时可以为服务分配1个版本号,客户端调用服务端的provider时,需要指定调用哪1个版本的服务。项目实施过程碰到一些问题:
1. version属性在xml文件中配置,意味着服务端升级版本号,客户端也要相应修改配置文件并重启应用。
2. 项目中存在灰度发布的要求,服务端的provider可能同时存在多个版本,客户端根据用户工号来选择调用哪个版本,比如指定一批测试工号,来测试新版本的provider。整个发布过程需要不重启web应用实现无缝切换,客户端在xml文件中指定版本号就显得不够,dubbo需要支持客户端自行实现版本选择规则,根据规则决定调用相应版本的provider。
本文描述了如何重载dubbo的部分代码实现客户端版本规则。
实现思路
通过分析dubbo 2.5.3 的源码,<dubbo:reference/> 标签对应的类是 com.alibaba.dubbo.config.spring.ReferenceBean
<dubbo:reference/>的version属性被写到ReferenceBean的version属性中,可以通过ReferenceBean.getVersion() 来获取配置文件中指定的远程服务的版本号。
该类是工厂类,实现 org.springframework.beans.factory.FactoryBean,第64行代码
public Object getObject() throws Exception {
return get();
}
返回IHelloService的代理对象,调用该对象的sayHello方法即可调用远程dubbo服务,远程服务的版本号由ReferenceBean的version属性决定。
本文的实现方法是重载 ReferenceBean 的get() 方法,使用javassist技术构造一个新的代理,根据用户自定义的规则来调用相应版本的dubbo服务。
实现方法
重写ReferenceBean
package com.telecom.dubbo.extend;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.context.ApplicationContext;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.annotation.Reference;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
/**
* 自定义的referencebean,该类会创建动态代理,运行时根据上下文的版本号选择远程服务
* @author wuchangzheng
*
* @param <T>
*/
public class ReferenceBean<T> extends com.alibaba.dubbo.config.spring.ReferenceBean<T> implements Cloneable{
private static final long serialVersionUID = 8383612147446385736L;
private transient T proxy;//代理
private transient IVersionDesider versionDesider;//版本决定器
private transient ApplicationContext applicationContext;
private transient ReferenceBean self_=this;
private transient ConcurrentHashMap<String,ReferenceConfig> versionMap = new ConcurrentHashMap<String,ReferenceConfig>();
private transient final String DEFAULT_VERSION="DEFAULT_VERSION";
@SuppressWarnings({ "all"})
public void setApplicationContext(ApplicationContext applicationContext){
super.setApplicationContext(applicationContext);
this.applicationContext=applicationContext;
}
@SuppressWarnings({ "all"})
public void afterPropertiesSet() throws Exception{
super.afterPropertiesSet();
try{
versionDesider=applicationContext.getBean(IVersionDesider.class);//从上下文中查找IVersionDesider实现,该实现代表用户自定义版本规则
}catch(Exception ex){
versionDesider = null;
}
proxy=getVersionDecideProxy();//创建代理对象
}
/**
*
* @return
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
*/
@SuppressWarnings({ "all"})
private T getVersionDecideProxy() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
Class[] interfaces={Class.forName(this.getInterface())};
proxyFactory.setInterfaces(interfaces);
// 创建代理类型的Class
Class<ProxyObject> proxyClass = proxyFactory.createClass();
T proxy = (T) proxyClass.newInstance();
((ProxyObject) proxy).setHandler(new MethodHandler() {
@Override
public T invoke(Object self, Method thisMethod,
Method proceed, Object[] args) throws Throwable {
T target = null;
String version=DEFAULT_VERSION;
if (versionDesider!=null){
version=versionDesider.desideVersion(self_);
}
if (version==null) version=DEFAULT_VERSION;
ReferenceConfig config =null;
synchronized(self_){
config=versionMap.get(version);
if (config==null){
config=(ReferenceConfig)self_.clone();
config.setVersion(version);
config.setCheck(false);//
versionMap.put(version, config);
}
}
target = (T)config.get();
T retObj = (T)thisMethod.invoke(target, args);
return retObj;
}
});
return proxy;
}
public Object getObject() throws Exception {
return proxy;
}
}
重载dubbo命名空间解析器
/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed 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 com.telecom.dubbo.extend;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import mon.Version;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.ModuleConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.AnnotationBean;
import com.telecom.dubbo.extend.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
/**
* DubboNamespaceHandler
*
* @author william.liangf
* @export
*/
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
}
/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed 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 com.telecom.dubbo.extend;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import mon.Constants;
import mon.extension.ExtensionLoader;
import mon.logger.Logger;
import mon.logger.LoggerFactory;
import mon.utils.ReflectUtils;
import mon.utils.StringUtils;
import com.alibaba.dubbo.config.ArgumentConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.MethodConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.telecom.dubbo.extend.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
import com.alibaba.dubbo.rpc.Protocol;
/**
* AbstractBeanDefinitionParser
*
* @author william.liangf
* @export
*/
public class DubboBeanDefinitionParser implements BeanDefinitionParser {
private static final Logger logger = LoggerFactory.getLogger(DubboBeanDefinitionParser.class);
private final Class<?> beanClass;
private final boolean required;
public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
this.beanClass = beanClass;
this.required = required;
}
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass, required);
}
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String id = element.getAttribute("id");
if ((id == null || id.length() == 0) && required) {
String generatedBeanName = element.getAttribute("name");
if (generatedBeanName == null || generatedBeanName.length() == 0) {
if (ProtocolConfig.class.equals(beanClass)) {
generatedBeanName = "dubbo";
} else {
generatedBeanName = element.getAttribute("interface");
}
}
if (generatedBeanName == null || generatedBeanName.length() == 0) {
generatedBeanName = beanClass.getName();
}
id = generatedBeanName;
int counter = 2;
while(parserContext.getRegistry().containsBeanDefinition(id)) {
id = generatedBeanName + (counter ++);
}
}
if (id != null && id.length() > 0) {
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
if (ProtocolConfig.class.equals(beanClass)) {
for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
if (property != null) {
Object value = property.getValue();
if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
}
}
}
} else if (ServiceBean.class.equals(beanClass)) {
String className = element.getAttribute("class");
if(className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
parseProperties(element.getChildNodes(), classDefinition);
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
} else if (ProviderConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
} else if (ConsumerConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}
Set<String> props = new HashSet<String>();
ManagedMap parameters = null;
for (Method setter : beanClass.getMethods()) {
String name = setter.getName();
if (name.length() > 3 && name.startsWith("set")
&& Modifier.isPublic(setter.getModifiers())
&& setter.getParameterTypes().length == 1) {
Class<?> type = setter.getParameterT
展开阅读全文