extends AbstractReferenceConfig {
-
- private static final long serialVersionUID = -5864351140409987595L;
-
- /**
- * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios.
- * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}.
- * For example:
- *
- * when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample,
- * then the protocol is RegistryProtocol
- *
- * when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then
- * the protocol is DubboProtocol
- *
- * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two
- * layers, and eventually will get a ProtocolFilterWrapper or ProtocolListenerWrapper
- */
- private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
-
- /**
- * The {@link Cluster}'s implementation with adaptive functionality, and actually it will get a {@link Cluster}'s
- * specific implementation who is wrapped with MockClusterInvoker
- */
- private static final Cluster CLUSTER = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
-
- /**
- * A {@link ProxyFactory} implementation that will generate a reference service's proxy,the JavassistProxyFactory is
- * its default implementation
- */
- private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
-
- /**
- * The url of the reference service
- */
- private final List urls = new ArrayList();
-
- /**
- * The interface name of the reference service
- */
- private String interfaceName;
-
- /**
- * The interface class of the reference service
- */
- private Class> interfaceClass;
-
- /**
- * client type
- */
- private String client;
-
- /**
- * The url for peer-to-peer invocation
- */
- private String url;
-
- /**
- * The method configs
- */
- private List methods;
-
- /**
- * The consumer config (default)
- */
- private ConsumerConfig consumer;
-
- /**
- * Only the service provider of the specified protocol is invoked, and other protocols are ignored.
- */
- private String protocol;
-
- /**
- * The interface proxy reference
- */
- private transient volatile T ref;
-
- /**
- * The invoker of the reference service
- */
- private transient volatile Invoker> invoker;
-
- /**
- * The flag whether the ReferenceConfig has been initialized
- */
- private transient volatile boolean initialized;
-
- /**
- * whether this ReferenceConfig has been destroyed
- */
- private transient volatile boolean destroyed;
-
- @SuppressWarnings("unused")
- private final Object finalizerGuardian = new Object() {
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
-
- if (!ReferenceConfig.this.destroyed) {
- logger.warn("ReferenceConfig(" + url + ") is not DESTROYED when FINALIZE");
-
- /* don't destroy for now
- try {
- ReferenceConfig.this.destroy();
- } catch (Throwable t) {
- logger.warn("Unexpected err when destroy invoker of ReferenceConfig(" + url + ") in finalize method!", t);
- }
- */
- }
- }
- };
-
- public ReferenceConfig() {
- }
-
- public ReferenceConfig(Reference reference) {
- appendAnnotation(Reference.class, reference);
- setMethods(MethodConfig.constructMethodConfig(reference.methods()));
- }
-
- public URL toUrl() {
- return urls.isEmpty() ? null : urls.iterator().next();
- }
-
- public List toUrls() {
- return urls;
- }
-
- /**
- * This method should be called right after the creation of this class's instance, before any property in other config modules is used.
- * Check each config modules are created properly and override their properties if necessary.
- */
- public void checkAndUpdateSubConfigs() {
- if (StringUtils.isEmpty(interfaceName)) {
- throw new IllegalStateException(" interface not allow null!");
- }
- completeCompoundConfigs();
- startConfigCenter();
- // get consumer's global configuration
- checkDefault();
- this.refresh();
- if (getGeneric() == null && getConsumer() != null) {
- setGeneric(getConsumer().getGeneric());
- }
- if (ProtocolUtils.isGeneric(getGeneric())) {
- interfaceClass = GenericService.class;
- } else {
- try {
- interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
- .getContextClassLoader());
- } catch (ClassNotFoundException e) {
- throw new IllegalStateException(e.getMessage(), e);
- }
- checkInterfaceAndMethods(interfaceClass, methods);
- }
- resolveFile();
- checkApplication();
- checkMetadataReport();
- }
-
- public synchronized T get() {
- checkAndUpdateSubConfigs();
-
- if (destroyed) {
- throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
- }
- if (ref == null) {
- init();
- }
- return ref;
- }
-
- public synchronized void destroy() {
- if (ref == null) {
- return;
- }
- if (destroyed) {
- return;
- }
- destroyed = true;
- try {
- invoker.destroy();
- } catch (Throwable t) {
- logger.warn("Unexpected error occured when destroy invoker of ReferenceConfig(" + url + ").", t);
- }
- invoker = null;
- ref = null;
- }
-
- private void init() {
- if (initialized) {
- return;
- }
- checkStubAndLocal(interfaceClass);
- checkMock(interfaceClass);
- Map map = new HashMap();
-
- map.put(SIDE_KEY, CONSUMER_SIDE);
-
- appendRuntimeParameters(map);
- if (!isGeneric()) {
- String revision = Version.getVersion(interfaceClass, version);
- if (revision != null && revision.length() > 0) {
- map.put(REVISION_KEY, revision);
- }
-
- String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
- if (methods.length == 0) {
- logger.warn("No method found in service interface " + interfaceClass.getName());
- map.put(METHODS_KEY, ANY_VALUE);
- } else {
- map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), COMMA_SEPARATOR));
- }
- }
- map.put(INTERFACE_KEY, interfaceName);
- appendParameters(map, metrics);
- appendParameters(map, application);
- appendParameters(map, module);
- // remove 'default.' prefix for configs from ConsumerConfig
- // appendParameters(map, consumer, Constants.DEFAULT_KEY);
- appendParameters(map, consumer);
- appendParameters(map, this);
- Map attributes = null;
- if (CollectionUtils.isNotEmpty(methods)) {
- attributes = new HashMap();
- for (MethodConfig methodConfig : methods) {
- appendParameters(map, methodConfig, methodConfig.getName());
- String retryKey = methodConfig.getName() + ".retry";
- if (map.containsKey(retryKey)) {
- String retryValue = map.remove(retryKey);
- if ("false".equals(retryValue)) {
- map.put(methodConfig.getName() + ".retries", "0");
- }
- }
- attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig));
- }
- }
-
- String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
- if (StringUtils.isEmpty(hostToRegistry)) {
- hostToRegistry = NetUtils.getLocalHost();
- } else if (isInvalidLocalHost(hostToRegistry)) {
- throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
- }
- map.put(REGISTER_IP_KEY, hostToRegistry);
-
- ref = createProxy(map);
-
- String serviceKey = URL.buildKey(interfaceName, group, version);
- ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
- initialized = true;
- }
-
- private ConsumerModel buildConsumerModel(String serviceKey, Map attributes) {
- Method[] methods = interfaceClass.getMethods();
- Class serviceInterface = interfaceClass;
- if (interfaceClass == GenericService.class) {
- try {
- serviceInterface = Class.forName(interfaceName);
- methods = serviceInterface.getMethods();
- } catch (ClassNotFoundException e) {
- methods = interfaceClass.getMethods();
- }
- }
- return new ConsumerModel(serviceKey, serviceInterface, ref, methods, attributes);
- }
-
- @SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
- private T createProxy(Map map) {
- if (shouldJvmRefer(map)) {
- URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
- invoker = REF_PROTOCOL.refer(interfaceClass, url);
- if (logger.isInfoEnabled()) {
- logger.info("Using injvm service " + interfaceClass.getName());
- }
- } else {
- if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
- String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
- if (us != null && us.length > 0) {
- for (String u : us) {
- URL url = URL.valueOf(u);
- if (StringUtils.isEmpty(url.getPath())) {
- url = url.setPath(interfaceName);
- }
- if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
- urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
- } else {
- urls.add(ClusterUtils.mergeUrl(url, map));
- }
- }
- }
- } else { // assemble URL from register center's configuration
- // if protocols not injvm checkRegistry
- if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())){
- checkRegistry();
- List us = loadRegistries(false);
- if (CollectionUtils.isNotEmpty(us)) {
- for (URL u : us) {
- URL monitorUrl = loadMonitor(u);
- if (monitorUrl != null) {
- map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
- }
- urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
- }
- }
- if (urls.isEmpty()) {
- throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config to your spring config.");
- }
- }
- }
-
- if (urls.size() == 1) {
- invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
- } else {
- List> invokers = new ArrayList>();
- URL registryURL = null;
- for (URL url : urls) {
- invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
- if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
- registryURL = url; // use last registry url
- }
- }
- if (registryURL != null) { // registry url is available
- // use RegistryAwareCluster only when register's CLUSTER is available
- URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
- // The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
- invoker = CLUSTER.join(new StaticDirectory(u, invokers));
- } else { // not a registry url, must be direct invoke.
- invoker = CLUSTER.join(new StaticDirectory(invokers));
- }
- }
- }
-
- if (shouldCheck() && !invoker.isAvailable()) {
- throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
- }
- if (logger.isInfoEnabled()) {
- logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
- }
- /**
- * @since 2.7.0
- * ServiceData Store
- */
- MetadataReportService metadataReportService = null;
- if ((metadataReportService = getMetadataReportService()) != null) {
- URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
- metadataReportService.publishConsumer(consumerURL);
- }
- // create service proxy
- return (T) PROXY_FACTORY.getProxy(invoker);
- }
-
- /**
- * Figure out should refer the service in the same JVM from configurations. The default behavior is true
- * 1. if injvm is specified, then use it
- * 2. then if a url is specified, then assume it's a remote call
- * 3. otherwise, check scope parameter
- * 4. if scope is not specified but the target service is provided in the same JVM, then prefer to make the local
- * call, which is the default behavior
- */
- protected boolean shouldJvmRefer(Map map) {
- URL tmpUrl = new URL("temp", "localhost", 0, map);
- boolean isJvmRefer;
- if (isInjvm() == null) {
- // if a url is specified, don't do local reference
- if (url != null && url.length() > 0) {
- isJvmRefer = false;
- } else {
- // by default, reference local service if there is
- isJvmRefer = InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl);
- }
- } else {
- isJvmRefer = isInjvm();
- }
- return isJvmRefer;
- }
-
- protected boolean shouldCheck() {
- Boolean shouldCheck = isCheck();
- if (shouldCheck == null && getConsumer() != null) {
- shouldCheck = getConsumer().isCheck();
- }
- if (shouldCheck == null) {
- // default true
- shouldCheck = true;
- }
- return shouldCheck;
- }
-
- protected boolean shouldInit() {
- Boolean shouldInit = isInit();
- if (shouldInit == null && getConsumer() != null) {
- shouldInit = getConsumer().isInit();
- }
- if (shouldInit == null) {
- // default is false
- return false;
- }
- return shouldInit;
- }
-
- private void checkDefault() {
- if (consumer != null) {
- return;
- }
- setConsumer(ConfigManager.getInstance().getDefaultConsumer().orElseGet(() -> {
- ConsumerConfig consumerConfig = new ConsumerConfig();
- consumerConfig.refresh();
- return consumerConfig;
- }));
- }
-
- private void completeCompoundConfigs() {
- if (consumer != null) {
- if (application == null) {
- setApplication(consumer.getApplication());
- }
- if (module == null) {
- setModule(consumer.getModule());
- }
- if (registries == null) {
- setRegistries(consumer.getRegistries());
- }
- if (monitor == null) {
- setMonitor(consumer.getMonitor());
- }
- }
- if (module != null) {
- if (registries == null) {
- setRegistries(module.getRegistries());
- }
- if (monitor == null) {
- setMonitor(module.getMonitor());
- }
- }
- if (application != null) {
- if (registries == null) {
- setRegistries(application.getRegistries());
- }
- if (monitor == null) {
- setMonitor(application.getMonitor());
- }
- }
- }
-
- public Class> getInterfaceClass() {
- if (interfaceClass != null) {
- return interfaceClass;
- }
- if (isGeneric()
- || (getConsumer() != null && getConsumer().isGeneric())) {
- return GenericService.class;
- }
- try {
- if (interfaceName != null && interfaceName.length() > 0) {
- this.interfaceClass = Class.forName(interfaceName, true, ClassUtils.getClassLoader());
- }
- } catch (ClassNotFoundException t) {
- throw new IllegalStateException(t.getMessage(), t);
- }
- return interfaceClass;
- }
-
- /**
- * @param interfaceClass
- * @see #setInterface(Class)
- * @deprecated
- */
- @Deprecated
- public void setInterfaceClass(Class> interfaceClass) {
- setInterface(interfaceClass);
- }
-
- public String getInterface() {
- return interfaceName;
- }
-
- public void setInterface(String interfaceName) {
- this.interfaceName = interfaceName;
- if (StringUtils.isEmpty(id)) {
- id = interfaceName;
- }
- }
-
- public void setInterface(Class> interfaceClass) {
- if (interfaceClass != null && !interfaceClass.isInterface()) {
- throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!");
- }
- this.interfaceClass = interfaceClass;
- setInterface(interfaceClass == null ? null : interfaceClass.getName());
- }
-
- public String getClient() {
- return client;
- }
-
- public void setClient(String client) {
- checkName(Constants.CLIENT_KEY, client);
- this.client = client;
- }
-
- @Parameter(excluded = true)
- public String getUrl() {
- return url;
- }
-
- public void setUrl(String url) {
- this.url = url;
- }
-
- public List getMethods() {
- return methods;
- }
-
- @SuppressWarnings("unchecked")
- public void setMethods(List extends MethodConfig> methods) {
- this.methods = (List) methods;
- }
-
- public ConsumerConfig getConsumer() {
- return consumer;
- }
-
- public void setConsumer(ConsumerConfig consumer) {
- ConfigManager.getInstance().addConsumer(consumer);
- this.consumer = consumer;
- }
-
- public String getProtocol() {
- return protocol;
- }
-
- public void setProtocol(String protocol) {
- this.protocol = protocol;
- }
-
- // just for test
- Invoker> getInvoker() {
- return invoker;
- }
-
- @Override
- @Parameter(excluded = true)
- public String getPrefix() {
- return DUBBO + ".reference." + interfaceName;
- }
-
- private void resolveFile() {
- String resolve = System.getProperty(interfaceName);
- String resolveFile = null;
- if (StringUtils.isEmpty(resolve)) {
- resolveFile = System.getProperty("dubbo.resolve.file");
- if (StringUtils.isEmpty(resolveFile)) {
- File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
- if (userResolveFile.exists()) {
- resolveFile = userResolveFile.getAbsolutePath();
- }
- }
- if (resolveFile != null && resolveFile.length() > 0) {
- Properties properties = new Properties();
- try (FileInputStream fis = new FileInputStream(new File(resolveFile))) {
- properties.load(fis);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to load " + resolveFile + ", cause: " + e.getMessage(), e);
- }
-
- resolve = properties.getProperty(interfaceName);
- }
- }
- if (resolve != null && resolve.length() > 0) {
- url = resolve;
- if (logger.isWarnEnabled()) {
- if (resolveFile != null) {
- logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
- } else {
- logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
- }
- }
- }
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.Version;
+import org.apache.dubbo.common.bytecode.Wrapper;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.utils.ClassUtils;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.ConfigUtils;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.config.annotation.Reference;
+import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.config.event.ReferenceConfigDestroyedEvent;
+import org.apache.dubbo.config.event.ReferenceConfigInitializedEvent;
+import org.apache.dubbo.config.support.Parameter;
+import org.apache.dubbo.event.Event;
+import org.apache.dubbo.event.EventDispatcher;
+import org.apache.dubbo.metadata.integration.MetadataReportService;
+import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.ProxyFactory;
+import org.apache.dubbo.rpc.cluster.Cluster;
+import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
+import org.apache.dubbo.rpc.cluster.support.ClusterUtils;
+import org.apache.dubbo.rpc.cluster.support.RegistryAwareCluster;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ConsumerModel;
+import org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol;
+import org.apache.dubbo.rpc.service.GenericService;
+import org.apache.dubbo.rpc.support.ProtocolUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR;
+import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
+import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE;
+import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.SEMICOLON_SPLIT_PATTERN;
+import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.common.constants.ConfigConstants.CLUSTER_KEY;
+import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY;
+import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
+import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
+import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL;
+import static org.apache.dubbo.common.constants.RegistryConstants.CONSUMER_PROTOCOL;
+import static org.apache.dubbo.common.constants.RegistryConstants.REFER_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_PROTOCOL;
+import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost;
+import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY;
+import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY;
+import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
+
+/**
+ * ReferenceConfig
+ *
+ * @export
+ */
+public class ReferenceConfig extends AbstractReferenceConfig {
+
+ private static final long serialVersionUID = -5864351140409987595L;
+
+ /**
+ * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios.
+ * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}.
+ * For example:
+ *
+ * when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample,
+ * then the protocol is RegistryProtocol
+ *
+ * when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then
+ * the protocol is DubboProtocol
+ *
+ * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two
+ * layers, and eventually will get a ProtocolFilterWrapper or ProtocolListenerWrapper
+ */
+ private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+
+ /**
+ * The {@link Cluster}'s implementation with adaptive functionality, and actually it will get a {@link Cluster}'s
+ * specific implementation who is wrapped with MockClusterInvoker
+ */
+ private static final Cluster CLUSTER = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
+
+ /**
+ * A {@link ProxyFactory} implementation that will generate a reference service's proxy,the JavassistProxyFactory is
+ * its default implementation
+ */
+ private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+
+ /**
+ * The url of the reference service
+ */
+ private final List urls = new ArrayList();
+
+ /**
+ * The {@link EventDispatcher}
+ */
+ private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension();
+
+ /**
+ * The interface name of the reference service
+ */
+ private String interfaceName;
+
+ /**
+ * The interface class of the reference service
+ */
+ private Class> interfaceClass;
+
+ /**
+ * client type
+ */
+ private String client;
+
+ /**
+ * The url for peer-to-peer invocation
+ */
+ private String url;
+
+ /**
+ * The method configs
+ */
+ private List methods;
+
+ /**
+ * The consumer config (default)
+ */
+ private ConsumerConfig consumer;
+
+ /**
+ * Only the service provider of the specified protocol is invoked, and other protocols are ignored.
+ */
+ private String protocol;
+
+ /**
+ * The interface proxy reference
+ */
+ private transient volatile T ref;
+
+ /**
+ * The invoker of the reference service
+ */
+ private transient volatile Invoker> invoker;
+
+ /**
+ * The flag whether the ReferenceConfig has been initialized
+ */
+ private transient volatile boolean initialized;
+
+ /**
+ * whether this ReferenceConfig has been destroyed
+ */
+ private transient volatile boolean destroyed;
+
+ @SuppressWarnings("unused")
+ private final Object finalizerGuardian = new Object() {
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+
+ if (!ReferenceConfig.this.destroyed) {
+ logger.warn("ReferenceConfig(" + url + ") is not DESTROYED when FINALIZE");
+
+ /* don't destroy for now
+ try {
+ ReferenceConfig.this.destroy();
+ } catch (Throwable t) {
+ logger.warn("Unexpected err when destroy invoker of ReferenceConfig(" + url + ") in finalize method!", t);
+ }
+ */
+ }
+ }
+ };
+
+ public ReferenceConfig() {
+ }
+
+ public ReferenceConfig(Reference reference) {
+ appendAnnotation(Reference.class, reference);
+ setMethods(MethodConfig.constructMethodConfig(reference.methods()));
+ }
+
+ public URL toUrl() {
+ return urls.isEmpty() ? null : urls.iterator().next();
+ }
+
+ public List toUrls() {
+ return urls;
+ }
+
+ /**
+ * This method should be called right after the creation of this class's instance, before any property in other config modules is used.
+ * Check each config modules are created properly and override their properties if necessary.
+ */
+ public void checkAndUpdateSubConfigs() {
+ if (StringUtils.isEmpty(interfaceName)) {
+ throw new IllegalStateException(" interface not allow null!");
+ }
+ completeCompoundConfigs();
+ startConfigCenter();
+ // get consumer's global configuration
+ checkDefault();
+ this.refresh();
+ if (getGeneric() == null && getConsumer() != null) {
+ setGeneric(getConsumer().getGeneric());
+ }
+ if (ProtocolUtils.isGeneric(getGeneric())) {
+ interfaceClass = GenericService.class;
+ } else {
+ try {
+ interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
+ .getContextClassLoader());
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ }
+ checkInterfaceAndMethods(interfaceClass, methods);
+ }
+ resolveFile();
+ checkApplication();
+ checkMetadataReport();
+ }
+
+ public synchronized T get() {
+ checkAndUpdateSubConfigs();
+
+ if (destroyed) {
+ throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
+ }
+ if (ref == null) {
+ init();
+ }
+ return ref;
+ }
+
+ public synchronized void destroy() {
+ if (ref == null) {
+ return;
+ }
+ if (destroyed) {
+ return;
+ }
+ destroyed = true;
+ try {
+ invoker.destroy();
+ } catch (Throwable t) {
+ logger.warn("Unexpected error occured when destroy invoker of ReferenceConfig(" + url + ").", t);
+ }
+ invoker = null;
+ ref = null;
+
+ // dispatch a ReferenceConfigDestroyedEvent since 2.7.2
+ dispatch(new ReferenceConfigDestroyedEvent(this));
+ }
+
+ private void init() {
+ if (initialized) {
+ return;
+ }
+ checkStubAndLocal(interfaceClass);
+ checkMock(interfaceClass);
+ Map map = new HashMap();
+
+ map.put(SIDE_KEY, CONSUMER_SIDE);
+
+ appendRuntimeParameters(map);
+ if (!isGeneric()) {
+ String revision = Version.getVersion(interfaceClass, version);
+ if (revision != null && revision.length() > 0) {
+ map.put(REVISION_KEY, revision);
+ }
+
+ String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
+ if (methods.length == 0) {
+ logger.warn("No method found in service interface " + interfaceClass.getName());
+ map.put(METHODS_KEY, ANY_VALUE);
+ } else {
+ map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), COMMA_SEPARATOR));
+ }
+ }
+ map.put(INTERFACE_KEY, interfaceName);
+ appendParameters(map, metrics);
+ appendParameters(map, application);
+ appendParameters(map, module);
+ // remove 'default.' prefix for configs from ConsumerConfig
+ // appendParameters(map, consumer, Constants.DEFAULT_KEY);
+ appendParameters(map, consumer);
+ appendParameters(map, this);
+ Map attributes = null;
+ if (CollectionUtils.isNotEmpty(methods)) {
+ attributes = new HashMap();
+ for (MethodConfig methodConfig : methods) {
+ appendParameters(map, methodConfig, methodConfig.getName());
+ String retryKey = methodConfig.getName() + ".retry";
+ if (map.containsKey(retryKey)) {
+ String retryValue = map.remove(retryKey);
+ if ("false".equals(retryValue)) {
+ map.put(methodConfig.getName() + ".retries", "0");
+ }
+ }
+ attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig));
+ }
+ }
+
+ String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
+ if (StringUtils.isEmpty(hostToRegistry)) {
+ hostToRegistry = NetUtils.getLocalHost();
+ } else if (isInvalidLocalHost(hostToRegistry)) {
+ throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
+ }
+ map.put(REGISTER_IP_KEY, hostToRegistry);
+
+ ref = createProxy(map);
+
+ String serviceKey = URL.buildKey(interfaceName, group, version);
+ ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
+ initialized = true;
+
+ // dispatch a ReferenceConfigInitializedEvent since 2.7.2
+ dispatch(new ReferenceConfigInitializedEvent(this, invoker));
+ }
+
+ private ConsumerModel buildConsumerModel(String serviceKey, Map attributes) {
+ Method[] methods = interfaceClass.getMethods();
+ Class serviceInterface = interfaceClass;
+ if (interfaceClass == GenericService.class) {
+ try {
+ serviceInterface = Class.forName(interfaceName);
+ methods = serviceInterface.getMethods();
+ } catch (ClassNotFoundException e) {
+ methods = interfaceClass.getMethods();
+ }
+ }
+ return new ConsumerModel(serviceKey, serviceInterface, ref, methods, attributes);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
+ private T createProxy(Map map) {
+ if (shouldJvmRefer(map)) {
+ URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
+ invoker = REF_PROTOCOL.refer(interfaceClass, url);
+ if (logger.isInfoEnabled()) {
+ logger.info("Using injvm service " + interfaceClass.getName());
+ }
+ } else {
+ if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
+ String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
+ if (us != null && us.length > 0) {
+ for (String u : us) {
+ URL url = URL.valueOf(u);
+ if (StringUtils.isEmpty(url.getPath())) {
+ url = url.setPath(interfaceName);
+ }
+ if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
+ urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
+ } else {
+ urls.add(ClusterUtils.mergeUrl(url, map));
+ }
+ }
+ }
+ } else { // assemble URL from register center's configuration
+ // if protocols not injvm checkRegistry
+ if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
+ checkRegistry();
+ List us = loadRegistries(false);
+ if (CollectionUtils.isNotEmpty(us)) {
+ for (URL u : us) {
+ URL monitorUrl = loadMonitor(u);
+ if (monitorUrl != null) {
+ map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
+ }
+ urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
+ }
+ }
+ if (urls.isEmpty()) {
+ throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config to your spring config.");
+ }
+ }
+ }
+
+ if (urls.size() == 1) {
+ invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
+ } else {
+ List> invokers = new ArrayList>();
+ URL registryURL = null;
+ for (URL url : urls) {
+ invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
+ if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
+ registryURL = url; // use last registry url
+ }
+ }
+ if (registryURL != null) { // registry url is available
+ // use RegistryAwareCluster only when register's CLUSTER is available
+ URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
+ // The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
+ invoker = CLUSTER.join(new StaticDirectory(u, invokers));
+ } else { // not a registry url, must be direct invoke.
+ invoker = CLUSTER.join(new StaticDirectory(invokers));
+ }
+ }
+ }
+
+ if (shouldCheck() && !invoker.isAvailable()) {
+ throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
+ }
+ if (logger.isInfoEnabled()) {
+ logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
+ }
+ /**
+ * @since 2.7.0
+ * ServiceData Store
+ */
+ MetadataReportService metadataReportService = null;
+ if ((metadataReportService = getMetadataReportService()) != null) {
+ URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
+ metadataReportService.publishConsumer(consumerURL);
+ }
+ // create service proxy
+ return (T) PROXY_FACTORY.getProxy(invoker);
+ }
+
+ /**
+ * Figure out should refer the service in the same JVM from configurations. The default behavior is true
+ * 1. if injvm is specified, then use it
+ * 2. then if a url is specified, then assume it's a remote call
+ * 3. otherwise, check scope parameter
+ * 4. if scope is not specified but the target service is provided in the same JVM, then prefer to make the local
+ * call, which is the default behavior
+ */
+ protected boolean shouldJvmRefer(Map map) {
+ URL tmpUrl = new URL("temp", "localhost", 0, map);
+ boolean isJvmRefer;
+ if (isInjvm() == null) {
+ // if a url is specified, don't do local reference
+ if (url != null && url.length() > 0) {
+ isJvmRefer = false;
+ } else {
+ // by default, reference local service if there is
+ isJvmRefer = InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl);
+ }
+ } else {
+ isJvmRefer = isInjvm();
+ }
+ return isJvmRefer;
+ }
+
+ protected boolean shouldCheck() {
+ Boolean shouldCheck = isCheck();
+ if (shouldCheck == null && getConsumer() != null) {
+ shouldCheck = getConsumer().isCheck();
+ }
+ if (shouldCheck == null) {
+ // default true
+ shouldCheck = true;
+ }
+ return shouldCheck;
+ }
+
+ protected boolean shouldInit() {
+ Boolean shouldInit = isInit();
+ if (shouldInit == null && getConsumer() != null) {
+ shouldInit = getConsumer().isInit();
+ }
+ if (shouldInit == null) {
+ // default is false
+ return false;
+ }
+ return shouldInit;
+ }
+
+ private void checkDefault() {
+ if (consumer != null) {
+ return;
+ }
+ setConsumer(ConfigManager.getInstance().getDefaultConsumer().orElseGet(() -> {
+ ConsumerConfig consumerConfig = new ConsumerConfig();
+ consumerConfig.refresh();
+ return consumerConfig;
+ }));
+ }
+
+ private void completeCompoundConfigs() {
+ if (consumer != null) {
+ if (application == null) {
+ setApplication(consumer.getApplication());
+ }
+ if (module == null) {
+ setModule(consumer.getModule());
+ }
+ if (registries == null) {
+ setRegistries(consumer.getRegistries());
+ }
+ if (monitor == null) {
+ setMonitor(consumer.getMonitor());
+ }
+ }
+ if (module != null) {
+ if (registries == null) {
+ setRegistries(module.getRegistries());
+ }
+ if (monitor == null) {
+ setMonitor(module.getMonitor());
+ }
+ }
+ if (application != null) {
+ if (registries == null) {
+ setRegistries(application.getRegistries());
+ }
+ if (monitor == null) {
+ setMonitor(application.getMonitor());
+ }
+ }
+ }
+
+ public Class> getInterfaceClass() {
+ if (interfaceClass != null) {
+ return interfaceClass;
+ }
+ if (isGeneric()
+ || (getConsumer() != null && getConsumer().isGeneric())) {
+ return GenericService.class;
+ }
+ try {
+ if (interfaceName != null && interfaceName.length() > 0) {
+ this.interfaceClass = Class.forName(interfaceName, true, ClassUtils.getClassLoader());
+ }
+ } catch (ClassNotFoundException t) {
+ throw new IllegalStateException(t.getMessage(), t);
+ }
+ return interfaceClass;
+ }
+
+ /**
+ * @param interfaceClass
+ * @see #setInterface(Class)
+ * @deprecated
+ */
+ @Deprecated
+ public void setInterfaceClass(Class> interfaceClass) {
+ setInterface(interfaceClass);
+ }
+
+ public String getInterface() {
+ return interfaceName;
+ }
+
+ public void setInterface(String interfaceName) {
+ this.interfaceName = interfaceName;
+ if (StringUtils.isEmpty(id)) {
+ id = interfaceName;
+ }
+ }
+
+ public void setInterface(Class> interfaceClass) {
+ if (interfaceClass != null && !interfaceClass.isInterface()) {
+ throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!");
+ }
+ this.interfaceClass = interfaceClass;
+ setInterface(interfaceClass == null ? null : interfaceClass.getName());
+ }
+
+ public String getClient() {
+ return client;
+ }
+
+ public void setClient(String client) {
+ checkName(Constants.CLIENT_KEY, client);
+ this.client = client;
+ }
+
+ @Parameter(excluded = true)
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public List getMethods() {
+ return methods;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setMethods(List extends MethodConfig> methods) {
+ this.methods = (List) methods;
+ }
+
+ public ConsumerConfig getConsumer() {
+ return consumer;
+ }
+
+ public void setConsumer(ConsumerConfig consumer) {
+ ConfigManager.getInstance().addConsumer(consumer);
+ this.consumer = consumer;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ // just for test
+ Invoker> getInvoker() {
+ return invoker;
+ }
+
+ @Override
+ @Parameter(excluded = true)
+ public String getPrefix() {
+ return DUBBO + ".reference." + interfaceName;
+ }
+
+ private void resolveFile() {
+ String resolve = System.getProperty(interfaceName);
+ String resolveFile = null;
+ if (StringUtils.isEmpty(resolve)) {
+ resolveFile = System.getProperty("dubbo.resolve.file");
+ if (StringUtils.isEmpty(resolveFile)) {
+ File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
+ if (userResolveFile.exists()) {
+ resolveFile = userResolveFile.getAbsolutePath();
+ }
+ }
+ if (resolveFile != null && resolveFile.length() > 0) {
+ Properties properties = new Properties();
+ try (FileInputStream fis = new FileInputStream(new File(resolveFile))) {
+ properties.load(fis);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to load " + resolveFile + ", cause: " + e.getMessage(), e);
+ }
+
+ resolve = properties.getProperty(interfaceName);
+ }
+ }
+ if (resolve != null && resolve.length() > 0) {
+ url = resolve;
+ if (logger.isWarnEnabled()) {
+ if (resolveFile != null) {
+ logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
+ } else {
+ logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Dispatch an {@link Event event}
+ *
+ * @param event an {@link Event event}
+ * @since 2.7.2
+ */
+ protected void dispatch(Event event) {
+ eventDispatcher.dispatch(event);
+ }
+}
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
index f95e15e0e55..5be6e76f959 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
@@ -1,1054 +1,1077 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.config;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.URLBuilder;
-import org.apache.dubbo.common.Version;
-import org.apache.dubbo.common.bytecode.Wrapper;
-import org.apache.dubbo.common.config.Environment;
-import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.utils.ClassUtils;
-import org.apache.dubbo.common.utils.CollectionUtils;
-import org.apache.dubbo.common.utils.ConfigUtils;
-import org.apache.dubbo.common.utils.NamedThreadFactory;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.config.annotation.Service;
-import org.apache.dubbo.config.context.ConfigManager;
-import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker;
-import org.apache.dubbo.config.support.Parameter;
-import org.apache.dubbo.metadata.integration.MetadataReportService;
-import org.apache.dubbo.remoting.Constants;
-import org.apache.dubbo.rpc.Exporter;
-import org.apache.dubbo.rpc.Invoker;
-import org.apache.dubbo.rpc.Protocol;
-import org.apache.dubbo.rpc.ProxyFactory;
-import org.apache.dubbo.rpc.cluster.ConfiguratorFactory;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.rpc.model.ProviderModel;
-import org.apache.dubbo.rpc.service.GenericService;
-import org.apache.dubbo.rpc.support.ProtocolUtils;
-
-import java.lang.reflect.Method;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
-import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
-import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
-import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE;
-import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
-import static org.apache.dubbo.common.constants.ConfigConstants.DUBBO_IP_TO_BIND;
-import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY;
-import static org.apache.dubbo.config.Constants.DUBBO_PORT_TO_BIND;
-import static org.apache.dubbo.config.Constants.DUBBO_PORT_TO_REGISTRY;
-import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY;
-import static org.apache.dubbo.config.Constants.MULTICAST;
-import static org.apache.dubbo.config.Constants.PROTOCOLS_SUFFIX;
-import static org.apache.dubbo.rpc.Constants.SCOPE_KEY;
-import static org.apache.dubbo.rpc.Constants.SCOPE_LOCAL;
-import static org.apache.dubbo.config.Constants.SCOPE_NONE;
-import static org.apache.dubbo.rpc.Constants.SCOPE_REMOTE;
-import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
-import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
-import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
-import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
-import static org.apache.dubbo.rpc.Constants.PROXY_KEY;
-import static org.apache.dubbo.rpc.Constants.TOKEN_KEY;
-import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort;
-import static org.apache.dubbo.common.utils.NetUtils.getLocalHost;
-import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost;
-import static org.apache.dubbo.common.utils.NetUtils.isInvalidPort;
-
-/**
- * ServiceConfig
- *
- * @export
- */
-public class ServiceConfig extends AbstractServiceConfig {
-
- private static final long serialVersionUID = 3033787999037024738L;
-
- /**
- * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios.
- * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}.
- * For example:
- *
- * when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample,
- * then the protocol is RegistryProtocol
- *
- * when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then
- * the protocol is DubboProtocol
- *
- * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two
- * layers, and eventually will get a ProtocolFilterWrapper or ProtocolListenerWrapper
- */
- private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
-
- /**
- * A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its
- * default implementation
- */
- private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
-
- /**
- * A random port cache, the different protocols who has no port specified have different random port
- */
- private static final Map RANDOM_PORT_MAP = new HashMap();
-
- /**
- * A delayed exposure service timer
- */
- private static final ScheduledExecutorService delayExportExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboServiceDelayExporter", true));
-
- /**
- * The urls of the services exported
- */
- private final List urls = new ArrayList();
-
- /**
- * The exported services
- */
- private final List> exporters = new ArrayList>();
-
- /**
- * The interface name of the exported service
- */
- private String interfaceName;
-
- /**
- * The interface class of the exported service
- */
- private Class> interfaceClass;
-
- /**
- * The reference of the interface implementation
- */
- private T ref;
-
- /**
- * The service name
- */
- private String path;
-
- /**
- * The method configuration
- */
- private List methods;
-
- /**
- * The provider configuration
- */
- private ProviderConfig provider;
-
- /**
- * The providerIds
- */
- private String providerIds;
-
- /**
- * Whether the provider has been exported
- */
- private transient volatile boolean exported;
-
- /**
- * The flag whether a service has unexported ,if the method unexported is invoked, the value is true
- */
- private transient volatile boolean unexported;
-
- /**
- * whether it is a GenericService
- */
- private volatile String generic;
-
- public ServiceConfig() {
- }
-
- public ServiceConfig(Service service) {
- appendAnnotation(Service.class, service);
- setMethods(MethodConfig.constructMethodConfig(service.methods()));
- }
-
- @Deprecated
- private static List convertProviderToProtocol(List providers) {
- if (CollectionUtils.isEmpty(providers)) {
- return null;
- }
- List protocols = new ArrayList(providers.size());
- for (ProviderConfig provider : providers) {
- protocols.add(convertProviderToProtocol(provider));
- }
- return protocols;
- }
-
- @Deprecated
- private static List convertProtocolToProvider(List protocols) {
- if (CollectionUtils.isEmpty(protocols)) {
- return null;
- }
- List providers = new ArrayList(protocols.size());
- for (ProtocolConfig provider : protocols) {
- providers.add(convertProtocolToProvider(provider));
- }
- return providers;
- }
-
- @Deprecated
- private static ProtocolConfig convertProviderToProtocol(ProviderConfig provider) {
- ProtocolConfig protocol = new ProtocolConfig();
- protocol.setName(provider.getProtocol().getName());
- protocol.setServer(provider.getServer());
- protocol.setClient(provider.getClient());
- protocol.setCodec(provider.getCodec());
- protocol.setHost(provider.getHost());
- protocol.setPort(provider.getPort());
- protocol.setPath(provider.getPath());
- protocol.setPayload(provider.getPayload());
- protocol.setThreads(provider.getThreads());
- protocol.setParameters(provider.getParameters());
- return protocol;
- }
-
- @Deprecated
- private static ProviderConfig convertProtocolToProvider(ProtocolConfig protocol) {
- ProviderConfig provider = new ProviderConfig();
- provider.setProtocol(protocol);
- provider.setServer(protocol.getServer());
- provider.setClient(protocol.getClient());
- provider.setCodec(protocol.getCodec());
- provider.setHost(protocol.getHost());
- provider.setPort(protocol.getPort());
- provider.setPath(protocol.getPath());
- provider.setPayload(protocol.getPayload());
- provider.setThreads(protocol.getThreads());
- provider.setParameters(protocol.getParameters());
- return provider;
- }
-
- private static Integer getRandomPort(String protocol) {
- protocol = protocol.toLowerCase();
- return RANDOM_PORT_MAP.getOrDefault(protocol, Integer.MIN_VALUE);
- }
-
- private static void putRandomPort(String protocol, Integer port) {
- protocol = protocol.toLowerCase();
- if (!RANDOM_PORT_MAP.containsKey(protocol)) {
- RANDOM_PORT_MAP.put(protocol, port);
- logger.warn("Use random available port(" + port + ") for protocol " + protocol);
- }
- }
-
- public URL toUrl() {
- return urls.isEmpty() ? null : urls.iterator().next();
- }
-
- public List toUrls() {
- return urls;
- }
-
- @Parameter(excluded = true)
- public boolean isExported() {
- return exported;
- }
-
- @Parameter(excluded = true)
- public boolean isUnexported() {
- return unexported;
- }
-
- public void checkAndUpdateSubConfigs() {
- // Use default configs defined explicitly on global configs
- completeCompoundConfigs();
- // Config Center should always being started first.
- startConfigCenter();
- checkDefault();
- checkProtocol();
- checkApplication();
- // if protocol is not injvm checkRegistry
- if (!isOnlyInJvm()) {
- checkRegistry();
- }
- this.refresh();
- checkMetadataReport();
-
- if (StringUtils.isEmpty(interfaceName)) {
- throw new IllegalStateException(" interface not allow null!");
- }
-
- if (ref instanceof GenericService) {
- interfaceClass = GenericService.class;
- if (StringUtils.isEmpty(generic)) {
- generic = Boolean.TRUE.toString();
- }
- } else {
- try {
- interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
- .getContextClassLoader());
- } catch (ClassNotFoundException e) {
- throw new IllegalStateException(e.getMessage(), e);
- }
- checkInterfaceAndMethods(interfaceClass, methods);
- checkRef();
- generic = Boolean.FALSE.toString();
- }
- if (local != null) {
- if ("true".equals(local)) {
- local = interfaceName + "Local";
- }
- Class> localClass;
- try {
- localClass = ClassUtils.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);
- }
- }
- if (stub != null) {
- if ("true".equals(stub)) {
- stub = interfaceName + "Stub";
- }
- Class> stubClass;
- try {
- stubClass = ClassUtils.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);
- }
- }
- checkStubAndLocal(interfaceClass);
- checkMock(interfaceClass);
- }
-
- /**
- * Determine if it is injvm
- *
- * @return
- */
- private boolean isOnlyInJvm() {
- return getProtocols().size() == 1 && LOCAL_PROTOCOL.equalsIgnoreCase(getProtocols().get(0).getName());
- }
-
- public synchronized void export() {
- checkAndUpdateSubConfigs();
-
- if (!shouldExport()) {
- return;
- }
-
- if (shouldDelay()) {
- delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
- } else {
- doExport();
- }
- }
-
- private boolean shouldExport() {
- Boolean export = getExport();
- // default value is true
- return export == null ? true : export;
- }
-
- @Override
- public Boolean getExport() {
- return (export == null && provider != null) ? provider.getExport() : export;
- }
-
- private boolean shouldDelay() {
- Integer delay = getDelay();
- return delay != null && delay > 0;
- }
-
- @Override
- public Integer getDelay() {
- return (delay == null && provider != null) ? provider.getDelay() : delay;
- }
-
- protected synchronized void doExport() {
- if (unexported) {
- throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
- }
- if (exported) {
- return;
- }
- exported = true;
-
- if (StringUtils.isEmpty(path)) {
- path = interfaceName;
- }
- doExportUrls();
- }
-
- private void checkRef() {
- // reference should not be null, and is the implementation of the given interface
- if (ref == null) {
- throw new IllegalStateException("ref not allow null!");
- }
- if (!interfaceClass.isInstance(ref)) {
- throw new IllegalStateException("The class "
- + ref.getClass().getName() + " unimplemented interface "
- + interfaceClass + "!");
- }
- }
-
- public synchronized void unexport() {
- if (!exported) {
- return;
- }
- if (unexported) {
- return;
- }
- if (!exporters.isEmpty()) {
- for (Exporter> exporter : exporters) {
- try {
- exporter.unexport();
- } catch (Throwable t) {
- logger.warn("Unexpected error occured when unexport " + exporter, t);
- }
- }
- exporters.clear();
- }
- unexported = true;
- }
-
- @SuppressWarnings({"unchecked", "rawtypes"})
- private void doExportUrls() {
- List registryURLs = loadRegistries(true);
- for (ProtocolConfig protocolConfig : protocols) {
- String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
- ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
- ApplicationModel.initProviderModel(pathKey, providerModel);
- doExportUrlsFor1Protocol(protocolConfig, registryURLs);
- }
- }
-
- private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
- String name = protocolConfig.getName();
- if (StringUtils.isEmpty(name)) {
- name = DUBBO;
- }
-
- Map map = new HashMap();
- map.put(SIDE_KEY, PROVIDER_SIDE);
-
- appendRuntimeParameters(map);
- appendParameters(map, metrics);
- appendParameters(map, application);
- appendParameters(map, module);
- // remove 'default.' prefix for configs from ProviderConfig
- // appendParameters(map, provider, Constants.DEFAULT_KEY);
- appendParameters(map, provider);
- appendParameters(map, protocolConfig);
- appendParameters(map, this);
- if (CollectionUtils.isNotEmpty(methods)) {
- for (MethodConfig method : methods) {
- appendParameters(map, method, method.getName());
- String retryKey = method.getName() + ".retry";
- if (map.containsKey(retryKey)) {
- String retryValue = map.remove(retryKey);
- if ("false".equals(retryValue)) {
- map.put(method.getName() + ".retries", "0");
- }
- }
- List arguments = method.getArguments();
- if (CollectionUtils.isNotEmpty(arguments)) {
- for (ArgumentConfig argument : arguments) {
- // convert argument type
- if (argument.getType() != null && argument.getType().length() > 0) {
- Method[] methods = interfaceClass.getMethods();
- // visit all methods
- if (methods != null && methods.length > 0) {
- for (int i = 0; i < methods.length; i++) {
- String methodName = methods[i].getName();
- // target the method, and get its signature
- if (methodName.equals(method.getName())) {
- Class>[] argtypes = methods[i].getParameterTypes();
- // one callback in the method
- if (argument.getIndex() != -1) {
- if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
- appendParameters(map, argument, method.getName() + "." + argument.getIndex());
- } else {
- throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
- }
- } else {
- // multiple callbacks in the method
- for (int j = 0; j < argtypes.length; j++) {
- Class> argclazz = argtypes[j];
- if (argclazz.getName().equals(argument.getType())) {
- appendParameters(map, argument, method.getName() + "." + j);
- if (argument.getIndex() != -1 && argument.getIndex() != j) {
- throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
- }
- }
- }
- }
- }
- }
- }
- } else if (argument.getIndex() != -1) {
- appendParameters(map, argument, method.getName() + "." + argument.getIndex());
- } else {
- throw new IllegalArgumentException("Argument config must set index or type attribute.eg: or ");
- }
-
- }
- }
- } // end of methods for
- }
-
- if (ProtocolUtils.isGeneric(generic)) {
- map.put(GENERIC_KEY, generic);
- map.put(METHODS_KEY, ANY_VALUE);
- } else {
- String revision = Version.getVersion(interfaceClass, version);
- if (revision != null && revision.length() > 0) {
- map.put(REVISION_KEY, revision);
- }
-
- String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
- if (methods.length == 0) {
- logger.warn("No method found in service interface " + interfaceClass.getName());
- map.put(METHODS_KEY, ANY_VALUE);
- } else {
- map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), ","));
- }
- }
- if (!ConfigUtils.isEmpty(token)) {
- if (ConfigUtils.isDefault(token)) {
- map.put(TOKEN_KEY, UUID.randomUUID().toString());
- } else {
- map.put(TOKEN_KEY, token);
- }
- }
- // export service
- String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
- Integer port = this.findConfigedPorts(protocolConfig, name, map);
- URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
-
- if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
- .hasExtension(url.getProtocol())) {
- url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
- .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
- }
-
- String scope = url.getParameter(SCOPE_KEY);
- // don't export when none is configured
- if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
-
- // export to local if the config is not remote (export to remote only when config is remote)
- if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
- exportLocal(url);
- }
- // export to remote if the config is not local (export to local only when config is local)
- if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
- if (!isOnlyInJvm() && logger.isInfoEnabled()) {
- logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
- }
- if (CollectionUtils.isNotEmpty(registryURLs)) {
- for (URL registryURL : registryURLs) {
- //if protocol is only injvm ,not register
- if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
- continue;
- }
- url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
- URL monitorUrl = loadMonitor(registryURL);
- if (monitorUrl != null) {
- url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
- }
- if (logger.isInfoEnabled()) {
- logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
- }
-
- // For providers, this is used to enable custom proxy to generate invoker
- String proxy = url.getParameter(PROXY_KEY);
- if (StringUtils.isNotEmpty(proxy)) {
- registryURL = registryURL.addParameter(PROXY_KEY, proxy);
- }
-
- Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
- DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
-
- Exporter> exporter = protocol.export(wrapperInvoker);
- exporters.add(exporter);
- }
- } else {
- Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
- DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
-
- Exporter> exporter = protocol.export(wrapperInvoker);
- exporters.add(exporter);
- }
- /**
- * @since 2.7.0
- * ServiceData Store
- */
- MetadataReportService metadataReportService = null;
- if ((metadataReportService = getMetadataReportService()) != null) {
- metadataReportService.publishProvider(url);
- }
- }
- }
- this.urls.add(url);
- }
-
- @SuppressWarnings({"unchecked", "rawtypes"})
- /**
- * always export injvm
- */
- private void exportLocal(URL url) {
- URL local = URLBuilder.from(url)
- .setProtocol(LOCAL_PROTOCOL)
- .setHost(LOCALHOST_VALUE)
- .setPort(0)
- .build();
- Exporter> exporter = protocol.export(
- proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
- exporters.add(exporter);
- logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
- }
-
- private Optional getContextPath(ProtocolConfig protocolConfig) {
- String contextPath = protocolConfig.getContextpath();
- if (StringUtils.isEmpty(contextPath) && provider != null) {
- contextPath = provider.getContextpath();
- }
- return Optional.ofNullable(contextPath);
- }
-
- protected Class getServiceClass(T ref) {
- return ref.getClass();
- }
-
- /**
- * Register & bind IP address for service provider, can be configured separately.
- * Configuration priority: environment variables -> java system properties -> host property in config file ->
- * /etc/hosts -> default network address -> first available network address
- *
- * @param protocolConfig
- * @param registryURLs
- * @param map
- * @return
- */
- private String findConfigedHosts(ProtocolConfig protocolConfig, List registryURLs, Map map) {
- boolean anyhost = false;
-
- String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND);
- if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) {
- throw new IllegalArgumentException("Specified invalid bind ip from property:" + DUBBO_IP_TO_BIND + ", value:" + hostToBind);
- }
-
- // if bind ip is not found in environment, keep looking up
- if (StringUtils.isEmpty(hostToBind)) {
- hostToBind = protocolConfig.getHost();
- if (provider != null && StringUtils.isEmpty(hostToBind)) {
- hostToBind = provider.getHost();
- }
- if (isInvalidLocalHost(hostToBind)) {
- anyhost = true;
- try {
- hostToBind = InetAddress.getLocalHost().getHostAddress();
- } catch (UnknownHostException e) {
- logger.warn(e.getMessage(), e);
- }
- if (isInvalidLocalHost(hostToBind)) {
- if (CollectionUtils.isNotEmpty(registryURLs)) {
- for (URL registryURL : registryURLs) {
- if (MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) {
- // skip multicast registry since we cannot connect to it via Socket
- continue;
- }
- try (Socket socket = new Socket()) {
- SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
- socket.connect(addr, 1000);
- hostToBind = socket.getLocalAddress().getHostAddress();
- break;
- } catch (Exception e) {
- logger.warn(e.getMessage(), e);
- }
- }
- }
- if (isInvalidLocalHost(hostToBind)) {
- hostToBind = getLocalHost();
- }
- }
- }
- }
-
- map.put(Constants.BIND_IP_KEY, hostToBind);
-
- // registry ip is not used for bind ip by default
- String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY);
- if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) {
- throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
- } else if (StringUtils.isEmpty(hostToRegistry)) {
- // bind ip is used as registry ip by default
- hostToRegistry = hostToBind;
- }
-
- map.put(ANYHOST_KEY, String.valueOf(anyhost));
-
- return hostToRegistry;
- }
-
- /**
- * Register port and bind port for the provider, can be configured separately
- * Configuration priority: environment variable -> java system properties -> port property in protocol config file
- * -> protocol default port
- *
- * @param protocolConfig
- * @param name
- * @return
- */
- private Integer findConfigedPorts(ProtocolConfig protocolConfig, String name, Map map) {
- Integer portToBind = null;
-
- // parse bind port from environment
- String port = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_BIND);
- portToBind = parsePort(port);
-
- // if there's no bind port found from environment, keep looking up.
- if (portToBind == null) {
- portToBind = protocolConfig.getPort();
- if (provider != null && (portToBind == null || portToBind == 0)) {
- portToBind = provider.getPort();
- }
- final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
- if (portToBind == null || portToBind == 0) {
- portToBind = defaultPort;
- }
- if (portToBind == null || portToBind <= 0) {
- portToBind = getRandomPort(name);
- if (portToBind == null || portToBind < 0) {
- portToBind = getAvailablePort(defaultPort);
- putRandomPort(name, portToBind);
- }
- }
- }
-
- // save bind port, used as url's key later
- map.put(Constants.BIND_PORT_KEY, String.valueOf(portToBind));
-
- // registry port, not used as bind port by default
- String portToRegistryStr = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_REGISTRY);
- Integer portToRegistry = parsePort(portToRegistryStr);
- if (portToRegistry == null) {
- portToRegistry = portToBind;
- }
-
- return portToRegistry;
- }
-
- private Integer parsePort(String configPort) {
- Integer port = null;
- if (configPort != null && configPort.length() > 0) {
- try {
- Integer intPort = Integer.parseInt(configPort);
- if (isInvalidPort(intPort)) {
- throw new IllegalArgumentException("Specified invalid port from env value:" + configPort);
- }
- port = intPort;
- } catch (Exception e) {
- throw new IllegalArgumentException("Specified invalid port from env value:" + configPort);
- }
- }
- return port;
- }
-
- private String getValueFromConfig(ProtocolConfig protocolConfig, String key) {
- String protocolPrefix = protocolConfig.getName().toUpperCase() + "_";
- String port = ConfigUtils.getSystemProperty(protocolPrefix + key);
- if (StringUtils.isEmpty(port)) {
- port = ConfigUtils.getSystemProperty(key);
- }
- return port;
- }
-
- private void completeCompoundConfigs() {
- if (provider != null) {
- if (application == null) {
- setApplication(provider.getApplication());
- }
- if (module == null) {
- setModule(provider.getModule());
- }
- if (registries == null) {
- setRegistries(provider.getRegistries());
- }
- if (monitor == null) {
- setMonitor(provider.getMonitor());
- }
- if (protocols == null) {
- setProtocols(provider.getProtocols());
- }
- if (configCenter == null) {
- setConfigCenter(provider.getConfigCenter());
- }
- }
- if (module != null) {
- if (registries == null) {
- setRegistries(module.getRegistries());
- }
- if (monitor == null) {
- setMonitor(module.getMonitor());
- }
- }
- if (application != null) {
- if (registries == null) {
- setRegistries(application.getRegistries());
- }
- if (monitor == null) {
- setMonitor(application.getMonitor());
- }
- }
- }
-
- private void checkDefault() {
- createProviderIfAbsent();
- }
-
- private void createProviderIfAbsent() {
- if (provider != null) {
- return;
- }
- setProvider(
- ConfigManager.getInstance()
- .getDefaultProvider()
- .orElseGet(() -> {
- ProviderConfig providerConfig = new ProviderConfig();
- providerConfig.refresh();
- return providerConfig;
- })
- );
- }
-
- private void checkProtocol() {
- if (CollectionUtils.isEmpty(protocols) && provider != null) {
- setProtocols(provider.getProtocols());
- }
- convertProtocolIdsToProtocols();
- }
-
- private void convertProtocolIdsToProtocols() {
- if (StringUtils.isEmpty(protocolIds) && CollectionUtils.isEmpty(protocols)) {
- List configedProtocols = new ArrayList<>();
- configedProtocols.addAll(getSubProperties(Environment.getInstance()
- .getExternalConfigurationMap(), PROTOCOLS_SUFFIX));
- configedProtocols.addAll(getSubProperties(Environment.getInstance()
- .getAppExternalConfigurationMap(), PROTOCOLS_SUFFIX));
-
- protocolIds = String.join(",", configedProtocols);
- }
-
- if (StringUtils.isEmpty(protocolIds)) {
- if (CollectionUtils.isEmpty(protocols)) {
- setProtocols(
- ConfigManager.getInstance().getDefaultProtocols()
- .filter(CollectionUtils::isNotEmpty)
- .orElseGet(() -> {
- ProtocolConfig protocolConfig = new ProtocolConfig();
- protocolConfig.refresh();
- return new ArrayList<>(Arrays.asList(protocolConfig));
- })
- );
- }
- } else {
- String[] arr = COMMA_SPLIT_PATTERN.split(protocolIds);
- List tmpProtocols = CollectionUtils.isNotEmpty(protocols) ? protocols : new ArrayList<>();
- Arrays.stream(arr).forEach(id -> {
- if (tmpProtocols.stream().noneMatch(prot -> prot.getId().equals(id))) {
- tmpProtocols.add(ConfigManager.getInstance().getProtocol(id).orElseGet(() -> {
- ProtocolConfig protocolConfig = new ProtocolConfig();
- protocolConfig.setId(id);
- protocolConfig.refresh();
- return protocolConfig;
- }));
- }
- });
- if (tmpProtocols.size() > arr.length) {
- throw new IllegalStateException("Too much protocols found, the protocols comply to this service are :" + protocolIds + " but got " + protocols
- .size() + " registries!");
- }
- setProtocols(tmpProtocols);
- }
- }
-
- public Class> getInterfaceClass() {
- if (interfaceClass != null) {
- return interfaceClass;
- }
- if (ref instanceof GenericService) {
- return GenericService.class;
- }
- try {
- if (interfaceName != null && interfaceName.length() > 0) {
- this.interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
- .getContextClassLoader());
- }
- } catch (ClassNotFoundException t) {
- throw new IllegalStateException(t.getMessage(), t);
- }
- return interfaceClass;
- }
-
- /**
- * @param interfaceClass
- * @see #setInterface(Class)
- * @deprecated
- */
- public void setInterfaceClass(Class> interfaceClass) {
- setInterface(interfaceClass);
- }
-
- public String getInterface() {
- return interfaceName;
- }
-
- public void setInterface(Class> interfaceClass) {
- if (interfaceClass != null && !interfaceClass.isInterface()) {
- throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!");
- }
- this.interfaceClass = interfaceClass;
- setInterface(interfaceClass == null ? null : interfaceClass.getName());
- }
-
- public void setInterface(String interfaceName) {
- this.interfaceName = interfaceName;
- if (StringUtils.isEmpty(id)) {
- id = interfaceName;
- }
- }
-
- public T getRef() {
- return ref;
- }
-
- public void setRef(T ref) {
- this.ref = ref;
- }
-
- @Parameter(excluded = true)
- public String getPath() {
- return path;
- }
-
- public void setPath(String path) {
- checkPathName(PATH_KEY, path);
- this.path = path;
- }
-
- public List getMethods() {
- return methods;
- }
-
- // ======== Deprecated ========
-
- @SuppressWarnings("unchecked")
- public void setMethods(List extends MethodConfig> methods) {
- this.methods = (List) methods;
- }
-
- public ProviderConfig getProvider() {
- return provider;
- }
-
- public void setProvider(ProviderConfig provider) {
- ConfigManager.getInstance().addProvider(provider);
- this.provider = provider;
- }
-
- @Parameter(excluded = true)
- public String getProviderIds() {
- return providerIds;
- }
-
- public void setProviderIds(String providerIds) {
- this.providerIds = providerIds;
- }
-
- public String getGeneric() {
- return generic;
- }
-
- public void setGeneric(String generic) {
- if (StringUtils.isEmpty(generic)) {
- return;
- }
- if (ProtocolUtils.isGeneric(generic)) {
- this.generic = generic;
- } else {
- throw new IllegalArgumentException("Unsupported generic type " + generic);
- }
- }
-
- @Override
- public void setMock(Boolean mock) {
- throw new IllegalArgumentException("mock doesn't support on provider side");
- }
-
- @Override
- public void setMock(String mock) {
- throw new IllegalArgumentException("mock doesn't support on provider side");
- }
-
- public List getExportedUrls() {
- return urls;
- }
-
- /**
- * @deprecated Replace to getProtocols()
- */
- @Deprecated
- public List getProviders() {
- return convertProtocolToProvider(protocols);
- }
-
- /**
- * @deprecated Replace to setProtocols()
- */
- @Deprecated
- public void setProviders(List providers) {
- this.protocols = convertProviderToProtocol(providers);
- }
-
- @Override
- @Parameter(excluded = true)
- public String getPrefix() {
- return DUBBO + ".service." + interfaceName;
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.URLBuilder;
+import org.apache.dubbo.common.Version;
+import org.apache.dubbo.common.bytecode.Wrapper;
+import org.apache.dubbo.common.config.Environment;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.utils.ClassUtils;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.ConfigUtils;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.config.annotation.Service;
+import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.config.event.ServiceConfigExportedEvent;
+import org.apache.dubbo.config.event.ServiceConfigUnexportedEvent;
+import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker;
+import org.apache.dubbo.config.support.Parameter;
+import org.apache.dubbo.event.Event;
+import org.apache.dubbo.event.EventDispatcher;
+import org.apache.dubbo.metadata.integration.MetadataReportService;
+import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.rpc.Exporter;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.ProxyFactory;
+import org.apache.dubbo.rpc.cluster.ConfiguratorFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.service.GenericService;
+import org.apache.dubbo.rpc.support.ProtocolUtils;
+
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
+import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE;
+import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
+import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.common.constants.ConfigConstants.DUBBO_IP_TO_BIND;
+import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.EXPORT_KEY;
+import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort;
+import static org.apache.dubbo.common.utils.NetUtils.getLocalHost;
+import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost;
+import static org.apache.dubbo.common.utils.NetUtils.isInvalidPort;
+import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY;
+import static org.apache.dubbo.config.Constants.DUBBO_PORT_TO_BIND;
+import static org.apache.dubbo.config.Constants.DUBBO_PORT_TO_REGISTRY;
+import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY;
+import static org.apache.dubbo.config.Constants.MULTICAST;
+import static org.apache.dubbo.config.Constants.PROTOCOLS_SUFFIX;
+import static org.apache.dubbo.config.Constants.SCOPE_NONE;
+import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
+import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
+import static org.apache.dubbo.rpc.Constants.PROXY_KEY;
+import static org.apache.dubbo.rpc.Constants.SCOPE_KEY;
+import static org.apache.dubbo.rpc.Constants.SCOPE_LOCAL;
+import static org.apache.dubbo.rpc.Constants.SCOPE_REMOTE;
+import static org.apache.dubbo.rpc.Constants.TOKEN_KEY;
+
+/**
+ * ServiceConfig
+ *
+ * @export
+ */
+public class ServiceConfig extends AbstractServiceConfig {
+
+ private static final long serialVersionUID = 3033787999037024738L;
+
+ /**
+ * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios.
+ * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}.
+ * For example:
+ *
+ * when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample,
+ * then the protocol is RegistryProtocol
+ *
+ * when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then
+ * the protocol is DubboProtocol
+ *
+ * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two
+ * layers, and eventually will get a ProtocolFilterWrapper or ProtocolListenerWrapper
+ */
+ private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+
+ /**
+ * A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its
+ * default implementation
+ */
+ private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+
+ /**
+ * A random port cache, the different protocols who has no port specified have different random port
+ */
+ private static final Map RANDOM_PORT_MAP = new HashMap();
+
+ /**
+ * A delayed exposure service timer
+ */
+ private static final ScheduledExecutorService delayExportExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboServiceDelayExporter", true));
+
+ /**
+ * The urls of the services exported
+ */
+ private final List urls = new ArrayList();
+
+ /**
+ * The exported services
+ */
+ private final List> exporters = new ArrayList>();
+
+ private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension();
+
+ /**
+ * The interface name of the exported service
+ */
+ private String interfaceName;
+
+ /**
+ * The interface class of the exported service
+ */
+ private Class> interfaceClass;
+
+ /**
+ * The reference of the interface implementation
+ */
+ private T ref;
+
+ /**
+ * The service name
+ */
+ private String path;
+
+ /**
+ * The method configuration
+ */
+ private List methods;
+
+ /**
+ * The provider configuration
+ */
+ private ProviderConfig provider;
+
+ /**
+ * The providerIds
+ */
+ private String providerIds;
+
+ /**
+ * Whether the provider has been exported
+ */
+ private transient volatile boolean exported;
+
+ /**
+ * The flag whether a service has unexported ,if the method unexported is invoked, the value is true
+ */
+ private transient volatile boolean unexported;
+
+ /**
+ * whether it is a GenericService
+ */
+ private volatile String generic;
+
+ public ServiceConfig() {
+ }
+
+ public ServiceConfig(Service service) {
+ appendAnnotation(Service.class, service);
+ setMethods(MethodConfig.constructMethodConfig(service.methods()));
+ }
+
+ @Deprecated
+ private static List convertProviderToProtocol(List providers) {
+ if (CollectionUtils.isEmpty(providers)) {
+ return null;
+ }
+ List protocols = new ArrayList(providers.size());
+ for (ProviderConfig provider : providers) {
+ protocols.add(convertProviderToProtocol(provider));
+ }
+ return protocols;
+ }
+
+ @Deprecated
+ private static List convertProtocolToProvider(List protocols) {
+ if (CollectionUtils.isEmpty(protocols)) {
+ return null;
+ }
+ List providers = new ArrayList(protocols.size());
+ for (ProtocolConfig provider : protocols) {
+ providers.add(convertProtocolToProvider(provider));
+ }
+ return providers;
+ }
+
+ @Deprecated
+ private static ProtocolConfig convertProviderToProtocol(ProviderConfig provider) {
+ ProtocolConfig protocol = new ProtocolConfig();
+ protocol.setName(provider.getProtocol().getName());
+ protocol.setServer(provider.getServer());
+ protocol.setClient(provider.getClient());
+ protocol.setCodec(provider.getCodec());
+ protocol.setHost(provider.getHost());
+ protocol.setPort(provider.getPort());
+ protocol.setPath(provider.getPath());
+ protocol.setPayload(provider.getPayload());
+ protocol.setThreads(provider.getThreads());
+ protocol.setParameters(provider.getParameters());
+ return protocol;
+ }
+
+ @Deprecated
+ private static ProviderConfig convertProtocolToProvider(ProtocolConfig protocol) {
+ ProviderConfig provider = new ProviderConfig();
+ provider.setProtocol(protocol);
+ provider.setServer(protocol.getServer());
+ provider.setClient(protocol.getClient());
+ provider.setCodec(protocol.getCodec());
+ provider.setHost(protocol.getHost());
+ provider.setPort(protocol.getPort());
+ provider.setPath(protocol.getPath());
+ provider.setPayload(protocol.getPayload());
+ provider.setThreads(protocol.getThreads());
+ provider.setParameters(protocol.getParameters());
+ return provider;
+ }
+
+ private static Integer getRandomPort(String protocol) {
+ protocol = protocol.toLowerCase();
+ return RANDOM_PORT_MAP.getOrDefault(protocol, Integer.MIN_VALUE);
+ }
+
+ private static void putRandomPort(String protocol, Integer port) {
+ protocol = protocol.toLowerCase();
+ if (!RANDOM_PORT_MAP.containsKey(protocol)) {
+ RANDOM_PORT_MAP.put(protocol, port);
+ logger.warn("Use random available port(" + port + ") for protocol " + protocol);
+ }
+ }
+
+ public URL toUrl() {
+ return urls.isEmpty() ? null : urls.iterator().next();
+ }
+
+ public List toUrls() {
+ return urls;
+ }
+
+ @Parameter(excluded = true)
+ public boolean isExported() {
+ return exported;
+ }
+
+ @Parameter(excluded = true)
+ public boolean isUnexported() {
+ return unexported;
+ }
+
+ public void checkAndUpdateSubConfigs() {
+ // Use default configs defined explicitly on global configs
+ completeCompoundConfigs();
+ // Config Center should always being started first.
+ startConfigCenter();
+ checkDefault();
+ checkProtocol();
+ checkApplication();
+ // if protocol is not injvm checkRegistry
+ if (!isOnlyInJvm()) {
+ checkRegistry();
+ }
+ this.refresh();
+ checkMetadataReport();
+
+ if (StringUtils.isEmpty(interfaceName)) {
+ throw new IllegalStateException(" interface not allow null!");
+ }
+
+ if (ref instanceof GenericService) {
+ interfaceClass = GenericService.class;
+ if (StringUtils.isEmpty(generic)) {
+ generic = Boolean.TRUE.toString();
+ }
+ } else {
+ try {
+ interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
+ .getContextClassLoader());
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ }
+ checkInterfaceAndMethods(interfaceClass, methods);
+ checkRef();
+ generic = Boolean.FALSE.toString();
+ }
+ if (local != null) {
+ if ("true".equals(local)) {
+ local = interfaceName + "Local";
+ }
+ Class> localClass;
+ try {
+ localClass = ClassUtils.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);
+ }
+ }
+ if (stub != null) {
+ if ("true".equals(stub)) {
+ stub = interfaceName + "Stub";
+ }
+ Class> stubClass;
+ try {
+ stubClass = ClassUtils.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);
+ }
+ }
+ checkStubAndLocal(interfaceClass);
+ checkMock(interfaceClass);
+ }
+
+ /**
+ * Determine if it is injvm
+ *
+ * @return
+ */
+ private boolean isOnlyInJvm() {
+ return getProtocols().size() == 1 && LOCAL_PROTOCOL.equalsIgnoreCase(getProtocols().get(0).getName());
+ }
+
+ public synchronized void export() {
+ checkAndUpdateSubConfigs();
+
+ if (!shouldExport()) {
+ return;
+ }
+
+ if (shouldDelay()) {
+ delayExportExecutor.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
+ } else {
+ doExport();
+ }
+ }
+
+ private boolean shouldExport() {
+ Boolean export = getExport();
+ // default value is true
+ return export == null ? true : export;
+ }
+
+ @Override
+ public Boolean getExport() {
+ return (export == null && provider != null) ? provider.getExport() : export;
+ }
+
+ private boolean shouldDelay() {
+ Integer delay = getDelay();
+ return delay != null && delay > 0;
+ }
+
+ @Override
+ public Integer getDelay() {
+ return (delay == null && provider != null) ? provider.getDelay() : delay;
+ }
+
+ protected synchronized void doExport() {
+ if (unexported) {
+ throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
+ }
+ if (exported) {
+ return;
+ }
+ exported = true;
+
+ if (StringUtils.isEmpty(path)) {
+ path = interfaceName;
+ }
+ doExportUrls();
+
+ // dispatch a ServiceConfigExportedEvent since 2.7.2
+ dispatch(new ServiceConfigExportedEvent(this));
+ }
+
+ private void checkRef() {
+ // reference should not be null, and is the implementation of the given interface
+ if (ref == null) {
+ throw new IllegalStateException("ref not allow null!");
+ }
+ if (!interfaceClass.isInstance(ref)) {
+ throw new IllegalStateException("The class "
+ + ref.getClass().getName() + " unimplemented interface "
+ + interfaceClass + "!");
+ }
+ }
+
+ public synchronized void unexport() {
+ if (!exported) {
+ return;
+ }
+ if (unexported) {
+ return;
+ }
+ if (!exporters.isEmpty()) {
+ for (Exporter> exporter : exporters) {
+ try {
+ exporter.unexport();
+ } catch (Throwable t) {
+ logger.warn("Unexpected error occured when unexport " + exporter, t);
+ }
+ }
+ exporters.clear();
+ }
+ unexported = true;
+
+ // dispatch a ServiceConfigUnExportedEvent since 2.7.2
+ dispatch(new ServiceConfigUnexportedEvent(this));
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private void doExportUrls() {
+ List registryURLs = loadRegistries(true);
+ for (ProtocolConfig protocolConfig : protocols) {
+ String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
+ ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
+ ApplicationModel.initProviderModel(pathKey, providerModel);
+ doExportUrlsFor1Protocol(protocolConfig, registryURLs);
+ }
+ }
+
+ private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
+ String name = protocolConfig.getName();
+ if (StringUtils.isEmpty(name)) {
+ name = DUBBO;
+ }
+
+ Map map = new HashMap();
+ map.put(SIDE_KEY, PROVIDER_SIDE);
+
+ appendRuntimeParameters(map);
+ appendParameters(map, metrics);
+ appendParameters(map, application);
+ appendParameters(map, module);
+ // remove 'default.' prefix for configs from ProviderConfig
+ // appendParameters(map, provider, Constants.DEFAULT_KEY);
+ appendParameters(map, provider);
+ appendParameters(map, protocolConfig);
+ appendParameters(map, this);
+ if (CollectionUtils.isNotEmpty(methods)) {
+ for (MethodConfig method : methods) {
+ appendParameters(map, method, method.getName());
+ String retryKey = method.getName() + ".retry";
+ if (map.containsKey(retryKey)) {
+ String retryValue = map.remove(retryKey);
+ if ("false".equals(retryValue)) {
+ map.put(method.getName() + ".retries", "0");
+ }
+ }
+ List arguments = method.getArguments();
+ if (CollectionUtils.isNotEmpty(arguments)) {
+ for (ArgumentConfig argument : arguments) {
+ // convert argument type
+ if (argument.getType() != null && argument.getType().length() > 0) {
+ Method[] methods = interfaceClass.getMethods();
+ // visit all methods
+ if (methods != null && methods.length > 0) {
+ for (int i = 0; i < methods.length; i++) {
+ String methodName = methods[i].getName();
+ // target the method, and get its signature
+ if (methodName.equals(method.getName())) {
+ Class>[] argtypes = methods[i].getParameterTypes();
+ // one callback in the method
+ if (argument.getIndex() != -1) {
+ if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
+ appendParameters(map, argument, method.getName() + "." + argument.getIndex());
+ } else {
+ throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
+ }
+ } else {
+ // multiple callbacks in the method
+ for (int j = 0; j < argtypes.length; j++) {
+ Class> argclazz = argtypes[j];
+ if (argclazz.getName().equals(argument.getType())) {
+ appendParameters(map, argument, method.getName() + "." + j);
+ if (argument.getIndex() != -1 && argument.getIndex() != j) {
+ throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (argument.getIndex() != -1) {
+ appendParameters(map, argument, method.getName() + "." + argument.getIndex());
+ } else {
+ throw new IllegalArgumentException("Argument config must set index or type attribute.eg: or ");
+ }
+
+ }
+ }
+ } // end of methods for
+ }
+
+ if (ProtocolUtils.isGeneric(generic)) {
+ map.put(GENERIC_KEY, generic);
+ map.put(METHODS_KEY, ANY_VALUE);
+ } else {
+ String revision = Version.getVersion(interfaceClass, version);
+ if (revision != null && revision.length() > 0) {
+ map.put(REVISION_KEY, revision);
+ }
+
+ String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
+ if (methods.length == 0) {
+ logger.warn("No method found in service interface " + interfaceClass.getName());
+ map.put(METHODS_KEY, ANY_VALUE);
+ } else {
+ map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), ","));
+ }
+ }
+ if (!ConfigUtils.isEmpty(token)) {
+ if (ConfigUtils.isDefault(token)) {
+ map.put(TOKEN_KEY, UUID.randomUUID().toString());
+ } else {
+ map.put(TOKEN_KEY, token);
+ }
+ }
+ // export service
+ String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
+ Integer port = this.findConfigedPorts(protocolConfig, name, map);
+ URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
+
+ if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
+ .hasExtension(url.getProtocol())) {
+ url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
+ .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
+ }
+
+ String scope = url.getParameter(SCOPE_KEY);
+ // don't export when none is configured
+ if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
+
+ // export to local if the config is not remote (export to remote only when config is remote)
+ if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
+ exportLocal(url);
+ }
+ // export to remote if the config is not local (export to local only when config is local)
+ if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
+ if (!isOnlyInJvm() && logger.isInfoEnabled()) {
+ logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
+ }
+ if (CollectionUtils.isNotEmpty(registryURLs)) {
+ for (URL registryURL : registryURLs) {
+ //if protocol is only injvm ,not register
+ if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
+ continue;
+ }
+ url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
+ URL monitorUrl = loadMonitor(registryURL);
+ if (monitorUrl != null) {
+ url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
+ }
+ if (logger.isInfoEnabled()) {
+ logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
+ }
+
+ // For providers, this is used to enable custom proxy to generate invoker
+ String proxy = url.getParameter(PROXY_KEY);
+ if (StringUtils.isNotEmpty(proxy)) {
+ registryURL = registryURL.addParameter(PROXY_KEY, proxy);
+ }
+
+ Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
+ DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
+
+ Exporter> exporter = protocol.export(wrapperInvoker);
+ exporters.add(exporter);
+ }
+ } else {
+ Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
+ DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
+
+ Exporter> exporter = protocol.export(wrapperInvoker);
+ exporters.add(exporter);
+ }
+ /**
+ * @since 2.7.0
+ * ServiceData Store
+ */
+ MetadataReportService metadataReportService = null;
+ if ((metadataReportService = getMetadataReportService()) != null) {
+ metadataReportService.publishProvider(url);
+ }
+ }
+ }
+ this.urls.add(url);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ /**
+ * always export injvm
+ */
+ private void exportLocal(URL url) {
+ URL local = URLBuilder.from(url)
+ .setProtocol(LOCAL_PROTOCOL)
+ .setHost(LOCALHOST_VALUE)
+ .setPort(0)
+ .build();
+ Exporter> exporter = protocol.export(
+ proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
+ exporters.add(exporter);
+ logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
+ }
+
+ private Optional getContextPath(ProtocolConfig protocolConfig) {
+ String contextPath = protocolConfig.getContextpath();
+ if (StringUtils.isEmpty(contextPath) && provider != null) {
+ contextPath = provider.getContextpath();
+ }
+ return Optional.ofNullable(contextPath);
+ }
+
+ protected Class getServiceClass(T ref) {
+ return ref.getClass();
+ }
+
+ /**
+ * Register & bind IP address for service provider, can be configured separately.
+ * Configuration priority: environment variables -> java system properties -> host property in config file ->
+ * /etc/hosts -> default network address -> first available network address
+ *
+ * @param protocolConfig
+ * @param registryURLs
+ * @param map
+ * @return
+ */
+ private String findConfigedHosts(ProtocolConfig protocolConfig, List registryURLs, Map map) {
+ boolean anyhost = false;
+
+ String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND);
+ if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) {
+ throw new IllegalArgumentException("Specified invalid bind ip from property:" + DUBBO_IP_TO_BIND + ", value:" + hostToBind);
+ }
+
+ // if bind ip is not found in environment, keep looking up
+ if (StringUtils.isEmpty(hostToBind)) {
+ hostToBind = protocolConfig.getHost();
+ if (provider != null && StringUtils.isEmpty(hostToBind)) {
+ hostToBind = provider.getHost();
+ }
+ if (isInvalidLocalHost(hostToBind)) {
+ anyhost = true;
+ try {
+ hostToBind = InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException e) {
+ logger.warn(e.getMessage(), e);
+ }
+ if (isInvalidLocalHost(hostToBind)) {
+ if (CollectionUtils.isNotEmpty(registryURLs)) {
+ for (URL registryURL : registryURLs) {
+ if (MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) {
+ // skip multicast registry since we cannot connect to it via Socket
+ continue;
+ }
+ try (Socket socket = new Socket()) {
+ SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
+ socket.connect(addr, 1000);
+ hostToBind = socket.getLocalAddress().getHostAddress();
+ break;
+ } catch (Exception e) {
+ logger.warn(e.getMessage(), e);
+ }
+ }
+ }
+ if (isInvalidLocalHost(hostToBind)) {
+ hostToBind = getLocalHost();
+ }
+ }
+ }
+ }
+
+ map.put(Constants.BIND_IP_KEY, hostToBind);
+
+ // registry ip is not used for bind ip by default
+ String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY);
+ if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) {
+ throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
+ } else if (StringUtils.isEmpty(hostToRegistry)) {
+ // bind ip is used as registry ip by default
+ hostToRegistry = hostToBind;
+ }
+
+ map.put(ANYHOST_KEY, String.valueOf(anyhost));
+
+ return hostToRegistry;
+ }
+
+ /**
+ * Register port and bind port for the provider, can be configured separately
+ * Configuration priority: environment variable -> java system properties -> port property in protocol config file
+ * -> protocol default port
+ *
+ * @param protocolConfig
+ * @param name
+ * @return
+ */
+ private Integer findConfigedPorts(ProtocolConfig protocolConfig, String name, Map map) {
+ Integer portToBind = null;
+
+ // parse bind port from environment
+ String port = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_BIND);
+ portToBind = parsePort(port);
+
+ // if there's no bind port found from environment, keep looking up.
+ if (portToBind == null) {
+ portToBind = protocolConfig.getPort();
+ if (provider != null && (portToBind == null || portToBind == 0)) {
+ portToBind = provider.getPort();
+ }
+ final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
+ if (portToBind == null || portToBind == 0) {
+ portToBind = defaultPort;
+ }
+ if (portToBind == null || portToBind <= 0) {
+ portToBind = getRandomPort(name);
+ if (portToBind == null || portToBind < 0) {
+ portToBind = getAvailablePort(defaultPort);
+ putRandomPort(name, portToBind);
+ }
+ }
+ }
+
+ // save bind port, used as url's key later
+ map.put(Constants.BIND_PORT_KEY, String.valueOf(portToBind));
+
+ // registry port, not used as bind port by default
+ String portToRegistryStr = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_REGISTRY);
+ Integer portToRegistry = parsePort(portToRegistryStr);
+ if (portToRegistry == null) {
+ portToRegistry = portToBind;
+ }
+
+ return portToRegistry;
+ }
+
+ private Integer parsePort(String configPort) {
+ Integer port = null;
+ if (configPort != null && configPort.length() > 0) {
+ try {
+ Integer intPort = Integer.parseInt(configPort);
+ if (isInvalidPort(intPort)) {
+ throw new IllegalArgumentException("Specified invalid port from env value:" + configPort);
+ }
+ port = intPort;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Specified invalid port from env value:" + configPort);
+ }
+ }
+ return port;
+ }
+
+ private String getValueFromConfig(ProtocolConfig protocolConfig, String key) {
+ String protocolPrefix = protocolConfig.getName().toUpperCase() + "_";
+ String port = ConfigUtils.getSystemProperty(protocolPrefix + key);
+ if (StringUtils.isEmpty(port)) {
+ port = ConfigUtils.getSystemProperty(key);
+ }
+ return port;
+ }
+
+ private void completeCompoundConfigs() {
+ if (provider != null) {
+ if (application == null) {
+ setApplication(provider.getApplication());
+ }
+ if (module == null) {
+ setModule(provider.getModule());
+ }
+ if (registries == null) {
+ setRegistries(provider.getRegistries());
+ }
+ if (monitor == null) {
+ setMonitor(provider.getMonitor());
+ }
+ if (protocols == null) {
+ setProtocols(provider.getProtocols());
+ }
+ if (configCenter == null) {
+ setConfigCenter(provider.getConfigCenter());
+ }
+ }
+ if (module != null) {
+ if (registries == null) {
+ setRegistries(module.getRegistries());
+ }
+ if (monitor == null) {
+ setMonitor(module.getMonitor());
+ }
+ }
+ if (application != null) {
+ if (registries == null) {
+ setRegistries(application.getRegistries());
+ }
+ if (monitor == null) {
+ setMonitor(application.getMonitor());
+ }
+ }
+ }
+
+ private void checkDefault() {
+ createProviderIfAbsent();
+ }
+
+ private void createProviderIfAbsent() {
+ if (provider != null) {
+ return;
+ }
+ setProvider(
+ ConfigManager.getInstance()
+ .getDefaultProvider()
+ .orElseGet(() -> {
+ ProviderConfig providerConfig = new ProviderConfig();
+ providerConfig.refresh();
+ return providerConfig;
+ })
+ );
+ }
+
+ private void checkProtocol() {
+ if (CollectionUtils.isEmpty(protocols) && provider != null) {
+ setProtocols(provider.getProtocols());
+ }
+ convertProtocolIdsToProtocols();
+ }
+
+ private void convertProtocolIdsToProtocols() {
+ if (StringUtils.isEmpty(protocolIds) && CollectionUtils.isEmpty(protocols)) {
+ List configedProtocols = new ArrayList<>();
+ configedProtocols.addAll(getSubProperties(Environment.getInstance()
+ .getExternalConfigurationMap(), PROTOCOLS_SUFFIX));
+ configedProtocols.addAll(getSubProperties(Environment.getInstance()
+ .getAppExternalConfigurationMap(), PROTOCOLS_SUFFIX));
+
+ protocolIds = String.join(",", configedProtocols);
+ }
+
+ if (StringUtils.isEmpty(protocolIds)) {
+ if (CollectionUtils.isEmpty(protocols)) {
+ setProtocols(
+ ConfigManager.getInstance().getDefaultProtocols()
+ .filter(CollectionUtils::isNotEmpty)
+ .orElseGet(() -> {
+ ProtocolConfig protocolConfig = new ProtocolConfig();
+ protocolConfig.refresh();
+ return new ArrayList<>(Arrays.asList(protocolConfig));
+ })
+ );
+ }
+ } else {
+ String[] arr = COMMA_SPLIT_PATTERN.split(protocolIds);
+ List tmpProtocols = CollectionUtils.isNotEmpty(protocols) ? protocols : new ArrayList<>();
+ Arrays.stream(arr).forEach(id -> {
+ if (tmpProtocols.stream().noneMatch(prot -> prot.getId().equals(id))) {
+ tmpProtocols.add(ConfigManager.getInstance().getProtocol(id).orElseGet(() -> {
+ ProtocolConfig protocolConfig = new ProtocolConfig();
+ protocolConfig.setId(id);
+ protocolConfig.refresh();
+ return protocolConfig;
+ }));
+ }
+ });
+ if (tmpProtocols.size() > arr.length) {
+ throw new IllegalStateException("Too much protocols found, the protocols comply to this service are :" + protocolIds + " but got " + protocols
+ .size() + " registries!");
+ }
+ setProtocols(tmpProtocols);
+ }
+ }
+
+ public Class> getInterfaceClass() {
+ if (interfaceClass != null) {
+ return interfaceClass;
+ }
+ if (ref instanceof GenericService) {
+ return GenericService.class;
+ }
+ try {
+ if (interfaceName != null && interfaceName.length() > 0) {
+ this.interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
+ .getContextClassLoader());
+ }
+ } catch (ClassNotFoundException t) {
+ throw new IllegalStateException(t.getMessage(), t);
+ }
+ return interfaceClass;
+ }
+
+ /**
+ * @param interfaceClass
+ * @see #setInterface(Class)
+ * @deprecated
+ */
+ public void setInterfaceClass(Class> interfaceClass) {
+ setInterface(interfaceClass);
+ }
+
+ public String getInterface() {
+ return interfaceName;
+ }
+
+ public void setInterface(Class> interfaceClass) {
+ if (interfaceClass != null && !interfaceClass.isInterface()) {
+ throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!");
+ }
+ this.interfaceClass = interfaceClass;
+ setInterface(interfaceClass == null ? null : interfaceClass.getName());
+ }
+
+ public void setInterface(String interfaceName) {
+ this.interfaceName = interfaceName;
+ if (StringUtils.isEmpty(id)) {
+ id = interfaceName;
+ }
+ }
+
+ public T getRef() {
+ return ref;
+ }
+
+ public void setRef(T ref) {
+ this.ref = ref;
+ }
+
+ @Parameter(excluded = true)
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ checkPathName(PATH_KEY, path);
+ this.path = path;
+ }
+
+ public List getMethods() {
+ return methods;
+ }
+
+ // ======== Deprecated ========
+
+ @SuppressWarnings("unchecked")
+ public void setMethods(List extends MethodConfig> methods) {
+ this.methods = (List) methods;
+ }
+
+ public ProviderConfig getProvider() {
+ return provider;
+ }
+
+ public void setProvider(ProviderConfig provider) {
+ ConfigManager.getInstance().addProvider(provider);
+ this.provider = provider;
+ }
+
+ @Parameter(excluded = true)
+ public String getProviderIds() {
+ return providerIds;
+ }
+
+ public void setProviderIds(String providerIds) {
+ this.providerIds = providerIds;
+ }
+
+ public String getGeneric() {
+ return generic;
+ }
+
+ public void setGeneric(String generic) {
+ if (StringUtils.isEmpty(generic)) {
+ return;
+ }
+ if (ProtocolUtils.isGeneric(generic)) {
+ this.generic = generic;
+ } else {
+ throw new IllegalArgumentException("Unsupported generic type " + generic);
+ }
+ }
+
+ @Override
+ public void setMock(Boolean mock) {
+ throw new IllegalArgumentException("mock doesn't support on provider side");
+ }
+
+ @Override
+ public void setMock(String mock) {
+ throw new IllegalArgumentException("mock doesn't support on provider side");
+ }
+
+ public List getExportedUrls() {
+ return urls;
+ }
+
+ /**
+ * @deprecated Replace to getProtocols()
+ */
+ @Deprecated
+ public List getProviders() {
+ return convertProtocolToProvider(protocols);
+ }
+
+ /**
+ * @deprecated Replace to setProtocols()
+ */
+ @Deprecated
+ public void setProviders(List providers) {
+ this.protocols = convertProviderToProtocol(providers);
+ }
+
+ @Override
+ @Parameter(excluded = true)
+ public String getPrefix() {
+ return DUBBO + ".service." + interfaceName;
+ }
+
+ /**
+ * Dispatch an {@link Event event}
+ *
+ * @param event an {@link Event event}
+ * @since 2.7.2
+ */
+ protected void dispatch(Event event) {
+ eventDispatcher.dispatch(event);
+ }
+}
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigDestroyedEvent.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigDestroyedEvent.java
new file mode 100644
index 00000000000..58b871ea7e9
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigDestroyedEvent.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.event;
+
+import org.apache.dubbo.config.ReferenceConfig;
+import org.apache.dubbo.config.annotation.Reference;
+import org.apache.dubbo.event.Event;
+
+/**
+ * The {@link ReferenceConfig Dubbo service ReferenceConfig} destroyed {@link Event event}
+ *
+ * @see Reference
+ * @see ReferenceConfig#destroy()
+ * @see Event
+ * @since 2.7.2
+ */
+public class ReferenceConfigDestroyedEvent extends Event {
+
+ public ReferenceConfigDestroyedEvent(ReferenceConfig referenceConfig) {
+ super(referenceConfig);
+ }
+
+ public ReferenceConfig getReferenceConfig() {
+ return (ReferenceConfig) getSource();
+ }
+
+}
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigInitializedEvent.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigInitializedEvent.java
new file mode 100644
index 00000000000..dcf02cfec77
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigInitializedEvent.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.event;
+
+import org.apache.dubbo.config.ReferenceConfig;
+import org.apache.dubbo.config.annotation.Reference;
+import org.apache.dubbo.event.Event;
+import org.apache.dubbo.rpc.Invoker;
+
+/**
+ * The {@link ReferenceConfig Dubbo service ReferenceConfig} initialized {@link Event event}
+ *
+ * @see Reference
+ * @see ReferenceConfig#get()
+ * @see Event
+ * @since 2.7.2
+ */
+public class ReferenceConfigInitializedEvent extends Event {
+
+ private final Invoker> invoker;
+
+ public ReferenceConfigInitializedEvent(ReferenceConfig referenceConfig, Invoker> invoker) {
+ super(referenceConfig);
+ this.invoker = invoker;
+ }
+
+ public ReferenceConfig getReferenceConfig() {
+ return (ReferenceConfig) getSource();
+ }
+
+ public Invoker> getInvoker() {
+ return invoker;
+ }
+}
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigExportedEvent.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigExportedEvent.java
new file mode 100644
index 00000000000..e6e9c09a108
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigExportedEvent.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.event;
+
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.event.Event;
+
+/**
+ * {@link ServiceConfig} event post-{@link ServiceConfig#export() export}
+ *
+ * @since 2.7.2
+ */
+public class ServiceConfigExportedEvent extends Event {
+
+ public ServiceConfigExportedEvent(ServiceConfig source) {
+ super(source);
+ }
+
+ public ServiceConfig getServiceConfig() {
+ return (ServiceConfig) getSource();
+ }
+}
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigUnexportedEvent.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigUnexportedEvent.java
new file mode 100644
index 00000000000..f4914fe8034
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigUnexportedEvent.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.event;
+
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.event.Event;
+
+/**
+ * {@link ServiceConfig} event post-{@link ServiceConfig#unexport() unexport}
+ *
+ * @since 2.7.2
+ */
+public class ServiceConfigUnexportedEvent extends Event {
+
+ public ServiceConfigUnexportedEvent(ServiceConfig source) {
+ super(source);
+ }
+
+ public ServiceConfig getServiceConfig() {
+ return (ServiceConfig) getSource();
+ }
+}
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
new file mode 100644
index 00000000000..d3e4226767a
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.metadata;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.config.AbstractConfig;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.metadata.LocalMetadataService;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.MetadataServiceExporter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link MetadataServiceExporter} implementation based on {@link AbstractConfig Dubbo configurations}, the clients
+ * should make sure the {@link ApplicationConfig}, {@link RegistryConfig} and {@link ProtocolConfig} are ready before
+ * {@link #export()}.
+ *
+ * Typically, do not worry about their ready status, because they are initialized before
+ * any {@link ServiceConfig} exports, or The Dubbo export will be failed.
+ *
+ * @see MetadataServiceExporter
+ * @see ServiceConfig
+ * @see ConfigManager
+ * @since 2.7.2
+ */
+public class ConfigurableMetadataServiceExporter implements MetadataServiceExporter {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ /**
+ * {@link ConfigManager} stores {@link AbstractConfig the Dubbo *Config instances}
+ */
+ private final ConfigManager configManager = ConfigManager.getInstance();
+
+ private volatile ServiceConfig serviceConfig;
+
+ @Override
+ public List export() {
+
+ if (!isExported()) {
+
+ LocalMetadataService metadataService = LocalMetadataService.getDefaultExtension();
+
+ ServiceConfig serviceConfig = new ServiceConfig<>();
+ serviceConfig.setApplication(getApplicationConfig());
+ serviceConfig.setRegistries(getRegistries());
+ serviceConfig.setProtocols(getProtocols());
+ serviceConfig.setInterface(MetadataService.class);
+ serviceConfig.setRef(metadataService);
+ serviceConfig.setGroup(getApplicationConfig().getName());
+ serviceConfig.setVersion(metadataService.version());
+
+ // export
+ serviceConfig.export();
+
+ if (logger.isInfoEnabled()) {
+ logger.info("The MetadataService exports urls : " + serviceConfig.getExportedUrls());
+ }
+
+ this.serviceConfig = serviceConfig;
+ } else {
+ if (logger.isWarnEnabled()) {
+ logger.warn("The MetadataService has been exported : " + serviceConfig.getExportedUrls());
+ }
+ }
+ return serviceConfig.getExportedUrls();
+ }
+
+ @Override
+ public void unexport() {
+ if (isExported()) {
+ serviceConfig.unexport();
+ }
+ }
+
+ private List list(Map, T> map) {
+ return new ArrayList<>(map.values());
+ }
+
+ private List extends ProtocolConfig> getProtocols() {
+ return list(configManager.getProtocols());
+ }
+
+ private List extends RegistryConfig> getRegistries() {
+ return list(configManager.getRegistries());
+ }
+
+ private ApplicationConfig getApplicationConfig() {
+ return configManager.getApplication().get();
+ }
+
+ private boolean isExported() {
+ return serviceConfig != null && serviceConfig.isExported();
+ }
+}
diff --git a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
new file mode 100644
index 00000000000..30626fd41fc
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
@@ -0,0 +1 @@
+default=org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
index d1e02636dd7..f9afb355945 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
@@ -21,20 +21,30 @@
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.config.api.DemoService;
import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.config.event.ReferenceConfigDestroyedEvent;
+import org.apache.dubbo.config.event.ReferenceConfigInitializedEvent;
import org.apache.dubbo.config.provider.impl.DemoServiceImpl;
+import org.apache.dubbo.event.EventDispatcher;
+import org.apache.dubbo.event.EventListener;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import java.util.concurrent.atomic.AtomicReference;
+
import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
+import static org.junit.jupiter.api.Assertions.assertEquals;
public class ReferenceConfigTest {
+ private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension();
+
@BeforeEach
public void setUp() {
ConfigManager.getInstance().clear();
+ eventDispatcher.removeAllEventListeners();
}
@AfterEach
@@ -67,12 +77,38 @@ public void testInjvm() throws Exception {
rc.setInterface(DemoService.class.getName());
rc.setInjvm(false);
+ AtomicReference reference = new AtomicReference<>();
+
+ eventDispatcher.addEventListener(new EventListener() {
+ @Override
+ public void onEvent(ReferenceConfigInitializedEvent event) {
+ reference.set(event.getReferenceConfig());
+ }
+ });
+
try {
System.setProperty("java.net.preferIPv4Stack", "true");
demoService.export();
rc.get();
+
+ assertEquals(rc, reference.get());
+
+ reference.compareAndSet(rc, null);
+
Assertions.assertTrue(!LOCAL_PROTOCOL.equalsIgnoreCase(
rc.getInvoker().getUrl().getProtocol()));
+
+ eventDispatcher.addEventListener(new EventListener() {
+ @Override
+ public void onEvent(ReferenceConfigDestroyedEvent event) {
+ reference.set(event.getReferenceConfig());
+ }
+ });
+
+ rc.destroy();
+
+ assertEquals(rc, reference.get());
+
} finally {
System.clearProperty("java.net.preferIPv4Stack");
demoService.unexport();
@@ -134,17 +170,17 @@ public void testConstructWithReferenceAnnotation() throws NoSuchFieldException {
Reference reference = getClass().getDeclaredField("innerTest").getAnnotation(Reference.class);
ReferenceConfig referenceConfig = new ReferenceConfig(reference);
Assertions.assertTrue(referenceConfig.getMethods().size() == 1);
- Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getName(), "sayHello");
+ assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getName(), "sayHello");
Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).getTimeout() == 1300);
Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).getRetries() == 4);
- Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getLoadbalance(), "random");
+ assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getLoadbalance(), "random");
Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).getActives() == 3);
Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).getExecutes() == 5);
Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).isAsync());
- Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOninvoke(), "i");
- Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOnreturn(), "r");
- Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOnthrow(), "t");
- Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getCache(), "c");
+ assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOninvoke(), "i");
+ assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOnreturn(), "r");
+ assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOnthrow(), "t");
+ assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getCache(), "c");
}
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
index 147648b67fb..f45727dd325 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
@@ -21,10 +21,14 @@
import org.apache.dubbo.config.api.DemoService;
import org.apache.dubbo.config.api.Greeting;
import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.config.event.ServiceConfigExportedEvent;
+import org.apache.dubbo.config.event.ServiceConfigUnexportedEvent;
import org.apache.dubbo.config.mock.MockProtocol2;
import org.apache.dubbo.config.mock.MockRegistryFactory2;
import org.apache.dubbo.config.mock.TestProxyFactory;
import org.apache.dubbo.config.provider.impl.DemoServiceImpl;
+import org.apache.dubbo.event.EventDispatcher;
+import org.apache.dubbo.event.EventListener;
import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invoker;
@@ -40,6 +44,7 @@
import java.util.Collections;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
@@ -48,8 +53,9 @@
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY;
-import static org.apache.dubbo.config.Constants.SHUTDOWN_TIMEOUT_KEY;
import static org.apache.dubbo.common.constants.ConfigConstants.SHUTDOWN_WAIT_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.EXPORT_KEY;
+import static org.apache.dubbo.config.Constants.SHUTDOWN_TIMEOUT_KEY;
import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY;
import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
@@ -75,6 +81,8 @@ public class ServiceConfigTest {
private ServiceConfig service2 = new ServiceConfig();
private ServiceConfig delayService = new ServiceConfig();
+ private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension();
+
@BeforeEach
public void setUp() throws Exception {
MockProtocol2.delegate = protocolDelegate;
@@ -135,8 +143,20 @@ public void tearDown() {
@Test
public void testExport() throws Exception {
+
+ AtomicReference reference = new AtomicReference();
+
+ eventDispatcher.addEventListener(new EventListener() {
+ @Override
+ public void onEvent(ServiceConfigExportedEvent event) {
+ reference.set(event.getServiceConfig());
+ }
+ });
+
service.export();
+ assertEquals(service, reference.get());
+
assertThat(service.getExportedUrls(), hasSize(1));
URL url = service.toUrl();
assertThat(url.getProtocol(), equalTo("mockprotocol2"));
@@ -178,8 +198,32 @@ public void testDelayExport() throws Exception {
public void testUnexport() throws Exception {
System.setProperty(SHUTDOWN_WAIT_KEY, "0");
try {
+ AtomicReference reference = new AtomicReference();
+
+ eventDispatcher.addEventListener(new EventListener() {
+ @Override
+ public void onEvent(ServiceConfigExportedEvent event) {
+ reference.set(event.getServiceConfig());
+ }
+ });
+
service.export();
+
+ assertEquals(service, reference.get());
+
+ assertTrue(reference.compareAndSet(service, null));
+
+ eventDispatcher.addEventListener(new EventListener() {
+ @Override
+ public void onEvent(ServiceConfigUnexportedEvent event) {
+ reference.set(event.getServiceConfig());
+ }
+ });
+
service.unexport();
+
+ assertEquals(service, reference.get());
+
Thread.sleep(1000);
Mockito.verify(exporter, Mockito.atLeastOnce()).unexport();
} finally {
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporterTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporterTest.java
new file mode 100644
index 00000000000..48f3f38c068
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporterTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.metadata;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.MetadataServiceExporter;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link ConfigurableMetadataServiceExporter} Test
+ *
+ * @since 2.7.2
+ */
+public class ConfigurableMetadataServiceExporterTest {
+
+ @BeforeAll
+ public static void init() {
+ ConfigManager configManager = ConfigManager.getInstance();
+ ApplicationConfig applicationConfig = new ApplicationConfig();
+ applicationConfig.setName("test");
+ configManager.setApplication(applicationConfig);
+
+ // Add ProtocolConfig
+ configManager.addProtocol(protocolConfig());
+ // Add RegistryConfig
+ configManager.addRegistry(registryConfig());
+ }
+
+ private static ProtocolConfig protocolConfig() {
+ ProtocolConfig protocolConfig = new ProtocolConfig();
+ protocolConfig.setName("mockprotocol");
+ protocolConfig.setPort(20880);
+ return protocolConfig;
+ }
+
+ private static RegistryConfig registryConfig() {
+ RegistryConfig registryConfig = new RegistryConfig();
+ registryConfig.setAddress("mockregistry://127.0.0.1");
+ return registryConfig;
+ }
+
+ @Test
+ public void testExportAndUnexport() {
+ MetadataServiceExporter exporter = new ConfigurableMetadataServiceExporter();
+ List urls = exporter.export();
+
+ assertEquals(1, urls.size());
+
+ URL url = urls.get(0);
+
+ assertEquals("test", url.getParameter(APPLICATION_KEY));
+ assertEquals(MetadataService.class.getName(), url.getServiceInterface());
+ assertEquals("test", url.getParameter(GROUP_KEY));
+ assertEquals(MetadataService.VERSION, url.getParameter(VERSION_KEY));
+ assertEquals("mockprotocol", url.getProtocol());
+
+ exporter.unexport();
+ }
+
+}
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/AnnotationBeanNameBuilder.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/AnnotationBeanNameBuilder.java
deleted file mode 100644
index 6e18e2a3471..00000000000
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/AnnotationBeanNameBuilder.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.config.spring.beans.factory.annotation;
-
-import org.apache.dubbo.common.constants.CommonConstants;
-import org.apache.dubbo.config.annotation.Reference;
-import org.apache.dubbo.config.annotation.Service;
-import org.apache.dubbo.registry.Registry;
-
-import org.springframework.core.env.Environment;
-
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_PROTOCOL;
-import static org.apache.dubbo.common.constants.RegistryConstants.CONSUMERS_CATEGORY;
-import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATEGORY;
-import static org.apache.dubbo.config.spring.util.AnnotationUtils.resolveInterfaceName;
-import static org.springframework.util.StringUtils.arrayToCommaDelimitedString;
-import static org.springframework.util.StringUtils.hasText;
-
-/**
- * The Bean Name Builder for the annotations {@link Service} and {@link Reference}
- *
- * The naming rule is consistent with the the implementation {@link Registry} that is based on the service-name aware
- * infrastructure, e.g Spring Cloud, Cloud Native and so on.
- *
- * The pattern of bean name : ${category}:${protocol}:${serviceInterface}:${version}:${group}.
- *
- * ${version} and ${group} are optional.
- *
- * @since 2.6.6
- */
-class AnnotationBeanNameBuilder {
-
- private static final String SEPARATOR = ":";
-
- // Required properties
-
- private final String category;
-
- private final String protocol;
-
- private final String interfaceClassName;
-
- // Optional properties
-
- private String version;
-
- private String group;
-
- private Environment environment;
-
- private AnnotationBeanNameBuilder(String category, String protocol, String interfaceClassName) {
- this.category = category;
- this.protocol = protocol;
- this.interfaceClassName = interfaceClassName;
- }
-
- private AnnotationBeanNameBuilder(Service service, Class> interfaceClass) {
- this(PROVIDERS_CATEGORY, resolveProtocol(service.protocol()), resolveInterfaceName(service, interfaceClass));
- this.group(service.group());
- this.version(service.version());
- }
-
- private AnnotationBeanNameBuilder(Reference reference, Class> interfaceClass) {
- this(CONSUMERS_CATEGORY, resolveProtocol(reference.protocol()), resolveInterfaceName(reference, interfaceClass));
- this.group(reference.group());
- this.version(reference.version());
- }
-
- public static AnnotationBeanNameBuilder create(Service service, Class> interfaceClass) {
- return new AnnotationBeanNameBuilder(service, interfaceClass);
- }
-
- public static AnnotationBeanNameBuilder create(Reference reference, Class> interfaceClass) {
- return new AnnotationBeanNameBuilder(reference, interfaceClass);
- }
-
- private static void append(StringBuilder builder, String value) {
- if (hasText(value)) {
- builder.append(SEPARATOR).append(value);
- }
- }
-
- public AnnotationBeanNameBuilder group(String group) {
- this.group = group;
- return this;
- }
-
- public AnnotationBeanNameBuilder version(String version) {
- this.version = version;
- return this;
- }
-
- public AnnotationBeanNameBuilder environment(Environment environment) {
- this.environment = environment;
- return this;
- }
-
- /**
- * Resolve the protocol
- *
- * @param protocols one or more protocols
- * @return if protocols
== null
, it will return
- * {@link CommonConstants#DEFAULT_PROTOCOL "dubbo"} as the default protocol
- * @see CommonConstants#DEFAULT_PROTOCOL
- */
- private static String resolveProtocol(String... protocols) {
- String protocol = arrayToCommaDelimitedString(protocols);
- return hasText(protocol) ? protocol : DEFAULT_PROTOCOL;
- }
-
- /**
- * Build bean name while resolve the placeholders if possible.
- *
- * @return pattern : ${category}:${protocol}:${serviceInterface}:${version}:${group}
- */
- public String build() {
- // Append the required properties
- StringBuilder beanNameBuilder = new StringBuilder(category);
- append(beanNameBuilder, protocol);
- append(beanNameBuilder, interfaceClassName);
- // Append the optional properties
- append(beanNameBuilder, version);
- append(beanNameBuilder, group);
- String beanName = beanNameBuilder.toString();
- // Resolve placeholders
- return environment != null ? environment.resolvePlaceholders(beanName) : beanName;
- }
-}
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java
index 7c3a6b9b87d..b40948de95a 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java
@@ -41,6 +41,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import static org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilder.create;
+
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
* that Consumer service {@link Reference} annotated fields
@@ -178,16 +180,14 @@ protected String buildInjectedObjectCacheKey(Reference reference, Object bean, S
return buildReferencedBeanName(reference, injectedType) +
"#source=" + (injectedElement.getMember()) +
- "#attributes=" + AnnotationUtils.getAttributes(reference,getEnvironment(),true);
+ "#attributes=" + AnnotationUtils.getAttributes(reference, getEnvironment(), true);
}
private String buildReferencedBeanName(Reference reference, Class> injectedType) {
- AnnotationBeanNameBuilder builder = AnnotationBeanNameBuilder.create(reference, injectedType);
-
- builder.environment(getEnvironment());
+ ServiceBeanNameBuilder serviceBeanNameBuilder = create(reference, injectedType, getEnvironment());
- return getEnvironment().resolvePlaceholders(builder.build());
+ return serviceBeanNameBuilder.build();
}
private ReferenceBean buildReferenceBeanIfAbsent(String referencedBeanName, Reference reference,
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessor.java
index 305e3724132..e12db85fa7b 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessor.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessor.java
@@ -60,6 +60,7 @@
import java.util.Map;
import java.util.Set;
+import static org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilder.create;
import static org.apache.dubbo.config.spring.util.ObjectUtils.of;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
import static org.springframework.context.annotation.AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR;
@@ -259,7 +260,7 @@ private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, Bean
buildServiceBeanDefinition(service, interfaceClass, annotatedServiceBeanName);
// ServiceBean Bean name
- String beanName = generateServiceBeanName(service, interfaceClass, annotatedServiceBeanName);
+ String beanName = generateServiceBeanName(service, interfaceClass);
if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean
registry.registerBeanDefinition(beanName, serviceBeanDefinition);
@@ -285,19 +286,14 @@ private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, Bean
* Generates the bean name of {@link ServiceBean}
*
* @param service
- * @param interfaceClass the class of interface annotated {@link Service}
- * @param annotatedServiceBeanName the bean name of annotated {@link Service}
+ * @param interfaceClass the class of interface annotated {@link Service}
* @return ServiceBean@interfaceClassName#annotatedServiceBeanName
* @since 2.5.9
*/
- private String generateServiceBeanName(Service service, Class> interfaceClass, String annotatedServiceBeanName) {
-
- AnnotationBeanNameBuilder builder = AnnotationBeanNameBuilder.create(service, interfaceClass);
-
- builder.environment(environment);
+ private String generateServiceBeanName(Service service, Class> interfaceClass) {
+ ServiceBeanNameBuilder builder = create(service, interfaceClass, environment);
return builder.build();
-
}
private Class> resolveServiceInterfaceClass(Class> annotatedServiceBeanClass, Service service) {
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilder.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilder.java
new file mode 100644
index 00000000000..5d272515ab3
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilder.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.spring.beans.factory.annotation;
+
+import org.apache.dubbo.config.annotation.Reference;
+import org.apache.dubbo.config.annotation.Service;
+import org.apache.dubbo.config.spring.ReferenceBean;
+import org.apache.dubbo.config.spring.ServiceBean;
+
+import org.springframework.core.env.Environment;
+import org.springframework.util.StringUtils;
+
+import static org.apache.dubbo.config.spring.util.AnnotationUtils.resolveInterfaceName;
+
+/**
+ * Dubbo {@link Service @Service} Bean Builder
+ *
+ * @see Service
+ * @see Reference
+ * @see ServiceBean
+ * @see ReferenceBean
+ * @since 2.6.5
+ */
+public class ServiceBeanNameBuilder {
+
+ private static final String SEPARATOR = ":";
+
+ private final String interfaceClassName;
+
+ private final Environment environment;
+
+ // Optional
+ private String version;
+
+ private String group;
+
+ private ServiceBeanNameBuilder(String interfaceClassName, Environment environment) {
+ this.interfaceClassName = interfaceClassName;
+ this.environment = environment;
+ }
+
+ private ServiceBeanNameBuilder(Class> interfaceClass, Environment environment) {
+ this(interfaceClass.getName(), environment);
+ }
+
+ private ServiceBeanNameBuilder(Service service, Class> interfaceClass, Environment environment) {
+ this(resolveInterfaceName(service, interfaceClass), environment);
+ this.group(service.group());
+ this.version(service.version());
+ }
+
+ private ServiceBeanNameBuilder(Reference reference, Class> interfaceClass, Environment environment) {
+ this(resolveInterfaceName(reference, interfaceClass), environment);
+ this.group(reference.group());
+ this.version(reference.version());
+ }
+
+ public static ServiceBeanNameBuilder create(Class> interfaceClass, Environment environment) {
+ return new ServiceBeanNameBuilder(interfaceClass, environment);
+ }
+
+ public static ServiceBeanNameBuilder create(Service service, Class> interfaceClass, Environment environment) {
+ return new ServiceBeanNameBuilder(service, interfaceClass, environment);
+ }
+
+ public static ServiceBeanNameBuilder create(Reference reference, Class> interfaceClass, Environment environment) {
+ return new ServiceBeanNameBuilder(reference, interfaceClass, environment);
+ }
+
+ private static void append(StringBuilder builder, String value) {
+ if (StringUtils.hasText(value)) {
+ builder.append(value).append(SEPARATOR);
+ }
+ }
+
+ public ServiceBeanNameBuilder group(String group) {
+ this.group = group;
+ return this;
+ }
+
+ public ServiceBeanNameBuilder version(String version) {
+ this.version = version;
+ return this;
+ }
+
+ public String build() {
+ StringBuilder beanNameBuilder = new StringBuilder("ServiceBean").append(SEPARATOR);
+ // Required
+ append(beanNameBuilder, interfaceClassName);
+ // Optional
+ append(beanNameBuilder, version);
+ append(beanNameBuilder, group);
+ // Build and remove last ":"
+ String rawBeanName = beanNameBuilder.substring(0, beanNameBuilder.length() - 1);
+ // Resolve placeholders
+ return environment.resolvePlaceholders(rawBeanName);
+ }
+}
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/AnnotationBeanNameBuilderTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilderTest.java
similarity index 67%
rename from dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/AnnotationBeanNameBuilderTest.java
rename to dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilderTest.java
index 2e79109ab33..747fc1e2693 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/AnnotationBeanNameBuilderTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceBeanNameBuilderTest.java
@@ -27,18 +27,18 @@
import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ReflectionUtils;
-import static org.apache.dubbo.config.spring.beans.factory.annotation.AnnotationBeanNameBuilderTest.GROUP;
-import static org.apache.dubbo.config.spring.beans.factory.annotation.AnnotationBeanNameBuilderTest.VERSION;
+import static org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilderTest.GROUP;
+import static org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilderTest.VERSION;
/**
- * {@link AnnotationBeanNameBuilder} Test
+ * {@link ServiceBeanNameBuilder} Test
*
- * @see AnnotationBeanNameBuilder
+ * @see ServiceBeanNameBuilder
* @since 2.6.6
*/
@Service(interfaceClass = DemoService.class, group = GROUP, version = VERSION,
application = "application", module = "module", registry = {"1", "2", "3"})
-public class AnnotationBeanNameBuilderTest {
+public class ServiceBeanNameBuilderTest {
@Reference(interfaceClass = DemoService.class, group = "DUBBO", version = "${dubbo.version}",
application = "application", module = "module", registry = {"1", "2", "3"})
@@ -58,25 +58,20 @@ public void prepare() {
@Test
public void testServiceAnnotation() {
- Service service = AnnotationUtils.getAnnotation(AnnotationBeanNameBuilderTest.class, Service.class);
- AnnotationBeanNameBuilder builder = AnnotationBeanNameBuilder.create(service, INTERFACE_CLASS);
- Assert.assertEquals("providers:dubbo:org.apache.dubbo.config.spring.api.DemoService:1.0.0:DUBBO",
+ Service service = AnnotationUtils.getAnnotation(ServiceBeanNameBuilderTest.class, Service.class);
+ ServiceBeanNameBuilder builder = ServiceBeanNameBuilder.create(service, INTERFACE_CLASS, environment);
+ Assert.assertEquals("ServiceBean:org.apache.dubbo.config.spring.api.DemoService:1.0.0:DUBBO",
builder.build());
- builder.environment(environment);
- Assert.assertEquals("providers:dubbo:org.apache.dubbo.config.spring.api.DemoService:1.0.0:DUBBO",
+ Assert.assertEquals("ServiceBean:org.apache.dubbo.config.spring.api.DemoService:1.0.0:DUBBO",
builder.build());
}
@Test
public void testReferenceAnnotation() {
- Reference reference = AnnotationUtils.getAnnotation(ReflectionUtils.findField(AnnotationBeanNameBuilderTest.class, "INTERFACE_CLASS"), Reference.class);
- AnnotationBeanNameBuilder builder = AnnotationBeanNameBuilder.create(reference, INTERFACE_CLASS);
- Assert.assertEquals("consumers:dubbo:org.apache.dubbo.config.spring.api.DemoService:${dubbo.version}:DUBBO",
- builder.build());
-
- builder.environment(environment);
- Assert.assertEquals("consumers:dubbo:org.apache.dubbo.config.spring.api.DemoService:1.0.0:DUBBO",
+ Reference reference = AnnotationUtils.getAnnotation(ReflectionUtils.findField(ServiceBeanNameBuilderTest.class, "INTERFACE_CLASS"), Reference.class);
+ ServiceBeanNameBuilder builder = ServiceBeanNameBuilder.create(reference, INTERFACE_CLASS, environment);
+ Assert.assertEquals("ServiceBean:org.apache.dubbo.config.spring.api.DemoService:1.0.0:DUBBO",
builder.build());
}
diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java
index 23fc0f5e8bd..0c833af7e82 100644
--- a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java
@@ -19,7 +19,12 @@
import org.apache.dubbo.common.config.Configuration;
import org.apache.dubbo.common.config.Environment;
+import java.util.Collections;
import java.util.Optional;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
@@ -28,12 +33,13 @@
*
* From the use scenario internally in framework, there're mainly three kinds of methods:
*
- * - 1. getConfig, get governance rules or single config item from Config Center.
- * - 2. getConfigFile, get configuration file from Config Center at start up.
- * - 3. addListener/removeListener, add or remove listeners for governance rules or config items that need to watch.
+ * - 1. getConfig, get governance rules or single config item from Config Center.
+ * - 2. getConfigFile, get configuration file from Config Center at start up.
+ * - 3. addListener/removeListener, add or remove listeners for governance rules or config items that need to watch.
*
*/
public interface DynamicConfiguration extends Configuration {
+
String DEFAULT_GROUP = "dubbo";
/**
@@ -113,7 +119,7 @@ default String getConfig(String key, String group) {
/**
* {@see #getConfig(String, String, long)}
- *
+ *
* This method are mostly used to get a compound config file, such as a complete dubbo.properties file.
*/
default String getConfigs(String key, String group) throws IllegalStateException {
@@ -122,11 +128,67 @@ default String getConfigs(String key, String group) throws IllegalStateException
/**
* {@see #getConfig(String, String, long)}
- *
+ *
* This method are mostly used to get a compound config file, such as a complete dubbo.properties file.
*/
String getConfigs(String key, String group, long timeout) throws IllegalStateException;
+ /**
+ * Publish Config mapped to the given key and the given group.
+ *
+ * @param key the key to represent a configuration
+ * @param group the group where the key belongs to
+ * @param content the content of configuration
+ * @return true
if success, or false
+ * @throws UnsupportedOperationException If the under layer does not support
+ * @since 2.7.2
+ */
+ default boolean publishConfig(String key, String group, String content) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("No support");
+ }
+
+ /**
+ * Get the config keys by the specified group
+ *
+ * @param group the specified group
+ * @return the read-only non-null sorted {@link Set set} of config keys
+ * @throws UnsupportedOperationException If the under layer does not support
+ * @since 2.7.2
+ */
+ default SortedSet getConfigKeys(String group) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("No support");
+ }
+
+ /**
+ * Get the {@link SortedMap} with with config keys and contents value by the specified group
+ *
+ * @param group the specified group
+ * @return the read-only non-null sorted {@link SortedMap map}
+ * @throws UnsupportedOperationException If the under layer does not support
+ * @since 2.7.2
+ */
+ default SortedMap getConfigs(String group) throws UnsupportedOperationException {
+ return getConfigs(group, -1);
+ }
+
+ /**
+ * Get the {@link SortedMap} with with config keys and content value by the specified group
+ *
+ * @param group the specified group
+ * @param timeout the millisecond for timeout
+ * @return the read-only non-null sorted {@link SortedMap map}
+ * @throws UnsupportedOperationException If the under layer does not support
+ * @throws IllegalStateException If timeout exceeds
+ * @since 2.7.2
+ */
+ default SortedMap getConfigs(String group, long timeout) throws UnsupportedOperationException,
+ IllegalStateException {
+ SortedMap configs = new TreeMap<>();
+ SortedSet configKeys = getConfigKeys(group);
+ configKeys.forEach(key -> configs.put(key, getConfig(key, group, timeout)));
+ return Collections.unmodifiableSortedMap(configs);
+ }
+
/**
* Find DynamicConfiguration instance
*
diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java
index dbb91fdffee..52c309a437d 100644
--- a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java
@@ -20,6 +20,10 @@
import org.apache.dubbo.configcenter.ConfigurationListener;
import org.apache.dubbo.configcenter.DynamicConfiguration;
+import java.util.SortedSet;
+
+import static java.util.Collections.emptySortedSet;
+
/**
* The default extension of {@link DynamicConfiguration}. If user does not specify a config centre, or specifies one
* that is not a valid extension, it will default to this one.
@@ -30,7 +34,6 @@ public NopDynamicConfiguration(URL url) {
// no-op
}
-
@Override
public Object getInternalProperty(String key) {
return null;
@@ -55,4 +58,20 @@ public String getConfig(String key, String group, long timeout) throws IllegalSt
public String getConfigs(String key, String group, long timeout) throws IllegalStateException {
return null;
}
+
+ /**
+ * @since 2.7.2
+ */
+ @Override
+ public boolean publishConfig(String key, String group, String content) {
+ return true;
+ }
+
+ /**
+ * @since 2.7.2
+ */
+ @Override
+ public SortedSet getConfigKeys(String group) {
+ return emptySortedSet();
+ }
}
diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfigurationTest.java
new file mode 100644
index 00000000000..7ba5bbef917
--- /dev/null
+++ b/dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfigurationTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.configcenter.support.nop;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link NopDynamicConfiguration} Test
+ *
+ * @since 2.7.2
+ */
+public class NopDynamicConfigurationTest {
+
+ private NopDynamicConfiguration configuration = new NopDynamicConfiguration(null);
+
+ @Test
+ public void testGetInternalProperty() {
+ assertNull(configuration.getInternalProperty(null));
+ }
+
+ @Test
+ public void testGetConfig() {
+ assertNull(configuration.getConfig(null, null, -1));
+ }
+
+
+ @Test
+ public void testGetConfigs() {
+ assertNull(configuration.getConfigs(null, null, -1));
+ }
+
+ @Test
+ public void testAddListener() {
+ configuration.addListener(null, null, null);
+ }
+
+ @Test
+ public void testRemoveListener() {
+ configuration.removeListener(null, null, null);
+ }
+
+ @Test
+ public void testPublishConfig() {
+ assertTrue(configuration.publishConfig(null, null, null));
+ }
+
+ @Test
+ public void testGetConfigKeys() {
+ assertTrue(configuration.getConfigKeys(null).isEmpty());
+ }
+
+
+}
diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
index a06537b432c..5b034ca3740 100644
--- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
@@ -27,16 +27,25 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import static java.util.Collections.emptySortedSet;
+import static java.util.Collections.unmodifiableSortedSet;
+import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
import static org.apache.dubbo.configcenter.Constants.CONFIG_NAMESPACE_KEY;
/**
*
*/
public class ZookeeperDynamicConfiguration implements DynamicConfiguration {
+
+ private static final String EMPTY_STRING = "";
+
private static final Logger logger = LoggerFactory.getLogger(ZookeeperDynamicConfiguration.class);
private Executor executor;
@@ -91,12 +100,53 @@ public void removeListener(String key, String group, ConfigurationListener liste
@Override
public String getConfig(String key, String group, long timeout) throws IllegalStateException {
+ String path = buildPath(key, group);
+ return (String) getInternalProperty(path);
+ }
+
+ /**
+ * For zookeeper, {@link #getConfig(String, String, long)} and {@link #getConfigs(String, String, long)} have the same meaning.
+ *
+ * @param key
+ * @param group
+ * @param timeout
+ * @return
+ * @throws IllegalStateException
+ */
+ @Override
+ public String getConfigs(String key, String group, long timeout) throws IllegalStateException {
+ return getConfig(key, group, timeout);
+ }
+
+ @Override
+ public boolean publishConfig(String key, String group, String content) {
+ String path = buildPath(key, group);
+ zkClient.create(path, content, true);
+ return true;
+ }
+
+ @Override
+ public SortedSet getConfigKeys(String group) {
+ String path = buildPath(group);
+ List nodes = zkClient.getChildren(path);
+ return isEmpty(nodes) ? emptySortedSet() : unmodifiableSortedSet(new TreeSet<>(nodes));
+ }
+
+ /**
+ * Build the config node path by the specified key
and group
+ *
+ * @param key the key to represent a configuration
+ * @param group the group where the key belongs to
+ * @return
+ */
+ protected String buildPath(String key, String group) {
+ String path = null;
/**
* when group is not null, we are getting startup configs from Config Center, for example:
* group=dubbo, key=dubbo.properties
*/
if (StringUtils.isNotEmpty(group)) {
- key = group + "/" + key;
+ path = group + "/" + key;
}
/**
* when group is null, we are fetching governance rules, for example:
@@ -105,23 +155,14 @@ public String getConfig(String key, String group, long timeout) throws IllegalSt
*/
else {
int i = key.lastIndexOf(".");
- key = key.substring(0, i) + "/" + key.substring(i + 1);
+ path = key.substring(0, i) + "/" + key.substring(i + 1);
}
-
- return (String) getInternalProperty(rootPath + "/" + key);
+ return buildPath(path);
}
- /**
- * For zookeeper, {@link #getConfig(String, String, long)} and {@link #getConfigs(String, String, long)} have the same meaning.
- *
- * @param key
- * @param group
- * @param timeout
- * @return
- * @throws IllegalStateException
- */
- @Override
- public String getConfigs(String key, String group, long timeout) throws IllegalStateException {
- return (String) getConfig(key, group, timeout);
+ protected String buildPath(String relativePath) {
+ String path = rootPath + "/" + relativePath;
+ return path;
}
+
}
diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java
index 64e0becc64f..df440acc620 100644
--- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java
+++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java
@@ -35,8 +35,14 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
+import static java.util.Arrays.asList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
/**
* TODO refactor using mockito
*/
@@ -122,6 +128,39 @@ public void testAddListener() throws Exception {
Assertions.assertEquals("new value2", listener4.getValue());
}
+ @Test
+ public void testPublishConfig() {
+ String key = "user-service";
+ String group = "org.apache.dubbo.service.UserService";
+ String content = "test";
+
+ assertTrue(configuration.publishConfig(key, group, content));
+ assertEquals("test", configuration.getConfigs(key, group));
+ }
+
+ @Test
+ public void testGetConfigKeysAndContents() {
+
+ String key = "user-service";
+ String group = "org.apache.dubbo.service.UserService";
+ String content = "test";
+
+ String key2 = "user-service-1";
+
+ assertTrue(configuration.publishConfig(key, group, content));
+ assertTrue(configuration.publishConfig(key2, group, content));
+
+ Set configKeys = configuration.getConfigKeys(group);
+
+ assertEquals(new TreeSet(asList(key, key2)), configKeys);
+
+ Map configs = configuration.getConfigs(group);
+
+ assertEquals(configs.keySet(), configKeys);
+
+ configs.forEach((k, value) -> assertEquals(content, value));
+ }
+
private class TestListener implements ConfigurationListener {
private CountDownLatch latch;
private String value;
diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml
index ef42a6e4517..e250644f506 100644
--- a/dubbo-dependencies-bom/pom.xml
+++ b/dubbo-dependencies-bom/pom.xml
@@ -228,6 +228,17 @@