上篇Dubbo优雅服务降级之mock描述了关于mock的细节。此篇就详述一下关于Stub的实现。
在dubbo的官方文档中写道
Mock是Stub的一个子集,便于服务提供方在客户端执行容错逻辑,因经常需要在出现RpcException(比如网络失败,超时等)时进行容错,而在出现业务异常(比如登录用户名密码错误)时不需要容错,如果用Stub,可能就需要捕获并依赖RpcException类,而用Mock就可以不依赖RpcException,因为它的约定就是只有出现RpcException时才执行。
Mock通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过Mock数据返回授权失败。
从上述中的表述可知,stub的功能更为全面,可以说可以操作的范围更加广阔。
首先我们来看一下在Dubbo中核心生成代理类的方法。
com.alibaba.dubbo.rpc.ProxyFactory
我们找到熟悉的spi接口文件
stub=com.alibaba.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper
jdk=com.alibaba.dubbo.rpc.proxy.jdk.JdkProxyFactory
javassist=com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory
可以看到熟悉的jdk代理和javassist代理(我们常用的包括cglib,jdk代理,javassit代理)
除此之外就是StubProxyFactoryWrapper
如前几篇说描述,wrapper一定会出现在extensionLoader获取扩展点的包装上。
因此可以认为此wrapper将会包装所有从proxy扩展点获取到的代理对象上。
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> T getProxy(Invoker<T> invoker) throws RpcException {
T proxy = proxyFactory.getProxy(invoker);
if (GenericService.class != invoker.getInterface()) {
String stub = invoker.getUrl().getParameter(Constants.STUB_KEY, invoker.getUrl().getParameter(Constants.LOCAL_KEY));
if (ConfigUtils.isNotEmpty(stub)) {
Class<?> serviceType = invoker.getInterface();
if (ConfigUtils.isDefault(stub)) {
if (invoker.getUrl().hasParameter(Constants.STUB_KEY)) {
stub = serviceType.getName() + "Stub";
} else {
stub = serviceType.getName() + "Local";
}
}
try {
Class<?> stubClass = ReflectUtils.forName(stub);
if (! serviceType.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + serviceType.getName());
}
try {
Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType);
proxy = (T) constructor.newInstance(new Object[] {proxy});
//export stub service
URL url = invoker.getUrl();
if (url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT)){
url = url.addParameter(Constants.STUB_EVENT_METHODS_KEY, StringUtils.join(Wrapper.getWrapper(proxy.getClass()).getDeclaredMethodNames(), ","));
url = url.addParameter(Constants.IS_SERVER_KEY, Boolean.FALSE.toString());
try{
export(proxy, (Class)invoker.getInterface(), url);
}catch (Exception e) {
LOGGER.error("export a stub service error.", e);
}
}
} catch (NoSuchMethodException e) {
throw new IllegalStateException("No such constructor \"public " + stubClass.getSimpleName() + "(" + serviceType.getName() + ")\" in stub implemention class " + stubClass.getName(), e);
}
} catch (Throwable t) {
LOGGER.error("Failed to create stub implemention class " + stub + " in consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", cause: " + t.getMessage(), t);
// ignore
}
}
}
return proxy;
}
从代码中可知,绝大部分和mock的情况一致,通过获取指定的Stub对象(默认以Local结尾)。
我们在回顾一下ReferenceBean引用到远程服务的过程中的最后一步
// 创建服务代理
return (T) proxyFactory.getProxy(invoker);
由此我们可以认为创建的对象是由Stubwrapper包装过的。
那么此时Stub就拥有了比Mock更早开始或者更晚收尾的时机(基本可以认为是在Spring Aop针对invoke的环绕增强)。
这样服务端开发者也可以定义好Stub和接口jar一起下发给对应的服务使用者(比如参数校验等等,决定是否调用真实的服务端等等)
谈到了代理的创建,顺便也要说一下关于EchoService的创建。
我们在拿到Dubbo服务时,可以将任意服务强制转换为EchoService。
- 服务消费方,通过将服务强制转型为EchoService,并调用$echo()测试该服务的提供者是可用
如 assertEqauls(“OK”, ((EchoService)memberService).$echo(“OK”));
我们知道了能够强转说明了该服务必然是实现了此接口。那么自然可以猜测是在动态代理时做的手脚。
观察到JavassistProxyFactory和JdkProxyFactory均继承了
public abstract class AbstractProxyFactory implements ProxyFactory {
public <T> T getProxy(Invoker<T> invoker) throws RpcException {
Class<?>[] interfaces = null;
String config = invoker.getUrl().getParameter("interfaces");
if (config != null && config.length() > 0) {
String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
if (types != null && types.length > 0) {
interfaces = new Class<?>[types.length + 2];
interfaces[0] = invoker.getInterface();
interfaces[1] = EchoService.class;
for (int i = 0; i < types.length; i ++) {
interfaces[i + 1] = ReflectUtils.forName(types[i]);
}
}
}
if (interfaces == null) {
interfaces = new Class<?>[] {invoker.getInterface(), EchoService.class};
}
return getProxy(invoker, interfaces);
}
public abstract <T> T getProxy(Invoker<T> invoker, Class<?>[] types);
}
可以明显看出该接口会在动态代理时生成,那么具体的实现在哪里呢?
这个需要回到FIlter的机制上来了解dubbo源码系列之filter的今世
在对应的Filter中存在
@Activate(group = Constants.PROVIDER, order = -110000)
public class EchoFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
if(inv.getMethodName().equals(Constants.$ECHO) && inv.getArguments() != null && inv.getArguments().length == 1 )
return new RpcResult(inv.getArguments()[0]);
return invoker.invoke(inv);
}
}
该方法返回了inv的默认第一个参数。该FIlter作用在服务端,并且排序相对较小,可以认为基本上该过滤器的顺序靠后,尽量避免误拦截指定的方法(方法名称需要规范,框架采用$echo )
因此在客户端可以任意调用某个服务端的$echo方法完成校验。
网友评论