/* | |
* 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 com.alibaba.dubbo.registry.integration; | |
import com.alibaba.dubbo.common.Constants; | |
import com.alibaba.dubbo.common.URL; | |
import com.alibaba.dubbo.common.Version; | |
import com.alibaba.dubbo.common.extension.ExtensionLoader; | |
import com.alibaba.dubbo.common.logger.Logger; | |
import com.alibaba.dubbo.common.logger.LoggerFactory; | |
import com.alibaba.dubbo.common.utils.NetUtils; | |
import com.alibaba.dubbo.common.utils.StringUtils; | |
import com.alibaba.dubbo.registry.NotifyListener; | |
import com.alibaba.dubbo.registry.Registry; | |
import com.alibaba.dubbo.rpc.Invocation; | |
import com.alibaba.dubbo.rpc.Invoker; | |
import com.alibaba.dubbo.rpc.Protocol; | |
import com.alibaba.dubbo.rpc.RpcException; | |
import com.alibaba.dubbo.rpc.RpcInvocation; | |
import com.alibaba.dubbo.rpc.cluster.Cluster; | |
import com.alibaba.dubbo.rpc.cluster.Configurator; | |
import com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory; | |
import com.alibaba.dubbo.rpc.cluster.Router; | |
import com.alibaba.dubbo.rpc.cluster.RouterFactory; | |
import com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory; | |
import com.alibaba.dubbo.rpc.cluster.directory.StaticDirectory; | |
import com.alibaba.dubbo.rpc.cluster.support.ClusterUtils; | |
import com.alibaba.dubbo.rpc.protocol.InvokerWrapper; | |
import com.alibaba.dubbo.rpc.support.RpcUtils; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
/** | |
* RegistryDirectory | |
* | |
*/ | |
public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener { | |
private static final Logger logger = LoggerFactory.getLogger(RegistryDirectory.class); | |
private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension(); | |
private static final RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension(); | |
private static final ConfiguratorFactory configuratorFactory = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getAdaptiveExtension(); | |
private final String serviceKey; // Initialization at construction time, assertion not null | |
private final Class<T> serviceType; // Initialization at construction time, assertion not null | |
private final Map<String, String> queryMap; // Initialization at construction time, assertion not null | |
private final URL directoryUrl; // Initialization at construction time, assertion not null, and always assign non null value | |
private final String[] serviceMethods; | |
private final boolean multiGroup; | |
private Protocol protocol; // Initialization at the time of injection, the assertion is not null | |
private Registry registry; // Initialization at the time of injection, the assertion is not null | |
private volatile boolean forbidden = false; | |
private volatile URL overrideDirectoryUrl; // Initialization at construction time, assertion not null, and always assign non null value | |
/** | |
* override rules | |
* Priority: override>-D>consumer>provider | |
* Rule one: for a certain provider <ip:port,timeout=100> | |
* Rule two: for all providers <* ,timeout=5000> | |
*/ | |
private volatile List<Configurator> configurators; // The initial value is null and the midway may be assigned to null, please use the local variable reference | |
// Map<url, Invoker> cache service url to invoker mapping. | |
private volatile Map<String, Invoker<T>> urlInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference | |
// Map<methodName, Invoker> cache service method to invokers mapping. | |
private volatile Map<String, List<Invoker<T>>> methodInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference | |
// Set<invokerUrls> cache invokeUrls to invokers mapping. | |
private volatile Set<URL> cachedInvokerUrls; // The initial value is null and the midway may be assigned to null, please use the local variable reference | |
public RegistryDirectory(Class<T> serviceType, URL url) { | |
super(url); | |
if (serviceType == null) | |
throw new IllegalArgumentException("service type is null."); | |
if (url.getServiceKey() == null || url.getServiceKey().length() == 0) | |
throw new IllegalArgumentException("registry serviceKey is null."); | |
this.serviceType = serviceType; | |
this.serviceKey = url.getServiceKey(); | |
this.queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY)); | |
this.overrideDirectoryUrl = this.directoryUrl = url.setPath(url.getServiceInterface()).clearParameters().addParameters(queryMap).removeParameter(Constants.MONITOR_KEY); | |
String group = directoryUrl.getParameter(Constants.GROUP_KEY, ""); | |
this.multiGroup = group != null && ("*".equals(group) || group.contains(",")); | |
String methods = queryMap.get(Constants.METHODS_KEY); | |
this.serviceMethods = methods == null ? null : Constants.COMMA_SPLIT_PATTERN.split(methods); | |
} | |
/** | |
* Convert override urls to map for use when re-refer. | |
* Send all rules every time, the urls will be reassembled and calculated | |
* | |
* @param urls Contract: | |
* </br>1.override://0.0.0.0/...( or override://ip:port...?anyhost=true)¶1=value1... means global rules (all of the providers take effect) | |
* </br>2.override://ip:port...?anyhost=false Special rules (only for a certain provider) | |
* </br>3.override:// rule is not supported... ,needs to be calculated by registry itself. | |
* </br>4.override://0.0.0.0/ without parameters means clearing the override | |
* @return | |
*/ | |
public static List<Configurator> toConfigurators(List<URL> urls) { | |
if (urls == null || urls.isEmpty()) { | |
return Collections.emptyList(); | |
} | |
List<Configurator> configurators = new ArrayList<Configurator>(urls.size()); | |
for (URL url : urls) { | |
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) { | |
configurators.clear(); | |
break; | |
} | |
Map<String, String> override = new HashMap<String, String>(url.getParameters()); | |
//The anyhost parameter of override may be added automatically, it can't change the judgement of changing url | |
override.remove(Constants.ANYHOST_KEY); | |
if (override.size() == 0) { | |
configurators.clear(); | |
continue; | |
} | |
configurators.add(configuratorFactory.getConfigurator(url)); | |
} | |
Collections.sort(configurators); | |
return configurators; | |
} | |
public void setProtocol(Protocol protocol) { | |
this.protocol = protocol; | |
} | |
public void setRegistry(Registry registry) { | |
this.registry = registry; | |
} | |
public void subscribe(URL url) { | |
setConsumerUrl(url); | |
registry.subscribe(url, this); | |
} | |
@Override | |
public void destroy() { | |
if (isDestroyed()) { | |
return; | |
} | |
// unsubscribe. | |
try { | |
if (getConsumerUrl() != null && registry != null && registry.isAvailable()) { | |
registry.unsubscribe(getConsumerUrl(), this); | |
} | |
} catch (Throwable t) { | |
logger.warn("unexpeced error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t); | |
} | |
super.destroy(); // must be executed after unsubscribing | |
try { | |
destroyAllInvokers(); | |
} catch (Throwable t) { | |
logger.warn("Failed to destroy service " + serviceKey, t); | |
} | |
} | |
@Override | |
public synchronized void notify(List<URL> urls) { | |
List<URL> invokerUrls = new ArrayList<URL>(); | |
List<URL> routerUrls = new ArrayList<URL>(); | |
List<URL> configuratorUrls = new ArrayList<URL>(); | |
for (URL url : urls) { | |
String protocol = url.getProtocol(); | |
String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); | |
if (Constants.ROUTERS_CATEGORY.equals(category) | |
|| Constants.ROUTE_PROTOCOL.equals(protocol)) { | |
routerUrls.add(url); | |
} else if (Constants.CONFIGURATORS_CATEGORY.equals(category) | |
|| Constants.OVERRIDE_PROTOCOL.equals(protocol)) { | |
configuratorUrls.add(url); | |
} else if (Constants.PROVIDERS_CATEGORY.equals(category)) { | |
invokerUrls.add(url); | |
} else { | |
logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()); | |
} | |
} | |
// configurators | |
if (configuratorUrls != null && !configuratorUrls.isEmpty()) { | |
this.configurators = toConfigurators(configuratorUrls); | |
} | |
// routers | |
if (routerUrls != null && !routerUrls.isEmpty()) { | |
List<Router> routers = toRouters(routerUrls); | |
if (routers != null) { // null - do nothing | |
setRouters(routers); | |
} | |
} | |
List<Configurator> localConfigurators = this.configurators; // local reference | |
// merge override parameters | |
this.overrideDirectoryUrl = directoryUrl; | |
if (localConfigurators != null && !localConfigurators.isEmpty()) { | |
for (Configurator configurator : localConfigurators) { | |
this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl); | |
} | |
} | |
// providers | |
refreshInvoker(invokerUrls); | |
} | |
/** | |
* Convert the invokerURL list to the Invoker Map. The rules of the conversion are as follows: | |
* 1.If URL has been converted to invoker, it is no longer re-referenced and obtained directly from the cache, and notice that any parameter changes in the URL will be re-referenced. | |
* 2.If the incoming invoker list is not empty, it means that it is the latest invoker list | |
* 3.If the list of incoming invokerUrl is empty, It means that the rule is only a override rule or a route rule, which needs to be re-contrasted to decide whether to re-reference. | |
* | |
* @param invokerUrls this parameter can't be null | |
*/ | |
// TODO: 2017/8/31 FIXME The thread pool should be used to refresh the address, otherwise the task may be accumulated. | |
private void refreshInvoker(List<URL> invokerUrls) { | |
if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null | |
&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { | |
this.forbidden = true; // Forbid to access | |
this.methodInvokerMap = null; // Set the method invoker map to null | |
destroyAllInvokers(); // Close all invokers | |
} else { | |
this.forbidden = false; // Allow to access | |
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference | |
if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) { | |
invokerUrls.addAll(this.cachedInvokerUrls); | |
} else { | |
this.cachedInvokerUrls = new HashSet<URL>(); | |
this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison | |
} | |
if (invokerUrls.isEmpty()) { | |
return; | |
} | |
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map | |
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map | |
// state change | |
// If the calculation is wrong, it is not processed. | |
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { | |
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString())); | |
return; | |
} | |
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap; | |
this.urlInvokerMap = newUrlInvokerMap; | |
try { | |
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker | |
} catch (Exception e) { | |
logger.warn("destroyUnusedInvokers error. ", e); | |
} | |
} | |
} | |
private Map<String, List<Invoker<T>>> toMergeMethodInvokerMap(Map<String, List<Invoker<T>>> methodMap) { | |
Map<String, List<Invoker<T>>> result = new HashMap<String, List<Invoker<T>>>(); | |
for (Map.Entry<String, List<Invoker<T>>> entry : methodMap.entrySet()) { | |
String method = entry.getKey(); | |
List<Invoker<T>> invokers = entry.getValue(); | |
Map<String, List<Invoker<T>>> groupMap = new HashMap<String, List<Invoker<T>>>(); | |
for (Invoker<T> invoker : invokers) { | |
String group = invoker.getUrl().getParameter(Constants.GROUP_KEY, ""); | |
List<Invoker<T>> groupInvokers = groupMap.get(group); | |
if (groupInvokers == null) { | |
groupInvokers = new ArrayList<Invoker<T>>(); | |
groupMap.put(group, groupInvokers); | |
} | |
groupInvokers.add(invoker); | |
} | |
if (groupMap.size() == 1) { | |
result.put(method, groupMap.values().iterator().next()); | |
} else if (groupMap.size() > 1) { | |
List<Invoker<T>> groupInvokers = new ArrayList<Invoker<T>>(); | |
for (List<Invoker<T>> groupList : groupMap.values()) { | |
groupInvokers.add(cluster.join(new StaticDirectory<T>(groupList))); | |
} | |
result.put(method, groupInvokers); | |
} else { | |
result.put(method, invokers); | |
} | |
} | |
return result; | |
} | |
/** | |
* @param urls | |
* @return null : no routers ,do nothing | |
* else :routers list | |
*/ | |
private List<Router> toRouters(List<URL> urls) { | |
List<Router> routers = new ArrayList<Router>(); | |
if (urls == null || urls.isEmpty()) { | |
return routers; | |
} | |
if (urls != null && !urls.isEmpty()) { | |
for (URL url : urls) { | |
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) { | |
continue; | |
} | |
String routerType = url.getParameter(Constants.ROUTER_KEY); | |
if (routerType != null && routerType.length() > 0) { | |
url = url.setProtocol(routerType); | |
} | |
try { | |
Router router = routerFactory.getRouter(url); | |
if (!routers.contains(router)) | |
routers.add(router); | |
} catch (Throwable t) { | |
logger.error("convert router url to router error, url: " + url, t); | |
} | |
} | |
} | |
return routers; | |
} | |
/** | |
* Turn urls into invokers, and if url has been refer, will not re-reference. | |
* | |
* @param urls | |
* @return invokers | |
*/ | |
private Map<String, Invoker<T>> toInvokers(List<URL> urls) { | |
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>(); | |
if (urls == null || urls.isEmpty()) { | |
return newUrlInvokerMap; | |
} | |
Set<String> keys = new HashSet<String>(); | |
String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY); | |
for (URL providerUrl : urls) { | |
// If protocol is configured at the reference side, only the matching protocol is selected | |
if (queryProtocols != null && queryProtocols.length() > 0) { | |
boolean accept = false; | |
String[] acceptProtocols = queryProtocols.split(","); | |
for (String acceptProtocol : acceptProtocols) { | |
if (providerUrl.getProtocol().equals(acceptProtocol)) { | |
accept = true; | |
break; | |
} | |
} | |
if (!accept) { | |
continue; | |
} | |
} | |
if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) { | |
continue; | |
} | |
if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) { | |
logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost() | |
+ ", supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions())); | |
continue; | |
} | |
URL url = mergeUrl(providerUrl); | |
String key = url.toFullString(); // The parameter urls are sorted | |
if (keys.contains(key)) { // Repeated url | |
continue; | |
} | |
keys.add(key); | |
// Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again | |
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference | |
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key); | |
if (invoker == null) { // Not in the cache, refer again | |
try { | |
boolean enabled = true; | |
if (url.hasParameter(Constants.DISABLED_KEY)) { | |
enabled = !url.getParameter(Constants.DISABLED_KEY, false); | |
} else { | |
enabled = url.getParameter(Constants.ENABLED_KEY, true); | |
} | |
if (enabled) { | |
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl); | |
} | |
} catch (Throwable t) { | |
logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t); | |
} | |
if (invoker != null) { // Put new invoker in cache | |
newUrlInvokerMap.put(key, invoker); | |
} | |
} else { | |
newUrlInvokerMap.put(key, invoker); | |
} | |
} | |
keys.clear(); | |
return newUrlInvokerMap; | |
} | |
/** | |
* Merge url parameters. the order is: override > -D >Consumer > Provider | |
* | |
* @param providerUrl | |
* @return | |
*/ | |
private URL mergeUrl(URL providerUrl) { | |
providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); // Merge the consumer side parameters | |
List<Configurator> localConfigurators = this.configurators; // local reference | |
if (localConfigurators != null && !localConfigurators.isEmpty()) { | |
for (Configurator configurator : localConfigurators) { | |
providerUrl = configurator.configure(providerUrl); | |
} | |
} | |
providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false)); // Do not check whether the connection is successful or not, always create Invoker! | |
// The combination of directoryUrl and override is at the end of notify, which can't be handled here | |
this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters()); // Merge the provider side parameters | |
if ((providerUrl.getPath() == null || providerUrl.getPath().length() == 0) | |
&& "dubbo".equals(providerUrl.getProtocol())) { // Compatible version 1.0 | |
//fix by tony.chenl DUBBO-44 | |
String path = directoryUrl.getParameter(Constants.INTERFACE_KEY); | |
if (path != null) { | |
int i = path.indexOf('/'); | |
if (i >= 0) { | |
path = path.substring(i + 1); | |
} | |
i = path.lastIndexOf(':'); | |
if (i >= 0) { | |
path = path.substring(0, i); | |
} | |
providerUrl = providerUrl.setPath(path); | |
} | |
} | |
return providerUrl; | |
} | |
private List<Invoker<T>> route(List<Invoker<T>> invokers, String method) { | |
Invocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]); | |
List<Router> routers = getRouters(); | |
if (routers != null) { | |
for (Router router : routers) { | |
if (router.getUrl() != null) { | |
invokers = router.route(invokers, getConsumerUrl(), invocation); | |
} | |
} | |
} | |
return invokers; | |
} | |
/** | |
* Transform the invokers list into a mapping relationship with a method | |
* | |
* @param invokersMap Invoker Map | |
* @return Mapping relation between Invoker and method | |
*/ | |
private Map<String, List<Invoker<T>>> toMethodInvokers(Map<String, Invoker<T>> invokersMap) { | |
Map<String, List<Invoker<T>>> newMethodInvokerMap = new HashMap<String, List<Invoker<T>>>(); | |
// According to the methods classification declared by the provider URL, the methods is compatible with the registry to execute the filtered methods | |
List<Invoker<T>> invokersList = new ArrayList<Invoker<T>>(); | |
if (invokersMap != null && invokersMap.size() > 0) { | |
for (Invoker<T> invoker : invokersMap.values()) { | |
String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY); | |
if (parameter != null && parameter.length() > 0) { | |
String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter); | |
if (methods != null && methods.length > 0) { | |
for (String method : methods) { | |
if (method != null && method.length() > 0 | |
&& !Constants.ANY_VALUE.equals(method)) { | |
List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); | |
if (methodInvokers == null) { | |
methodInvokers = new ArrayList<Invoker<T>>(); | |
newMethodInvokerMap.put(method, methodInvokers); | |
} | |
methodInvokers.add(invoker); | |
} | |
} | |
} | |
} | |
invokersList.add(invoker); | |
} | |
} | |
List<Invoker<T>> newInvokersList = route(invokersList, null); | |
newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList); | |
if (serviceMethods != null && serviceMethods.length > 0) { | |
for (String method : serviceMethods) { | |
List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); | |
if (methodInvokers == null || methodInvokers.isEmpty()) { | |
methodInvokers = newInvokersList; | |
} | |
newMethodInvokerMap.put(method, route(methodInvokers, method)); | |
} | |
} | |
// sort and unmodifiable | |
for (String method : new HashSet<String>(newMethodInvokerMap.keySet())) { | |
List<Invoker<T>> methodInvokers = newMethodInvokerMap.get(method); | |
Collections.sort(methodInvokers, InvokerComparator.getComparator()); | |
newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers)); | |
} | |
return Collections.unmodifiableMap(newMethodInvokerMap); | |
} | |
/** | |
* Close all invokers | |
*/ | |
private void destroyAllInvokers() { | |
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference | |
if (localUrlInvokerMap != null) { | |
for (Invoker<T> invoker : new ArrayList<Invoker<T>>(localUrlInvokerMap.values())) { | |
try { | |
invoker.destroy(); | |
} catch (Throwable t) { | |
logger.warn("Failed to destroy service " + serviceKey + " to provider " + invoker.getUrl(), t); | |
} | |
} | |
localUrlInvokerMap.clear(); | |
} | |
methodInvokerMap = null; | |
} | |
/** | |
* Check whether the invoker in the cache needs to be destroyed | |
* If set attribute of url: refer.autodestroy=false, the invokers will only increase without decreasing,there may be a refer leak | |
* | |
* @param oldUrlInvokerMap | |
* @param newUrlInvokerMap | |
*/ | |
private void destroyUnusedInvokers(Map<String, Invoker<T>> oldUrlInvokerMap, Map<String, Invoker<T>> newUrlInvokerMap) { | |
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { | |
destroyAllInvokers(); | |
return; | |
} | |
// check deleted invoker | |
List<String> deleted = null; | |
if (oldUrlInvokerMap != null) { | |
Collection<Invoker<T>> newInvokers = newUrlInvokerMap.values(); | |
for (Map.Entry<String, Invoker<T>> entry : oldUrlInvokerMap.entrySet()) { | |
if (!newInvokers.contains(entry.getValue())) { | |
if (deleted == null) { | |
deleted = new ArrayList<String>(); | |
} | |
deleted.add(entry.getKey()); | |
} | |
} | |
} | |
if (deleted != null) { | |
for (String url : deleted) { | |
if (url != null) { | |
Invoker<T> invoker = oldUrlInvokerMap.remove(url); | |
if (invoker != null) { | |
try { | |
invoker.destroy(); | |
if (logger.isDebugEnabled()) { | |
logger.debug("destroy invoker[" + invoker.getUrl() + "] success. "); | |
} | |
} catch (Exception e) { | |
logger.warn("destroy invoker[" + invoker.getUrl() + "] faild. " + e.getMessage(), e); | |
} | |
} | |
} | |
} | |
} | |
} | |
@Override | |
public List<Invoker<T>> doList(Invocation invocation) { | |
if (forbidden) { | |
// 1. No service provider 2. Service providers are disabled | |
throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, | |
"No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost() | |
+ " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist)."); | |
} | |
List<Invoker<T>> invokers = null; | |
Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference | |
if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) { | |
String methodName = RpcUtils.getMethodName(invocation); | |
Object[] args = RpcUtils.getArguments(invocation); | |
if (args != null && args.length > 0 && args[0] != null | |
&& (args[0] instanceof String || args[0].getClass().isEnum())) { | |
invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter | |
} | |
if (invokers == null) { | |
invokers = localMethodInvokerMap.get(methodName); | |
} | |
if (invokers == null) { | |
invokers = localMethodInvokerMap.get(Constants.ANY_VALUE); | |
} | |
if (invokers == null) { | |
Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator(); | |
if (iterator.hasNext()) { | |
invokers = iterator.next(); | |
} | |
} | |
} | |
return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers; | |
} | |
@Override | |
public Class<T> getInterface() { | |
return serviceType; | |
} | |
@Override | |
public URL getUrl() { | |
return this.overrideDirectoryUrl; | |
} | |
@Override | |
public boolean isAvailable() { | |
if (isDestroyed()) { | |
return false; | |
} | |
Map<String, Invoker<T>> localUrlInvokerMap = urlInvokerMap; | |
if (localUrlInvokerMap != null && localUrlInvokerMap.size() > 0) { | |
for (Invoker<T> invoker : new ArrayList<Invoker<T>>(localUrlInvokerMap.values())) { | |
if (invoker.isAvailable()) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
/** | |
* Haomin: added for test purpose | |
*/ | |
public Map<String, Invoker<T>> getUrlInvokerMap() { | |
return urlInvokerMap; | |
} | |
/** | |
* Haomin: added for test purpose | |
*/ | |
public Map<String, List<Invoker<T>>> getMethodInvokerMap() { | |
return methodInvokerMap; | |
} | |
private static class InvokerComparator implements Comparator<Invoker<?>> { | |
private static final InvokerComparator comparator = new InvokerComparator(); | |
private InvokerComparator() { | |
} | |
public static InvokerComparator getComparator() { | |
return comparator; | |
} | |
@Override | |
public int compare(Invoker<?> o1, Invoker<?> o2) { | |
return o1.getUrl().toString().compareTo(o2.getUrl().toString()); | |
} | |
} | |
/** | |
* The delegate class, which is mainly used to store the URL address sent by the registry,and can be reassembled on the basis of providerURL queryMap overrideMap for re-refer. | |
* | |
* @param <T> | |
*/ | |
private static class InvokerDelegate<T> extends InvokerWrapper<T> { | |
private URL providerUrl; | |
public InvokerDelegate(Invoker<T> invoker, URL url, URL providerUrl) { | |
super(invoker, url); | |
this.providerUrl = providerUrl; | |
} | |
public URL getProviderUrl() { | |
return providerUrl; | |
} | |
} | |
} |