Dubbo之Stub(本地存根)
dubbo版本
- dubbo版本2.6.7
本地存根Stub
-
本地存根:在客户端执行部分逻辑。如果向下客户端执行部分逻辑,比如ThreadLocal缓存、提前校验参数、调用失败伪装容错数据等。此时就需要在API中带上Stub,客户端生成proxy实例,会把Proxy通过构造函数传递给Stub,然后把Stub暴露给用户,由Stub决定是否进行远程调用
-
配置
<dubbo:service interface="com.foo.BarService" stub="true" /> 或者 <dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub" /> 在consumer配置(stub本身就在consumer,所以consumer端配置比较合适) <dubbo:service interface="com.foo.BarService" stub="true" /> <dubbo:reference interface="com.foo.BarService" stub="com.foo.BarServiceStub" /> 因为local已经过时,现在使用stub代替local <dubbo:service interface="com.foo.BarService" local="true" /> <dubbo:reference interface="com.foo.BarService" local="com.foo.BarServiceLocal" />
-
编写存根实现规范:
- Stub和Local 必须有可传入 Proxy 的构造函数和实现业务接口。
- 如果配置local=true,实现类的命名规范必须是接口名+Stub或者接口名+Local,而且要和接口在一个包下
@Slf4j public class DemoServiceLocal implements DemoService { private final DemoService demoService; /** *构造函数传入真正的远程代理对象 */ public DemoServiceLocal(DemoService demoService) { this.demoService = demoService; } @Override public String sayHello(String name) { //可以前后实现AOP增强或者容错 try { log.info("开始远程调用"); String result = demoService.sayHello(name); log.info("结束远程调用,返回信息:{}", result); return result; } catch (Exception e) { log.error("容错数据"); } return "容错数据"; } @Override public SayResponseDTO sayHello(SayRequestDTO sayRequestDTO) { return null; } }
源码分析
-
检查配置
AbstractInterfaceConfig#checkStub
void checkStub(Class<?> interfaceClass) { //如果存在local=xxx配置,如果是local=true,则比如按照【接口名称+Local】的命名方式。如果是直接指定类名,则没有要求 if (ConfigUtils.isNotEmpty(local)) { Class<?> localClass = ConfigUtils.isDefault(local) ? ReflectUtils.forName(interfaceClass.getName() + "Local") : ReflectUtils.forName(local); //local实现类必须实现业务接口 if (!interfaceClass.isAssignableFrom(localClass)) { throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceClass.getName()); } //local实现类必须有一个接口构造函数 try { ReflectUtils.findConstructor(localClass, interfaceClass); } catch (NoSuchMethodException e) { throw new IllegalStateException("No such constructor \"public " + localClass.getSimpleName() + "(" + interfaceClass.getName() + ")\" in local implementation class " + localClass.getName()); } } //与上面local逻辑一样 if (ConfigUtils.isNotEmpty(stub)) { Class<?> localClass = ConfigUtils.isDefault(stub) ? ReflectUtils.forName(interfaceClass.getName() + "Stub") : ReflectUtils.forName(stub); if (!interfaceClass.isAssignableFrom(localClass)) { throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceClass.getName()); } try { ReflectUtils.findConstructor(localClass, interfaceClass); } catch (NoSuchMethodException e) { throw new IllegalStateException("No such constructor \"public " + localClass.getSimpleName() + "(" + interfaceClass.getName() + ")\" in local implementation class " + localClass.getName()); } } }
-
导出时检查
ServiceConfig#doExport
//local属性已被弃用,由stub属性替代 if (local != null) { if ("true".equals(local)) { local = interfaceName + "Local"; } Class<?> localClass; try { localClass = ClassHelper.forNameWithThreadContextClassLoader(local); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } if (!interfaceClass.isAssignableFrom(localClass)) { throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName); } } // stub设为true,表示使用缺省代理类名,即:接口名 +Stub后缀, // 服务接口客户端本地代理类名,用于在客户端执行本地逻辑,如本地缓存等, // 该本地代理类的构造函数必须允许传入远程代理对象, // 构造函数如:public XxxServiceStub(XxxService xxxService) if (stub != null) { if ("true".equals(stub)) { stub = interfaceName + "Stub"; } Class<?> stubClass; try { stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } if (!interfaceClass.isAssignableFrom(stubClass)) { throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName); } }
-
StubProxyFactoryWrapper
是对ProxyFactory进行增强处理@Override @SuppressWarnings({"unchecked", "rawtypes"}) public <T> T getProxy(Invoker<T> invoker) throws RpcException { T proxy = proxyFactory.getProxy(invoker); //非泛化调用 if (GenericService.class != invoker.getInterface()) { //local已经废弃,这里是为了兼容 String stub = invoker.getUrl().getParameter(Constants.STUB_KEY, invoker.getUrl().getParameter(Constants.LOCAL_KEY)); if (ConfigUtils.isNotEmpty(stub)) { Class<?> serviceType = invoker.getInterface(); //如果配置stub=true,而非直接配置Stub实现类名 if (ConfigUtils.isDefault(stub)) { //XXXServiceStub或者XXXServiceLocal 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 implementation class " + stubClass.getName() + " not implement interface " + serviceType.getName()); } try { //将代理对象传入Stub类的构造函数 Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType); //替换proxy成为新的proxy 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 = 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 implementation class " + stubClass.getName(), e); } } catch (Throwable t) { LOGGER.error("Failed to create stub implementation class " + stub + " in consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", cause: " + t.getMessage(), t); // ignore } } } return proxy; }
本地Mock
-
本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
-
Mock 是 Stub 的一个子集,便于服务提供方在客户端执行容错逻辑,因经常需要在出现 RpcException (比如网络失败,超时等)时进行容错,而在出现业务异常(比如登录用户名密码错误)时不需要容错,如果用 Stub,可能就需要捕获并依赖 RpcException 类,而用 Mock 就可以不依赖 RpcException,因为它的约定就是只有出现 RpcException 时才执行
-
配置
<dubbo:reference interface="com.foo.BarService" mock="true" /> <dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock" />
-
Mock实现:
mock=true
方式,命名规范必须是【接口名+Mock】- 必须实现业务接口,并且有一个无参数的构造函数
public class BarServiceMock implements BarService { public String sayHello(String name) { // 你可以伪造容错数据,此方法只在出现RpcException时被执行 return "容错数据"; } } 以下异常处理可以用mock代替 Offer offer = null; try { offer = offerService.findOffer(offerId); } catch (RpcException e) { logger.error(e); } 或者直接配置 <dubbo:reference interface="com.foo.BarService" mock="return null" />
-
mock语法
1. 使用 return 来返回一个字符串表示的对象,作为 Mock 的返回值。合法的字符串可以是: empty: 代表空,基本类型的默认值,或者集合类的空值 null: null true: true false: false JSON 格式: 反序列化 JSON 所得到的对象 2. 使用 throw 来返回一个 Exception 对象,作为 Mock 的返回值。 2.1 当调用出错时,抛出一个默认的 RPCException <dubbo:reference interface="com.foo.BarService" mock="throw" /> 2.2 当调用出错时,抛出指定的 Exception: <dubbo:reference interface="com.foo.BarService" mock="throw com.foo.MockException" /> 3.在 2.6.6 以上的版本,可以开始在 Spring XML 配置文件中使用 fail: 和 force:。 3.1 force: 代表强制使用 Mock 行为,在这种情况下不会走远程调用。 <dubbo:reference interface="com.foo.BarService" mock="force:return fake" /> <dubbo:reference interface="com.foo.BarService" mock="force:com.foo.BarServiceMock" /> 3.2 fail: 与默认行为一致,只有当远程调用发生错误时才使用 Mock 行为。force: 和 fail: 都支持与 throw 或者 return 组合使用 <dubbo:reference interface="com.foo.BarService" mock="force:throw com.foo.MockException" />
-
方法级别配置Mock
<dubbo:reference id="demoService" check="false" interface="com.foo.BarService"> 为sayHello()方法指定mock <dubbo:parameter key="sayHello.mock" value="force:return fake"/> </dubbo:reference>
源码分析
-
检查配置
AbstractInterfaceConfig#checkMock
void checkMock(Class<?> interfaceClass) { if (ConfigUtils.isEmpty(mock)) { return; } //标准化mock配置字符串 String normalizedMock = MockInvoker.normalizeMock(mock); //如果是以return开头 if (normalizedMock.startsWith(Constants.RETURN_PREFIX)) { normalizedMock = normalizedMock.substring(Constants.RETURN_PREFIX.length()).trim(); try { //解析return 字符串 MockInvoker.parseMockValue(normalizedMock); } catch (Exception e) { throw new IllegalStateException("Illegal mock return in <dubbo:service/reference ... " + "mock=\"" + mock + "\" />"); } } else if (normalizedMock.startsWith(Constants.THROW_PREFIX)) { normalizedMock = normalizedMock.substring(Constants.THROW_PREFIX.length()).trim(); if (ConfigUtils.isNotEmpty(normalizedMock)) { try { //解析throw 字符串 MockInvoker.getThrowable(normalizedMock); } catch (Exception e) { throw new IllegalStateException("Illegal mock throw in <dubbo:service/reference ... " + "mock=\"" + mock + "\" />"); } } } else { MockInvoker.getMockObject(normalizedMock, interfaceClass); } }
-
获取Mock对象
MockInvoker#getMockObject
public static Object getMockObject(String mockService, Class serviceType) { if (ConfigUtils.isDefault(mockService)) { mockService = serviceType.getName() + "Mock"; } Class<?> mockClass = ReflectUtils.forName(mockService); if (!serviceType.isAssignableFrom(mockClass)) { throw new IllegalStateException("The mock class " + mockClass.getName() + " not implement interface " + serviceType.getName()); } try { return mockClass.newInstance(); } catch (InstantiationException e) { throw new IllegalStateException("No default constructor from mock class " + mockClass.getName(), e); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } }