需求缘由
最近部分只读服务已经切换到了Dubbo提供的服务化接口;经过一段时间的生产验证, 各种监控指标显示非常稳定,所以打算开始切换写服务接口; 但大家对写操作跑服务化没有信心,于是提了一个需求能随时将远程rpc调用切换打本地jar方法调用;
dubbo的stub
听到这个需求,马上想到dubbo的stub属性;但需要在原来代码中做部分改造,具体步骤是:
- 原来的jar包实现方法中增加带自身接口类型参数的构造函数;
- 在所有的接口实现方法中判断开关状态决定调用RPC/本地方法;
但是,这样对原代码有极强入侵性;我们认为这不是最好的方案,于是有了下面的方案;
扩展dubbo:reference标签属性
首先需要说明,我们在实际使用dubbo的过程中并没有直接使用dubbo这个命名空间,而是自定义了自己的命名空间,自定义命名空间兼容了dubbo命名空间的所有属性,而且扩展了自己的属性;于是针对该需求我们有了新的方案,具体流程如下 :
- 增加reference的属性localbean,这个属性值为一个本地bean的id;类似的xml配置如下:
1 2 3
| <bean id="localDemoService" class="com.example.Service.impl.DemoServiceImpl" /> <mydubbo:reference interface="com.example.Service.DemoService" id="remoteDemoService" localbean ="localDemoService" version="1.0"/>
|
- 继承ReferenceBean实现自定义属性locbean;
- 继承DubboNamespaceHandler重写init方法,如下:
1 2 3 4
| public void init() { super.init(); this.registerBeanDefinitionParser("reference", new MyBeanDefinitionParser(LocalReferenceBean.class, false)); }
|
- 自定义filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class RPCLocalSwitchFilter<T> implements Filter { private static final Logger logger = LoggerFactory.getLogger(RPCLocalSwitchFilter.class); private static final ProxyFactory PROXY_FACTORY = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
public RPCLocalSwitchFilter() { }
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { if(cfg.getBooleanProperty("")) { Object bean = this.getLocalBean(invoker); return bean != null?this.executeLocalMethod(bean, invoker, invocation):invoker.invoke(invocation); } else { return invoker.invoke(invocation); } }
private Result executeLocalMethod(T bean, Invoker<?> invoker, Invocation invocation) { if(logger.isDebugEnabled()) { logger.debug("execute local method instead of rpc " + bean.getClass().getCanonicalName() + "." + invocation.getMethodName()); }
Invoker localInvoker = PROXY_FACTORY.getInvoker(bean, invoker.getInterface(), invoker.getUrl()); return localInvoker.invoke(invocation); }
private <T> T getLocalBean(Invoker<?> invoker) { String refLocal = invoker.getUrl().getParameter("localBean"); if(refLocal != null) { try { ApplicationContext e = LocalReferenceBean.getSpringContext(); return e.getBean(refLocal, invoker.getInterface()); } catch (BeansException e) { logger.error(var4.getMessage() + " couldn\'t find the bean[" + localBean + "], please check", e); } }
return null; } }
|
- 配置filter
在resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter文件中增加如下内容:1
| reflocal=com.example.filter. RPCLocalSwitchFilter
|
配置consumer的filter属性1
| <mydubbo:consumer filter="reflocal" />
|
至此可以通过配置中的开关状态来控制远程RPC/本地jar方法调用 ;