Dubbo的Reference注解必须先启动provider的问题
目录
现象
看源码分析原因
注解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注解一共分三步。
注解Reference第一步:用Reference注解里的参数初始化ReferenceConfig
当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<RegistryConfig> 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依然是:
<dubbo:reference singleton="true" generic="false" />
里面没有check参数。
这个规则可能是为了以后在生成Consumer代理的时候少循环几次,但是会引发一些问题。
此时是调用栈大概是这样的:
appendAnnotation:101, AbstractConfig (com.alibaba.dubbo.config)
<init>:122, ReferenceConfig (com.alibaba.dubbo.config)
<init>: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
注解Reference第二步:从配置文件里获取参数,写入ReferenceConfig
把注解的参数写入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的节点。
注解Reference第三步:生成Consumer代理
参数配置完之后,回到init()方法的最后一行,
this.ref = this.createProxy(map);
map就是之前装配的所有参数的集合,createProxy方法的作用就是生成代理,代码如下:
private T createProxy(Map<String, String> 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<URL> 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 <dubbo:registry address=\"...\" /> to your spring config.");}}if (this.urls.size() == 1) {this.invoker = refprotocol.refer(this.interfaceClass, (URL)this.urls.get(0));} else {List<Invoker<?>> 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。
Dubbo的Reference注解必须先启动provider的问题相关推荐
- dubbo的@Reference注解作用分析
目的 看看dubbo是怎么给加了@Reference注解的属性注入invoker实例,为什么有时候加了@Reference注解的属性会是null. ReferenceAnnotationBeanPos ...
- Dubbo学习记录(八) -- Spring整合Dubbo中@Reference注解解析原理
Spring整合Dubbo中@Reference注解解析原理 @Reference: 可以用在属性或者方法, 意味着需要引用某个Dubbo服务, 那么Dubbo整合Spring后, 我很好奇怎么把这个 ...
- Spring-Boot 整合Dubbo 解决@Reference 注解为null情况
首先检查一下你的spring boot版本是多少? 如果是2.X 不用看了,spring boot 2.x 必定会出现这个问题, 改为 1.5.9 或其他1.x版本,目前生产环境建议使用1.x版本. ...
- Dubbo之@Reference 和 ReferenceBean
consumer调用dubbo服务 两种方法: 1.构建一个ReferenceBean,然后使用Spring的@Autowired引用服务 @Bean public ReferenceBean< ...
- 由浅入深分布式(5)dubbo提供者用内网地址注册provider以及 spring boot admin client用主机名注册spring boot admin server
之前遇到过dubbo提供者用内网地址注册provider的问题 当时改了host文件成功了 但是没有想为什么会有这个问题 现在使用spring boot admin 来监控spring boot项目出 ...
- Dubbo(十一)dubbo的超时重试配置以及启动检查
一.dubbo超时重试配置 dubbo重试次数配置使用的是retries 属性.可以配置于服务提供者.消费者的接口方法.接口类.以及全局配置范围上.与超时配置优先级一样遵循就近原则,方法上的配置优先于 ...
- @Reference注解说明
@Reference是dubbo的注解也是注入,注入的是分布式中的远程服务对象. 源码: package org.apache.dubbo.config.annotation;import java. ...
- Dubbo的@Reference和@Service说明
前言 @Reference 用在消费端,表明使用的是服务端的什么服务 1 @RestController2 public class RemoteUserController {3 4 5 6 @Re ...
- Dubbo扩展点注解之@Activate
@Activate称为自动激活扩展点注解,主要使用在有多个扩展点实现.需要同时根据不同条件被激活的场景中,如Filter需要多个同时激活,因为每个Filter实现的是不同的功能. @Activate的 ...
最新文章
- iOS安全之RSA加密/生成公钥、秘钥 pem文件
- Mybatis的解析模块基础
- 关于阿里面试的的一个小题
- PHP内核探索:Zend引擎
- EEPROM存储器--AT24CXX
- 阿波罗数据集怎么下载_从2D images 到3D估计:现有最大规模数据集 ApolloCar3D
- tar.gz 文件类型(tar文件的解压和压缩)
- Android使用 SO 库时要注意的一些问题
- 【Elasticsearch】Elasticsearch 索引 索引模板 生命周期 关系
- 查看CDH平台各个组件的版本
- 浅谈.NET中泛型的基本原理
- Windows10:将cmd命令行添加到右键中的方法
- JAVA练习题(正则表达式)
- 容量治理-扩容、限流和降级
- 正交试验法生成测试用例工具PICT
- 面试阿里,看这一篇就够了!
- java中替换的快捷键是什么_idea替换快捷键,批量处理对象的操作
- 按键精灵的5级开发认证,笔试题参考
- HTML5期末大作业:动漫A网站设计——动画漫展学习资料电影模板(6页) 网页设计作业 _ 动漫网页设计作业,网页设计作业 _ 动漫网页设计成品,网页设计作业 _ 动漫网页设计成品模板下载
- ElasticKibana 安装部署