blob: 054f67949bd00275f07db986017f23914c9937ab [file] [log] [blame]
/*
* 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.constants.CommonConstants;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
import org.apache.dubbo.common.url.component.ServiceConfigURL;
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.Service;
import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker;
import org.apache.dubbo.config.support.Parameter;
import org.apache.dubbo.config.utils.ConfigValidationUtils;
import org.apache.dubbo.metadata.ServiceNameMapping;
import org.apache.dubbo.registry.client.metadata.MetadataUtils;
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.ServerService;
import org.apache.dubbo.rpc.cluster.ConfiguratorFactory;
import org.apache.dubbo.rpc.model.ModuleModel;
import org.apache.dubbo.rpc.model.ModuleServiceRepository;
import org.apache.dubbo.rpc.model.ProviderModel;
import org.apache.dubbo.rpc.model.ScopeModel;
import org.apache.dubbo.rpc.model.ServiceDescriptor;
import org.apache.dubbo.rpc.service.GenericService;
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.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
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.DUBBO;
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_IP_TO_BIND;
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.PROVIDER_SIDE;
import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.SERVICE_NAME_MAPPING_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_NO_METHOD_FOUND;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_UNEXPORT_ERROR;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_USE_RANDOM_PORT;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_FAILED_EXPORT_SERVICE;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CONFIG_SERVER_DISCONNECTED;
import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_PROTOCOL;
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.config.Constants.SCOPE_NONE;
import static org.apache.dubbo.registry.Constants.REGISTER_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.remoting.Constants.IS_PU_SERVER_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.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;
import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY;
import static org.apache.dubbo.rpc.support.ProtocolUtils.isGeneric;
public class ServiceConfig<T> extends ServiceConfigBase<T> {
private static final long serialVersionUID = 7868244018230856253L;
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ServiceConfig.class);
/**
* A random port cache, the different protocols who have no port specified have different random port
*/
private static final Map<String, Integer> RANDOM_PORT_MAP = new HashMap<String, Integer>();
private Protocol protocolSPI;
/**
* A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its
* default implementation
*/
private ProxyFactory proxyFactory;
private ProviderModel providerModel;
/**
* 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;
private transient volatile AtomicBoolean initialized = new AtomicBoolean(false);
/**
* The exported services
*/
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
private final List<ServiceListener> serviceListeners = new ArrayList<>();
public ServiceConfig() {
}
public ServiceConfig(ModuleModel moduleModel) {
super(moduleModel);
}
public ServiceConfig(Service service) {
super(service);
}
public ServiceConfig(ModuleModel moduleModel, Service service) {
super(moduleModel, service);
}
@Override
protected void postProcessAfterScopeModelChanged(ScopeModel oldScopeModel, ScopeModel newScopeModel) {
super.postProcessAfterScopeModelChanged(oldScopeModel, newScopeModel);
protocolSPI = this.getExtensionLoader(Protocol.class).getAdaptiveExtension();
proxyFactory = this.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
}
@Override
@Parameter(excluded = true, attribute = false)
public boolean isExported() {
return exported;
}
@Override
@Parameter(excluded = true, attribute = false)
public boolean isUnexported() {
return unexported;
}
@Override
public void unexport() {
if (!exported) {
return;
}
if (unexported) {
return;
}
if (!exporters.isEmpty()) {
for (Exporter<?> exporter : exporters) {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(CONFIG_UNEXPORT_ERROR, "", "", "Unexpected error occurred when unexport " + exporter, t);
}
}
exporters.clear();
}
unexported = true;
onUnexpoted();
ModuleServiceRepository repository = getScopeModel().getServiceRepository();
repository.unregisterProvider(providerModel);
}
/**
* for early init serviceMetadata
*/
public void init() {
if (this.initialized.compareAndSet(false, true)) {
// load ServiceListeners from extension
ExtensionLoader<ServiceListener> extensionLoader = this.getExtensionLoader(ServiceListener.class);
this.serviceListeners.addAll(extensionLoader.getSupportedExtensionInstances());
}
initServiceMetadata(provider);
serviceMetadata.setServiceType(getInterfaceClass());
serviceMetadata.setTarget(getRef());
serviceMetadata.generateServiceKey();
}
@Override
public void export() {
if (this.exported) {
return;
}
// ensure start module, compatible with old api usage
getScopeModel().getDeployer().start();
synchronized (this) {
if (this.exported) {
return;
}
if (!this.isRefreshed()) {
this.refresh();
}
if (this.shouldExport()) {
this.init();
if (shouldDelay()) {
doDelayExport();
} else {
doExport();
}
}
}
}
protected void doDelayExport() {
getScopeModel().getDefaultExtension(ExecutorRepository.class).getServiceExportExecutor()
.schedule(() -> {
try {
doExport();
} catch (Exception e) {
logger.error(CONFIG_FAILED_EXPORT_SERVICE, "configuration server disconnected", "", "Failed to (async)export service config: " + interfaceName, e);
}
}, getDelay(), TimeUnit.MILLISECONDS);
}
protected void exported() {
exported = true;
List<URL> exportedURLs = this.getExportedUrls();
exportedURLs.forEach(url -> {
if (url.getParameters().containsKey(SERVICE_NAME_MAPPING_KEY)) {
ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(getScopeModel());
try {
boolean succeeded = serviceNameMapping.map(url);
if (succeeded) {
logger.info("Successfully registered interface application mapping for service " + url.getServiceKey());
} else {
logger.error(CONFIG_SERVER_DISCONNECTED, "configuration server disconnected", "", "Failed register interface application mapping for service " + url.getServiceKey());
}
} catch (Exception e) {
logger.error(CONFIG_SERVER_DISCONNECTED, "configuration server disconnected", "", "Failed register interface application mapping for service " + url.getServiceKey(), e);
}
}
});
onExported();
}
private void checkAndUpdateSubConfigs() {
// Use default configs defined explicitly with global scope
completeCompoundConfigs();
checkProtocol();
// init some null configuration.
List<ConfigInitializer> configInitializers = this.getExtensionLoader(ConfigInitializer.class)
.getActivateExtension(URL.valueOf("configInitializer://", getScopeModel()), (String[]) null);
configInitializers.forEach(e -> e.initServiceConfig(this));
// if protocol is not injvm checkRegistry
if (!isOnlyInJvm()) {
checkRegistry();
}
if (StringUtils.isEmpty(interfaceName)) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
try {
if (getInterfaceClassLoader() != null) {
interfaceClass = Class.forName(interfaceName, true, getInterfaceClassLoader());
} else {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
}
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
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);
ConfigValidationUtils.checkMock(interfaceClass, this);
ConfigValidationUtils.validateServiceConfig(this);
postProcessConfig();
}
@Override
protected void postProcessRefresh() {
super.postProcessRefresh();
checkAndUpdateSubConfigs();
}
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
if (exported) {
return;
}
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
doExportUrls();
exported();
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void doExportUrls() {
ModuleServiceRepository repository = getScopeModel().getServiceRepository();
ServiceDescriptor serviceDescriptor;
final boolean serverService = ref instanceof ServerService;
if (serverService) {
serviceDescriptor = ((ServerService) ref).getServiceDescriptor();
repository.registerService(serviceDescriptor);
} else {
serviceDescriptor = repository.registerService(getInterfaceClass());
}
providerModel = new ProviderModel(serviceMetadata.getServiceKey(),
ref,
serviceDescriptor,
getScopeModel(),
serviceMetadata, interfaceClassLoader);
// Compatible with dependencies on ServiceModel#getServiceConfig(), and will be removed in a future version
providerModel.setConfig(this);
providerModel.setDestroyRunner(getDestroyRunner());
repository.registerProvider(providerModel);
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
// stub service will use generated service name
if (!serverService) {
// In case user specified path, register service one more time to map it to path.
repository.registerService(pathKey, interfaceClass);
}
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
providerModel.setServiceUrls(urls);
}
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
Map<String, String> map = buildAttributes(protocolConfig);
// remove null key and null value
map.keySet().removeIf(key -> StringUtils.isEmpty(key) || StringUtils.isEmpty(map.get(key)));
// init serviceMetadata attachments
serviceMetadata.getAttachments().putAll(map);
URL url = buildUrl(protocolConfig, map);
exportUrl(url, registryURLs);
}
private Map<String, String> buildAttributes(ProtocolConfig protocolConfig) {
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE);
// append params with basic configs,
ServiceConfig.appendRuntimeParameters(map);
AbstractConfig.appendParameters(map, getApplication());
AbstractConfig.appendParameters(map, getModule());
// remove 'default.' prefix for configs from ProviderConfig
// appendParameters(map, provider, Constants.DEFAULT_KEY);
AbstractConfig.appendParameters(map, provider);
AbstractConfig.appendParameters(map, protocolConfig);
AbstractConfig.appendParameters(map, this);
appendMetricsCompatible(map);
// append params with method configs,
if (CollectionUtils.isNotEmpty(getMethods())) {
getMethods().forEach(method -> appendParametersWithMethod(method, map));
}
if (isGeneric(generic)) {
map.put(GENERIC_KEY, generic);
map.put(METHODS_KEY, ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (StringUtils.isNotEmpty(revision)) {
map.put(REVISION_KEY, revision);
}
String[] methods = methods(interfaceClass);
if (methods.length == 0) {
logger.warn(CONFIG_NO_METHOD_FOUND, "", "", "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)), ","));
}
}
/**
* Here the token value configured by the provider is used to assign the value to ServiceConfig#token
*/
if (ConfigUtils.isEmpty(token) && provider != null) {
token = provider.getToken();
}
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(TOKEN_KEY, token);
}
}
if (ref instanceof ServerService) {
map.put(PROXY_KEY, CommonConstants.NATIVE_STUB);
}
return map;
}
private void appendParametersWithMethod(MethodConfig method, Map<String, String> params) {
AbstractConfig.appendParameters(params, method, method.getName());
String retryKey = method.getName() + ".retry";
if (params.containsKey(retryKey)) {
String retryValue = params.remove(retryKey);
if ("false".equals(retryValue)) {
params.put(method.getName() + ".retries", "0");
}
}
List<ArgumentConfig> arguments = method.getArguments();
if (CollectionUtils.isNotEmpty(arguments)) {
Method matchedMethod = findMatchedMethod(method);
if (matchedMethod != null) {
arguments.forEach(argument -> appendArgumentConfig(argument, matchedMethod, params));
}
}
}
private Method findMatchedMethod(MethodConfig methodConfig) {
for (Method method : interfaceClass.getMethods()) {
if (method.getName().equals(methodConfig.getName())) {
return method;
}
}
return null;
}
private void appendArgumentConfig(ArgumentConfig argument, Method method, Map<String, String> params) {
if (StringUtils.isNotEmpty(argument.getType())) {
Integer index = findArgumentIndexIndexWithGivenType(argument, method);
AbstractConfig.appendParameters(params, argument, method.getName() + "." + index);
} else if (hasIndex(argument)) {
AbstractConfig.appendParameters(params, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
}
}
private boolean hasIndex(ArgumentConfig argument) {
return argument.getIndex() != -1;
}
private boolean isTypeMatched(String type, Integer index, Class<?>[] argtypes) {
return index != null && index >= 0 && index < argtypes.length && argtypes[index].getName().equals(type);
}
private Integer findArgumentIndexIndexWithGivenType(ArgumentConfig argument, Method method) {
Class<?>[] argTypes = method.getParameterTypes();
// one callback in the method
if (hasIndex(argument)) {
Integer index = argument.getIndex();
String type = argument.getType();
if (isTypeMatched(type, index, argTypes)) {
return index;
} 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++) {
if (isTypeMatched(argument.getType(), j, argTypes)) {
return j;
}
}
throw new IllegalArgumentException("Argument config error : no argument matched with the type:" + argument.getType());
}
}
private URL buildUrl(ProtocolConfig protocolConfig, Map<String, String> params) {
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
// export service
String host = findConfiguredHosts(protocolConfig, provider, params);
if (NetUtils.isIPV6URLStdFormat(host)) {
if (!host.contains("[")) {
host = "[" + host + "]";
}
} else if (NetUtils.getLocalHostV6() != null) {
String ipv6Host = NetUtils.getLocalHostV6();
params.put(CommonConstants.IPV6_KEY, ipv6Host);
}
Integer port = findConfiguredPort(protocolConfig, provider, this.getExtensionLoader(Protocol.class), name, params);
URL url = new ServiceConfigURL(name, null, null, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), params);
// You can customize Configurator to append extra parameters
if (this.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = this.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
url = url.setScopeModel(getScopeModel());
url = url.setServiceModel(providerModel);
return url;
}
private void exportUrl(URL url, List<URL> registryURLs) {
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)) {
// export to extra protocol is used in remote export
String extProtocol = url.getParameter("ext.protocol", "");
List<String> protocols = new ArrayList<>();
// export original url
url = URLBuilder.from(url).
addParameter(IS_PU_SERVER_KEY, Boolean.TRUE.toString()).
removeParameter("ext.protocol").
build();
url = exportRemote(url, registryURLs);
if (!isGeneric(generic) && !getScopeModel().isInternal()) {
MetadataUtils.publishServiceDefinition(url, providerModel.getServiceModel(), getApplicationModel());
}
if (!extProtocol.equals("")) {
String[] extProtocols = extProtocol.split(",", -1);
protocols.addAll(Arrays.asList(extProtocols));
}
// export extra protocols
for(String protocol : protocols) {
if(!protocol.equals("")){
URL localUrl = URLBuilder.from(url).
setProtocol(protocol).
build();
localUrl = exportRemote(localUrl, registryURLs);
if (!isGeneric(generic) && !getScopeModel().isInternal()) {
MetadataUtils.publishServiceDefinition(localUrl, providerModel.getServiceModel(), getApplicationModel());
}
this.urls.add(localUrl);
}
}
}
}
this.urls.add(url);
}
private URL exportRemote(URL url, List<URL> registryURLs) {
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {
url = url.addParameterIfAbsent(SERVICE_NAME_MAPPING_KEY, "true");
}
//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 = ConfigValidationUtils.loadMonitor(this, registryURL);
if (monitorUrl != null) {
url = url.putAttribute(MONITOR_KEY, monitorUrl);
}
// 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);
}
if (logger.isInfoEnabled()) {
if (url.getParameter(REGISTER_KEY, true)) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL.getAddress());
} else {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
}
doExportUrl(registryURL.putAttribute(EXPORT_KEY, url), true);
}
} else {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
doExportUrl(url, true);
}
return url;
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void doExportUrl(URL url, boolean withMetaData) {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
if (withMetaData) {
invoker = new DelegateProviderMetaDataInvoker(invoker, this);
}
Exporter<?> exporter = protocolSPI.export(invoker);
exporters.add(exporter);
}
/**
* always export injvm
*/
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
local = local.setScopeModel(getScopeModel())
.setServiceModel(providerModel);
doExportUrl(local, false);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
/**
* Determine if it is injvm
*
* @return
*/
private boolean isOnlyInJvm() {
return getProtocols().size() == 1
&& LOCAL_PROTOCOL.equalsIgnoreCase(getProtocols().get(0).getName());
}
private void postProcessConfig() {
List<ConfigPostProcessor> configPostProcessors = this.getExtensionLoader(ConfigPostProcessor.class)
.getActivateExtension(URL.valueOf("configPostProcessor://", getScopeModel()), (String[]) null);
configPostProcessors.forEach(component -> component.postProcessServiceConfig(this));
}
public void addServiceListener(ServiceListener listener) {
this.serviceListeners.add(listener);
}
protected void onExported() {
for (ServiceListener serviceListener : this.serviceListeners) {
serviceListener.exported(this);
}
}
protected void onUnexpoted() {
for (ServiceListener serviceListener : this.serviceListeners) {
serviceListener.unexported(this);
}
}
/**
* 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 map
* @return
*/
private static String findConfiguredHosts(ProtocolConfig protocolConfig,
ProviderConfig provider,
Map<String, String> map) {
boolean anyhost = false;
String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND);
if (StringUtils.isNotEmpty(hostToBind) && 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;
if (logger.isDebugEnabled()) {
logger.debug("No valid ip found from environment, try to get local host.");
}
hostToBind = getLocalHost();
}
}
map.put(BIND_IP_KEY, hostToBind);
// bind ip is not used for registry ip by default
String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY);
if (StringUtils.isNotEmpty(hostToRegistry) && 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 static synchronized Integer findConfiguredPort(ProtocolConfig protocolConfig,
ProviderConfig provider,
ExtensionLoader<Protocol> extensionLoader,
String name, Map<String, String> map) {
Integer portToBind;
// 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.getExtension(name).getDefaultPort();
if (portToBind == null || portToBind == 0) {
portToBind = defaultPort;
}
if (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(BIND_PORT_KEY, String.valueOf(portToBind));
// bind port is not used as registry port by default
String portToRegistryStr = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_REGISTRY);
Integer portToRegistry = parsePort(portToRegistryStr);
if (portToRegistry == null) {
portToRegistry = portToBind;
}
return portToRegistry;
}
private static Integer parsePort(String configPort) {
Integer port = null;
if (StringUtils.isNotEmpty(configPort)) {
try {
int 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 static String getValueFromConfig(ProtocolConfig protocolConfig, String key) {
String protocolPrefix = protocolConfig.getName().toUpperCase() + "_";
String value = ConfigUtils.getSystemProperty(protocolPrefix + key);
if (StringUtils.isEmpty(value)) {
value = ConfigUtils.getSystemProperty(key);
}
return value;
}
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(CONFIG_USE_RANDOM_PORT, "", "", "Use random available port(" + port + ") for protocol " + protocol);
}
}
public Runnable getDestroyRunner() {
return this::unexport;
}
}