Dubbo配置参数详解-stub
Dubbo是一个由阿里开源的服务治理框架,笔者的公司重度使用Dubbo。Dubbo开源这么多年,配置项已经非常丰富,了解各个配置项的作用也变得非常重要,本系列将从源代码的角度分析Dubbo目前的最新版本(2.7.4)各个常用的配置项的具体含义以及是怎么起作用的。
画外音:目前Dubbo在开源中国举办的2019年度最受欢迎中国开源软件中排名第3名,支持Dubbo的朋友可以去投票哇。2019年度最受欢迎中国开源软件
stub是啥?
stub:存根,rmi需要存根(stub)和骨架(skeleton),stub用于客户端,skeleton用于服务器端。简单来说,你调用provider,相关的服务方不是要提供给你一个jar包吗,那个jar包就是存根。
画外音:使用过WebService的同学应该很清楚
stub有啥用?
一般的存根都是只有接口,不包含实现。但是dubbo提供的这个stub作用有点类似于拦截器,它可以在调用正在的provider之前或之后对请求及结果进行处理。
stub怎么使用?
存根主要有两种使用方式,一种是服务提供者写好,并随jar包一起提供给调用方,这种方式可以用来做前置验证,另外一种方式是调用方自己写,这可以用来对结果进行处理;
如果是服务方提供,Stub一般都是定义在接口的同包目录下,并且使用InterfaceName+Stub的命名方式,这时调用方只要设置stub="true"就可以使用了。
/**
* @description:
* @author: chengang6
* @create: 2019/5/10 10:38
**/
public interface HelloDubboService {
String say();
String hello();
String post(String key);
}
/**
* @description: Stub必须实现相同的接口,且跟接口在同一个目录
* @author: chengang6
* @create: 2020/1/7 10:37
**/
public class HelloDubboServiceStub implements HelloDubboService {
private HelloDubboService helloDubboService;
//必须提供这种类似代理的构造函数,否则会报错
public HelloDubboServiceStub(HelloDubboService helloDubboService) {
this.helloDubboService = helloDubboService;
}
@Override
public String say() {
return ">>>>>>>>Stub>>>>say";
}
@Override
public String hello() {
// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
try {
return helloDubboService.hello();
} catch (Exception e) {
// 你可以容错,可以做任何AOP拦截事项
return "容错数据";
}
}
@Override
public String post(String key) {
return ">>>>>>>>Stub>>>>post";
}
}
//只要配置stub=true就可以使用存根了
@Reference(stub = "true")
private HelloDubboService helloDubboService;
也可以在@Service中配置stub="true",这样就算Consumer端么有配置stub,也会强制Consumer端执行Stub
@Service(stub = "true")
public class HelloDubboServiceImpl implements HelloDubboService {
Consumer端也可以自定义Stub,此时不需要遵循InterfaceName+Stub的命名方式,但需要把完整的类名写在参数中,比如
@Reference(stub = "com.example.dubboconsumer.stub.MyStub")
private HelloDubboService helloDubboService;
stub的命名规则是什么样的?
在Reference初始化的时候会判断是否设置了stub,如果设置了该参数会在AbstractInterfaceConfig类的checkStubAndLocal方法中判断该参数的有效性
/**
* Legitimacy check of stub, note that: the local will deprecated, and replace with <code>stub</code>
*
* @param interfaceClass for provider side, it is the {@link Class} of the service that will be exported; for consumer
* side, it is the {@link Class} of the remote service interface
*/
void checkStubAndLocal(Class<?> interfaceClass) {
if (ConfigUtils.isNotEmpty(local)) {
Class<?> localClass = ConfigUtils.isDefault(local) ?
ReflectUtils.forName(interfaceClass.getName() + "Local") : ReflectUtils.forName(local);
verify(interfaceClass, localClass);
}
if (ConfigUtils.isNotEmpty(stub)) {
Class<?> localClass = ConfigUtils.isDefault(stub) ?
ReflectUtils.forName(interfaceClass.getName() + "Stub") : ReflectUtils.forName(stub);
verify(interfaceClass, localClass);
}
}
private void verify(Class<?> interfaceClass, Class<?> localClass) {
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() +
" not implement interface " + interfaceClass.getName());
}
try {
//Check if the localClass a constructor with parameter who's type is interfaceClass
ReflectUtils.findConstructor(localClass, interfaceClass);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("No such constructor \"public " + localClass.getSimpleName() +
"(" + interfaceClass.getName() + ")\" in local implementation class " + localClass.getName());
}
}
可以看到Dubbo除了提供Stub,还可以使用Local这种方式,但是这种方式Dubbo已经不推荐使用;
如果stub="true",Dubbo将会在相同接口的包下寻找InterfaceName+Stub这个类,否则将会根据全限定名寻找;并且Stub中必须有一个将接口作为形参的构造函数,否则报错;
stub是如何生效的?
Dubbo在为reference生成代理类的时候,都会调用StubProxyFactoryWrapper判断该reference是否需要使用stub,如果是则直接返回stub
public class StubProxyFactoryWrapper implements ProxyFactory {
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> T getProxy(Invoker<T> invoker) throws RpcException {
T proxy = proxyFactory.getProxy(invoker);
if (GenericService.class != invoker.getInterface()) {
URL url = invoker.getUrl();
String stub = url.getParameter(STUB_KEY, url.getParameter(LOCAL_KEY));
if (ConfigUtils.isNotEmpty(stub)) {
Class<?> serviceType = invoker.getInterface();
if (ConfigUtils.isDefault(stub)) {
if (url.hasParameter(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 {
Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType);
proxy = (T) constructor.newInstance(new Object[]{proxy});
//export stub service
URLBuilder urlBuilder = URLBuilder.from(url);
if (url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT)) {
urlBuilder.addParameter(STUB_EVENT_METHODS_KEY, StringUtils.join(Wrapper.getWrapper(proxy.getClass()).getDeclaredMethodNames(), ","));
urlBuilder.addParameter(IS_SERVER_KEY, Boolean.FALSE.toString());
try {
export(proxy, (Class) invoker.getInterface(), urlBuilder.build());
} 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;
}
}
问题
如果stub是在服务端配置,而没有在consumer端配置,并且consumer启动在前,这时StubProxyFactoryWrapper将会判断reference不需要stub,当provider启动后,就算@Service配置了stub="true",consumer也不会调用stub。
总结:
- stub一般是服务提供方提供,并随jar包一起提供给consumer;
- 可以设置@Service(stub="true"),这样就能强制consumer端使用stub;
- stub是在consumer端执行的,可以做前置验证,这样可以过滤非法请求,提高性能;
网友评论