目录
现象
看源码分析原因
注解Reference第一步:用Reference注解里的参数初始化ReferenceConfig
注解Reference第二步:从配置文件里获取参数,写入ReferenceConfig
注解Reference第三步:生成Consumer代理
解决方案
如果只想知道怎么解决,请翻到文章最后一句。
当使用Dubbo的Reference注解时,必须先启动provider,再启动consumer,否则被Reference注解的service就是null,且后来启动了provider之后,service也依然是null,不能使用。
被Reference注解的service是null,说明Dubbo的代理没有生成。
在Reference注解中配置check=false,没有用。
在springboot的配置文件application.properties中各种花式配置check=false,没有用。
Spring处理Reference注解一共分三步。
当Spring处理Controller注解时,使用Annotation类作为Controller的处理类,Spring会调用处理类的postProcessBeforeInitialization方法,当发现Controller类里有Reference注解的service时,会调用Annotation的refer方法来初始化被Reference注解的service,代码如下:
private Object refer(Reference reference, Class> referenceClass) {
String interfaceName;
if (!"".equals(reference.interfaceName())) {
interfaceName = reference.interfaceName();
} else if (!Void.TYPE.equals(reference.interfaceClass())) {
interfaceName = reference.interfaceClass().getName();
} else {
if (!referenceClass.isInterface()) {
throw new IllegalStateException("The @Reference undefined interfaceClass or interfaceName, and the property type " + referenceClass.getName() + " is not a interface.");
}
interfaceName = referenceClass.getName();
}
String key = reference.group() + "/" + interfaceName + ":" + reference.version();
ReferenceBean> referenceConfig = (ReferenceBean)this.referenceConfigs.get(key);
if (referenceConfig == null) {
referenceConfig = new ReferenceBean(reference);
if (Void.TYPE.equals(reference.interfaceClass()) && "".equals(reference.interfaceName()) && referenceClass.isInterface()) {
referenceConfig.setInterface(referenceClass);
}
if (this.applicationContext != null) {
referenceConfig.setApplicationContext(this.applicationContext);
if (reference.registry() != null && reference.registry().length > 0) {
List registryConfigs = new ArrayList();
String[] arr$ = reference.registry();
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
String registryId = arr$[i$];
if (registryId != null && registryId.length() > 0) {
registryConfigs.add((RegistryConfig)this.applicationContext.getBean(registryId, RegistryConfig.class));
}
}
referenceConfig.setRegistries(registryConfigs);
}
if (reference.consumer() != null && reference.consumer().length() > 0) {
referenceConfig.setConsumer((ConsumerConfig)this.applicationContext.getBean(reference.consumer(), ConsumerConfig.class));
}
if (reference.monitor() != null && reference.monitor().length() > 0) {
referenceConfig.setMonitor((MonitorConfig)this.applicationContext.getBean(reference.monitor(), MonitorConfig.class));
}
if (reference.application() != null && reference.application().length() > 0) {
referenceConfig.setApplication((ApplicationConfig)this.applicationContext.getBean(reference.application(), ApplicationConfig.class));
}
if (reference.module() != null && reference.module().length() > 0) {
referenceConfig.setModule((ModuleConfig)this.applicationContext.getBean(reference.module(), ModuleConfig.class));
}
if (reference.consumer() != null && reference.consumer().length() > 0) {
referenceConfig.setConsumer((ConsumerConfig)this.applicationContext.getBean(reference.consumer(), ConsumerConfig.class));
}
try {
referenceConfig.afterPropertiesSet();
} catch (RuntimeException var11) {
throw var11;
} catch (Exception var12) {
throw new IllegalStateException(var12.getMessage(), var12);
}
}
this.referenceConfigs.putIfAbsent(key, referenceConfig);
referenceConfig = (ReferenceBean)this.referenceConfigs.get(key);
}
return referenceConfig.get();
}
其中
referenceConfig = new ReferenceBean(reference);
这行表示用Reference注解生成ReferenceConfig代理,注解中配置的参数都会赋值给ReferenceConfig,实际上该方法调用的是ReferenceConfig的父类AbstractConfig类的
appendAnnotation(Class> annotationClass, Object annotation)
方法,其中的annotationClass参数就是com.alibaba.dubbo.config.annotation.Reference的class,annotation参数就是本次初始化的Reference对象的代理。
appendAnnotation方法的代码如下:
protected void appendAnnotation(Class> annotationClass, Object annotation) {
Method[] methods = annotationClass.getMethods();
Method[] arr$ = methods;
int len$ = methods.length;
for(int i$ = 0; i$ < len$; ++i$) {
Method method = arr$[i$];
if (method.getDeclaringClass() != Object.class && method.getReturnType() != Void.TYPE && method.getParameterTypes().length == 0 && Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers())) {
try {
String property = method.getName();
if ("interfaceClass".equals(property) || "interfaceName".equals(property)) {
property = "interface";
}
String setter = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
Object value = method.invoke(annotation);
if (value != null && !value.equals(method.getDefaultValue())) {
Class> parameterType = ReflectUtils.getBoxedClass(method.getReturnType());
if (!"filter".equals(property) && !"listener".equals(property)) {
if ("parameters".equals(property)) {
parameterType = Map.class;
value = CollectionUtils.toStringMap((String[])((String[])value));
}
} else {
parameterType = String.class;
value = StringUtils.join((String[])((String[])value), ",");
}
try {
Method setterMethod = this.getClass().getMethod(setter, parameterType);
setterMethod.invoke(this, value);
} catch (NoSuchMethodException var13) {
;
}
}
} catch (Throwable var14) {
logger.error(var14.getMessage(), var14);
}
}
}
}
注意其中的
if (value != null && !value.equals(method.getDefaultValue())) {
这一行,意思是如果Reference注解的参数等于默认值,则不会把设置的参数值写入ReferenceConfg。
也就是说,如果我这样使用Reference注解:
@Reference(check = false)
TestService testService;
那么check参数不会被写入ReferenceConfig,生成的ReferenceConfig依然是:
里面没有check参数。
这个规则可能是为了以后在生成Consumer代理的时候少循环几次,但是会引发一些问题。
此时是调用栈大概是这样的:
appendAnnotation:101, AbstractConfig (com.alibaba.dubbo.config)
:122, ReferenceConfig (com.alibaba.dubbo.config)
:56, ReferenceBean (com.alibaba.dubbo.config.spring) refer:259, AnnotationBean (com.alibaba.dubbo.config.spring)
postProcessBeforeInitialization:233, AnnotationBean (com.alibaba.dubbo.config.spring)
applyBeanPostProcessorsBeforeInitialization:422, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
initializeBean:1694, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:579, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:501, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:317, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 530696881 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$97)
getSingleton:228, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:315, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:760, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:869, AbstractApplicationContext (org.springframework.context.support)
refresh:550, AbstractApplicationContext (org.springframework.context.support)
refresh:140, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:759, SpringApplication (org.springframework.boot)
refreshContext:395, SpringApplication (org.springframework.boot)
run:327, SpringApplication (org.springframework.boot)
run:1255, SpringApplication (org.springframework.boot)
run:1243, SpringApplication (org.springframework.boot)
main:11,TestApplication
把注解的参数写入ReferenceConfig参数后,在refer方法最后一行,
return referenceConfig.get();
正式开始初始化ReferenceConfig。
get方法代码:
public synchronized T get() {
if (this.destroyed) {
throw new IllegalStateException("Already destroyed!");
} else {
if (this.ref == null) {
this.init();
}
return this.ref;
}
}
init()方法很长,配置了很多ReferenceConfig的参数,其中单独调用了一下appendProperties方法,这个方法负责把配置文件中的属性写入ReferenceConfig,代码如下:
protected static void appendProperties(AbstractConfig config) {
if (config != null) {
String prefix = "dubbo." + getTagName(config.getClass()) + ".";
Method[] methods = config.getClass().getMethods();
Method[] arr$ = methods;
int len$ = methods.length;
for(int i$ = 0; i$ < len$; ++i$) {
Method method = arr$[i$];
try {
String name = method.getName();
if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0])) {
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");
String value = null;
String pn;
if (config.getId() != null && config.getId().length() > 0) {
pn = prefix + config.getId() + "." + property;
value = System.getProperty(pn); //注释一
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
if (value == null || value.length() == 0) {
pn = prefix + property;
value = System.getProperty(pn); //注释二
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
if (value == null || value.length() == 0) {
Method getter;
try {
getter = config.getClass().getMethod("get" + name.substring(3));
} catch (NoSuchMethodException var14) {
try {
getter = config.getClass().getMethod("is" + name.substring(3));
} catch (NoSuchMethodException var13) {
getter = null;
}
}
if (getter != null && getter.invoke(config) == null) {
if (config.getId() != null && config.getId().length() > 0) {
value = ConfigUtils.getProperty(prefix + config.getId() + "." + property); //注释三
}
if (value == null || value.length() == 0) {
value = ConfigUtils.getProperty(prefix + property); //注释四
}
if (value == null || value.length() == 0) {
String legacyKey = (String)legacyProperties.get(prefix + property);
if (legacyKey != null && legacyKey.length() > 0) {
value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey)); //注释五
}
}
}
}
if (value != null && value.length() > 0) {
method.invoke(config, convertPrimitive(method.getParameterTypes()[0], value));
}
}
} catch (Exception var15) {
logger.error(var15.getMessage(), var15);
}
}
}
}
方法中设置了几个参数,假设我要把check参数赋值给Reference,那这几个参数如下:
prefix:dubbo.reference
property:check
config.getId():注解的service带路径的全名,我测试时用的service是test.TestService
根据这个方法的代码流程,赋值check有以下几次机会:
1,注释一
pn = prefix + config.getId() + "." + property;
value = System.getProperty(pn); //注释一
此时的pn的值是dubbo.reference. test.TestService.check,只要系统参数里有这个配置,比如
-Ddubbo.reference. test.TestService.check=false
就可以向ReferenceConfig赋值。
2,注释二
pn = prefix + property;
value = System.getProperty(pn); //注释二
此时的pn的值是dubbo.reference.check,只要系统参数里有这个配置,比如
-Ddubbo.reference.check=false
就可以向ReferenceConfig赋值。
可见在此处的配置中,单个service配置的优先级高于全局配置的优先级。
3,注释三
value = ConfigUtils.getProperty(prefix + config.getId() + "." + property); //注释三
用ConfigUtils获取参数,key值为:
dubbo.reference. test.TestService.check
ConfigUtils获取参数的流程是:
①,先检查系统参数也就是System.getProperty(pn),同注释一,此场景中显然没有值,要不也不会来到注释三了。
②,查找配置文件,配置文件的路径是用以下方法定义的:
public static Properties getProperties() {
if (PROPERTIES == null) {
Class var0 = ConfigUtils.class;
synchronized(ConfigUtils.class) {
if (PROPERTIES == null) {
String path = System.getProperty("dubbo.properties.file");
if (path == null || path.length() == 0) {
path = System.getenv("dubbo.properties.file");
if (path == null || path.length() == 0) {
path = "dubbo.properties";
}
}
PROPERTIES = loadProperties(path, false, true);
}
}
}
return PROPERTIES;
}
也就是按如下顺序查找配置文件是否存在
System.getProperty("dubbo.properties.file");
System.getenv("dubbo.properties.file");
dubbo.properties
前两个是查看是否存在dubbo.properties.file配置,最后是查看是否存在dubbo.properties文件。
③,从配置文件中获取dubbo.reference. test.TestService.check参数。
4,注释四
value = ConfigUtils.getProperty(prefix + property); //注释四
ConfigUtils中获取dubbo.reference.check参数。
可见此处的配置中,单个service配置的优先级高于全局配置的优先级。
5,注释五
String legacyKey = (String)legacyProperties.get(prefix + property);
if (legacyKey != null && legacyKey.length() > 0) {
value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey)); //注释五
}
legacyProperties是AbstractConfig中定义的一个map,而且在该类中用static代码块初始化:
static {
legacyProperties.put("dubbo.protocol.name", "dubbo.service.protocol");
legacyProperties.put("dubbo.protocol.host", "dubbo.service.server.host");
legacyProperties.put("dubbo.protocol.port", "dubbo.service.server.port");
legacyProperties.put("dubbo.protocol.threads", "dubbo.service.max.thread.pool.size");
legacyProperties.put("dubbo.consumer.timeout", "dubbo.service.invoke.timeout");
legacyProperties.put("dubbo.consumer.retries", "dubbo.service.max.retry.providers");
legacyProperties.put("dubbo.consumer.check", "dubbo.service.allow.no.provider");
legacyProperties.put("dubbo.service.url", "dubbo.service.address");
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
if (AbstractConfig.logger.isInfoEnabled()) {
AbstractConfig.logger.info("Run shutdown hook now.");
}
ProtocolConfig.destroyAll();
}
}, "DubboShutdownHook"));
SUFFIXS = new String[]{"Config", "Bean"};
}
legacyProperties是用来设置一些默认值的,处理Reference注解的时候用不上,但是很多默认值的时候会用到,比如设置Consumer的check属性默认值时,此时的prefix + property是dubbo.consumer.check,所以可以在ConfigUtils可用的配置文件中,配置dubbo.service.allow.no.provider参数,作为所有Consumer的默认check属性。
另外,在这里使用legacyProperties时只能令dubbo.service.allow.no.provider和dubbo.service.max.retry.providers两个参数生效,因为注释五的convertLegacyValue方法是这么写的:
private static String convertLegacyValue(String key, String value) {
if (value != null && value.length() > 0) {
if ("dubbo.service.max.retry.providers".equals(key)) {
return String.valueOf(Integer.parseInt(value) - 1);
}
if ("dubbo.service.allow.no.provider".equals(key)) {
return String.valueOf(!Boolean.parseBoolean(value));
}
}
return value;
}
可见,只用了两个参数,不明白这么设计的初衷是什么,可能别的参数在其他的方法和代码逻辑中会用到。
以上五处注释,就是Dubbo读取配置文件并且装配ReferenceConfig的节点。
参数配置完之后,回到init()方法的最后一行,
this.ref = this.createProxy(map);
map就是之前装配的所有参数的集合,createProxy方法的作用就是生成代理,代码如下:
private T createProxy(Map map) {
URL tmpUrl = new URL("temp", "localhost", 0, map);
boolean isJvmRefer;
if (this.isInjvm() == null) {
if (this.url != null && this.url.length() > 0) {
isJvmRefer = false;
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
isJvmRefer = this.isInjvm();
}
if (isJvmRefer) {
URL url = (new URL("injvm", "127.0.0.1", 0, this.interfaceClass.getName())).addParameters(map);
this.invoker = refprotocol.refer(this.interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + this.interfaceClass.getName());
}
} else {
URL u;
URL url;
if (this.url != null && this.url.length() > 0) {
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(this.url);
if (us != null && us.length > 0) {
String[] arr$ = us;
int len$ = us.length;
for(int i$ = 0; i$ < len$; ++i$) {
String u = arr$[i$];
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
url = url.setPath(this.interfaceName);
}
if ("registry".equals(url.getProtocol())) {
this.urls.add(url.addParameterAndEncoded("refer", StringUtils.toQueryString(map)));
} else {
this.urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else {
List us = this.loadRegistries(false);
if (us != null && us.size() > 0) {
for(Iterator i$ = us.iterator(); i$.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) {
u = (URL)i$.next();
url = this.loadMonitor(u);
if (url != null) {
map.put("monitor", URL.encode(url.toFullString()));
}
}
}
if (this.urls == null || this.urls.size() == 0) {
throw new IllegalStateException("No such any registry to reference " + this.interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config to your spring config.");
}
}
if (this.urls.size() == 1) {
this.invoker = refprotocol.refer(this.interfaceClass, (URL)this.urls.get(0));
} else {
List> invokers = new ArrayList();
URL registryURL = null;
Iterator i$ = this.urls.iterator();
while(i$.hasNext()) {
url = (URL)i$.next();
invokers.add(refprotocol.refer(this.interfaceClass, url));
if ("registry".equals(url.getProtocol())) {
registryURL = url;
}
}
if (registryURL != null) {
u = registryURL.addParameter("cluster", "available");
this.invoker = cluster.join(new StaticDirectory(u, invokers));
} else {
this.invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
Boolean c = this.check;
if (c == null && this.consumer != null) {
c = this.consumer.isCheck();
}
if (c == null) {
c = true;
}
if (c && !this.invoker.isAvailable()) {
throw new IllegalStateException("Failed to check the status of the service " + this.interfaceName + ". No provider available for the service " + (this.group == null ? "" : this.group + "/") + this.interfaceName + (this.version == null ? "" : ":" + this.version) + " from the url " + this.invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
} else {
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + this.interfaceClass.getName() + " from url " + this.invoker.getUrl());
}
return proxyFactory.getProxy(this.invoker);
}
}
代码主要分两部分,前面大部分代码判断了要生成的consumer在本地有没有provider,如果有就直接使用本地的provider,否则就进行一系列装配,组装用于远端消费的consumer。如果远端的provider地址只有一个,那么这个consumer就直接指定地址并生成一个invoker,如果provider地址有多个,则生成invoker列表。invoker是用来生成代理的。
然后是第二部分,对provider是否存在的判断,也就是这一部分代码:
Boolean c = this.check;
if (c == null && this.consumer != null) {
c = this.consumer.isCheck();
}
if (c == null) {
c = true;
}
if (c && !this.invoker.isAvailable()) {
throw new IllegalStateException("Failed to check the status of the service " + this.interfaceName + ". No provider available for the service " + (this.group == null ? "" : this.group + "/") + this.interfaceName + (this.version == null ? "" : ":" + this.version) + " from the url " + this.invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
} else {
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + this.interfaceClass.getName() + " from url " + this.invoker.getUrl());
}
return proxyFactory.getProxy(this.invoker);
}
代码获取了之前配置的check属性,如果没有check属性,则默认check为true,需要参与后面的provider是否存在判断,如果没有provider,则抛异常,并且不会生成代理。
那么问题就来了,在之前处理Reference参数的时候,如果设置的参数和默认值相同,则不写入Reference,而且 Reference注解中的check默认值就是false,如前文所说,如果代码里写的是
@Reference(check = false)
那么这个check就不会被写到Reference里,如果配置文件里又没有对此进行配置,那么这里的check就会是null,然后在这里被设置为true,然后不得不参与provider是否是null的判断,此时如果没有启动任何provider,那么代理类就再也生成不了了,即使后面启动了provider也没有用。
汇总check参数相关代码逻辑,如下:
注解写法1:
@Reference(check = true)
生成代理的时候check为true。
注解写法2:
@Reference(check = false)
生成Reference时check被忽略,生成代理之前check被设置为true。
注解写法3:
@Reference()
成代理之前check被设置为true。
结果就是无论注解怎么写,最终的check都是true,都必须先起provider。
想要先起consumer,就必须保证consumer代理能生成。
要保证consumer代理能生成,就必须配置check=false。
要配置check=false,不能在Reference注解里配置,而是应该在配置文件中配置。
汇总Spring从配置中获取check参数的方式,有以下几种:
1,System.getProperty("dubbo.reference.{id}.check")
其中{id}是被注解的类的全名。
System.getProperty()是从系统变量中获取参数,来自java启动命令的vm options参数。
人工定义此参数的方法是在启动java的命令中加入:-Dcheck=false参数。
可以在IDEA中的Run-->Edit Configurations-->VM options,添加-Dcheck=false参数。
可以在Eclipse中的Run Configurations—>Arguments-->VM arguments,添加-Dcheck=false参数。
2,System.getProperty("dubbo.reference.check")
系统变量中,配置所有Reference注解通用的配置方式。
3,System.getProperty("dubbo.properties.file")
系统变量中,指定配置文件路径,然后在配置文件中配置dubbo.reference.{id}.check或者dubbo.reference.check。
4,System.getenv("dubbo.properties.file")
环境变量中,指定配置文件路径,然后在配置文件中配置dubbo.reference.{id}.check或者dubbo.reference.check。
System.getenv()得到的变量是操作系统级的。
5,dubbo.properies
在resources目录中添加dubbo.properies配置文件,在此文件中添加dubbo.reference.{id}.check或者dubbo.reference.check配置。
以上就是Reference注解必须先启动provider的解决方案。
我选择添加dubbo.properies文件,并且在里面写上,dubbo.reference.check=false。