All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.dubbo.common.extension.ExtensionLoader Maven / Gradle / Ivy

There is a newer version: 3.3.0-beta.2
Show newest version
/*
 * 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.common.extension;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.context.Lifecycle;
import org.apache.dubbo.common.extension.support.ActivateComparator;
import org.apache.dubbo.common.extension.support.WrapperComparator;
import org.apache.dubbo.common.lang.Prioritized;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ArrayUtils;
import org.apache.dubbo.common.utils.ClassUtils;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.common.utils.Holder;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.common.utils.StringUtils;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;

import static java.util.Arrays.asList;
import static java.util.Collections.sort;
import static java.util.ServiceLoader.load;
import static java.util.stream.StreamSupport.stream;
import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.REMOVE_VALUE_PREFIX;

/**
 * {@link org.apache.dubbo.rpc.model.ApplicationModel}, {@code DubboBootstrap} and this class are
 * at present designed to be singleton or static (by itself totally static or uses some static fields).
 * So the instances returned from them are of process or classloader scope. If you want to support
 * multiple dubbo servers in a single process, you may need to refactor these three classes.
 * 

* Load dubbo extensions *

    *
  • auto inject dependency extension
  • *
  • auto wrap extension in wrapper
  • *
  • default extension is an adaptive instance
  • *
* * @see Service Provider in Java 5 * @see org.apache.dubbo.common.extension.SPI * @see org.apache.dubbo.common.extension.Adaptive * @see org.apache.dubbo.common.extension.Activate */ public class ExtensionLoader { private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*"); private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>(64); private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64); private final Class type; private final ExtensionFactory objectFactory; private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap<>(); private final Holder>> cachedClasses = new Holder<>(); private final Map cachedActivates = new ConcurrentHashMap<>(); private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>(); private final Holder cachedAdaptiveInstance = new Holder<>(); private volatile Class cachedAdaptiveClass = null; private String cachedDefaultName; private volatile Throwable createAdaptiveInstanceError; private Set> cachedWrapperClasses; private Map exceptions = new ConcurrentHashMap<>(); /** * Record all unacceptable exceptions when using SPI */ private Set unacceptableExceptions = new ConcurrentHashSet<>(); private static volatile LoadingStrategy[] strategies = loadLoadingStrategies(); public static void setLoadingStrategies(LoadingStrategy... strategies) { if (ArrayUtils.isNotEmpty(strategies)) { ExtensionLoader.strategies = strategies; } } /** * Load all {@link Prioritized prioritized} {@link LoadingStrategy Loading Strategies} via {@link ServiceLoader} * * @return non-null * @since 2.7.7 */ private static LoadingStrategy[] loadLoadingStrategies() { return stream(load(LoadingStrategy.class).spliterator(), false) .sorted() .toArray(LoadingStrategy[]::new); } /** * Get all {@link LoadingStrategy Loading Strategies} * * @return non-null * @see LoadingStrategy * @see Prioritized * @since 2.7.7 */ public static List getLoadingStrategies() { return asList(strategies); } private ExtensionLoader(Class type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); } private static boolean withExtensionAnnotation(Class type) { return type.isAnnotationPresent(SPI.class); } @SuppressWarnings("unchecked") public static ExtensionLoader getExtensionLoader(Class type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); } return loader; } // For testing purposes only @Deprecated public static void resetExtensionLoader(Class type) { ExtensionLoader loader = EXTENSION_LOADERS.get(type); if (loader != null) { // Remove all instances associated with this loader as well Map> classes = loader.getExtensionClasses(); for (Map.Entry> entry : classes.entrySet()) { EXTENSION_INSTANCES.remove(entry.getValue()); } classes.clear(); EXTENSION_LOADERS.remove(type); } } // only for unit test @Deprecated public static void destroyAll() { EXTENSION_INSTANCES.forEach((_type, instance) -> { if (instance instanceof Lifecycle) { Lifecycle lifecycle = (Lifecycle) instance; try { lifecycle.destroy(); } catch (Exception e) { logger.error("Error destroying extension " + lifecycle, e); } } }); EXTENSION_INSTANCES.clear(); EXTENSION_LOADERS.clear(); } private static ClassLoader findClassLoader() { return ClassUtils.getClassLoader(ExtensionLoader.class); } public String getExtensionName(T extensionInstance) { return getExtensionName(extensionInstance.getClass()); } public String getExtensionName(Class extensionClass) { getExtensionClasses();// load class return cachedNames.get(extensionClass); } /** * This is equivalent to {@code getActivateExtension(url, key, null)} * * @param url url * @param key url parameter key which used to get extension point names * @return extension list which are activated. * @see #getActivateExtension(org.apache.dubbo.common.URL, String, String) */ public List getActivateExtension(URL url, String key) { return getActivateExtension(url, key, null); } /** * This is equivalent to {@code getActivateExtension(url, values, null)} * * @param url url * @param values extension point names * @return extension list which are activated * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String) */ public List getActivateExtension(URL url, String[] values) { return getActivateExtension(url, values, null); } /** * This is equivalent to {@code getActivateExtension(url, url.getParameter(key).split(","), null)} * * @param url url * @param key url parameter key which used to get extension point names * @param group group * @return extension list which are activated. * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String) */ public List getActivateExtension(URL url, String key, String group) { String value = url.getParameter(key); return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group); } /** * Get activate extensions. * * @param url url * @param values extension point names * @param group group * @return extension list which are activated * @see org.apache.dubbo.common.extension.Activate */ public List getActivateExtension(URL url, String[] values, String group) { List activateExtensions = new ArrayList<>(); // solve the bug of using @SPI's wrapper method to report a null pointer exception. TreeMap activateExtensionsMap = new TreeMap<>(ActivateComparator.COMPARATOR); Set loadedNames = new HashSet<>(); Set names = CollectionUtils.ofSet(values); if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) { getExtensionClasses(); for (Map.Entry entry : cachedActivates.entrySet()) { String name = entry.getKey(); Object activate = entry.getValue(); String[] activateGroup, activateValue; if (activate instanceof Activate) { activateGroup = ((Activate) activate).group(); activateValue = ((Activate) activate).value(); } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) { activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group(); activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value(); } else { continue; } if (isMatchGroup(group, activateGroup) && !names.contains(name) && !names.contains(REMOVE_VALUE_PREFIX + name) && isActive(activateValue, url) && !loadedNames.contains(name)) { activateExtensionsMap.put(getExtensionClass(name), getExtension(name)); loadedNames.add(name); } } if (!activateExtensionsMap.isEmpty()) { activateExtensions.addAll(activateExtensionsMap.values()); } } List loadedExtensions = new ArrayList<>(); for (String name : names) { if (!name.startsWith(REMOVE_VALUE_PREFIX) && !names.contains(REMOVE_VALUE_PREFIX + name)) { if (!loadedNames.contains(name)) { if (DEFAULT_KEY.equals(name)) { if (!loadedExtensions.isEmpty()) { activateExtensions.addAll(0, loadedExtensions); loadedExtensions.clear(); } } else { loadedExtensions.add(getExtension(name)); } loadedNames.add(name); } else { // If getExtension(name) exists, getExtensionClass(name) must exist, so there is no null pointer processing here. String simpleName = getExtensionClass(name).getSimpleName(); logger.warn("Catch duplicated filter, ExtensionLoader will ignore one of them. Please check. Filter Name: " + name + ". Ignored Class Name: " + simpleName); } } } if (!loadedExtensions.isEmpty()) { activateExtensions.addAll(loadedExtensions); } return activateExtensions; } private boolean isMatchGroup(String group, String[] groups) { if (StringUtils.isEmpty(group)) { return true; } if (groups != null && groups.length > 0) { for (String g : groups) { if (group.equals(g)) { return true; } } } return false; } private boolean isActive(String[] keys, URL url) { if (keys.length == 0) { return true; } for (String key : keys) { // @Active(value="key1:value1, key2:value2") String keyValue = null; if (key.contains(":")) { String[] arr = key.split(":"); key = arr[0]; keyValue = arr[1]; } for (Map.Entry entry : url.getParameters().entrySet()) { String k = entry.getKey(); String v = entry.getValue(); if ((k.equals(key) || k.endsWith("." + key)) && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) { return true; } } } return false; } /** * Get extension's instance. Return null if extension is not found or is not initialized. Pls. note * that this method will not trigger extension load. *

* In order to trigger extension load, call {@link #getExtension(String)} instead. * * @see #getExtension(String) */ @SuppressWarnings("unchecked") public T getLoadedExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } Holder holder = getOrCreateHolder(name); return (T) holder.get(); } private Holder getOrCreateHolder(String name) { Holder holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<>()); holder = cachedInstances.get(name); } return holder; } /** * Return the list of extensions which are already loaded. *

* Usually {@link #getSupportedExtensions()} should be called in order to get all extensions. * * @see #getSupportedExtensions() */ public Set getLoadedExtensions() { return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet())); } public List getLoadedExtensionInstances() { List instances = new ArrayList<>(); cachedInstances.values().forEach(holder -> instances.add((T) holder.get())); return instances; } public Object getLoadedAdaptiveExtensionInstances() { return cachedAdaptiveInstance.get(); } /** * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException} * will be thrown. */ @SuppressWarnings("unchecked") public T getExtension(String name) { return getExtension(name, true); } public T getExtension(String name, boolean wrap) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) { return getDefaultExtension(); } final Holder holder = getOrCreateHolder(name); Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name, wrap); holder.set(instance); } } } return (T) instance; } /** * get the original type. * * @param name * @return */ public T getOriginalInstance(String name) { getExtension(name); Class clazz = getExtensionClasses().get(name); return (T) EXTENSION_INSTANCES.get(clazz); } /** * Get the extension by specified name if found, or {@link #getDefaultExtension() returns the default one} * * @param name the name of extension * @return non-null */ public T getOrDefaultExtension(String name) { return containsExtension(name) ? getExtension(name) : getDefaultExtension(); } /** * Return default extension, return null if it's not configured. */ public T getDefaultExtension() { getExtensionClasses(); if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) { return null; } return getExtension(cachedDefaultName); } public boolean hasExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } Class c = this.getExtensionClass(name); return c != null; } public Set getSupportedExtensions() { Map> clazzes = getExtensionClasses(); return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet())); } public Set getSupportedExtensionInstances() { List instances = new LinkedList<>(); Set supportedExtensions = getSupportedExtensions(); if (CollectionUtils.isNotEmpty(supportedExtensions)) { for (String name : supportedExtensions) { instances.add(getExtension(name)); } } // sort the Prioritized instances sort(instances, Prioritized.COMPARATOR); return new LinkedHashSet<>(instances); } /** * Return default extension name, return null if not configured. */ public String getDefaultExtensionName() { getExtensionClasses(); return cachedDefaultName; } /** * Register new extension via API * * @param name extension name * @param clazz extension class * @throws IllegalStateException when extension with the same name has already been registered. */ public void addExtension(String name, Class clazz) { getExtensionClasses(); // load classes if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Input type " + clazz + " doesn't implement the Extension " + type); } if (clazz.isInterface()) { throw new IllegalStateException("Input type " + clazz + " can't be interface!"); } if (!clazz.isAnnotationPresent(Adaptive.class)) { if (StringUtils.isBlank(name)) { throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); } if (cachedClasses.get().containsKey(name)) { throw new IllegalStateException("Extension name " + name + " already exists (Extension " + type + ")!"); } cachedNames.put(clazz, name); cachedClasses.get().put(name, clazz); } else { if (cachedAdaptiveClass != null) { throw new IllegalStateException("Adaptive Extension already exists (Extension " + type + ")!"); } cachedAdaptiveClass = clazz; } } /** * Replace the existing extension via API * * @param name extension name * @param clazz extension class * @throws IllegalStateException when extension to be placed doesn't exist * @deprecated not recommended any longer, and use only when test */ @Deprecated public void replaceExtension(String name, Class clazz) { getExtensionClasses(); // load classes if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Input type " + clazz + " doesn't implement Extension " + type); } if (clazz.isInterface()) { throw new IllegalStateException("Input type " + clazz + " can't be interface!"); } if (!clazz.isAnnotationPresent(Adaptive.class)) { if (StringUtils.isBlank(name)) { throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); } if (!cachedClasses.get().containsKey(name)) { throw new IllegalStateException("Extension name " + name + " doesn't exist (Extension " + type + ")!"); } cachedNames.put(clazz, name); cachedClasses.get().put(name, clazz); cachedInstances.remove(name); } else { if (cachedAdaptiveClass == null) { throw new IllegalStateException("Adaptive Extension doesn't exist (Extension " + type + ")!"); } cachedAdaptiveClass = clazz; cachedAdaptiveInstance.set(null); } } @SuppressWarnings("unchecked") public T getAdaptiveExtension() { Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError != null) { throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } return (T) instance; } private IllegalStateException findException(String name) { StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name); int i = 1; for (Map.Entry entry : exceptions.entrySet()) { if (entry.getKey().toLowerCase().startsWith(name.toLowerCase())) { if (i == 1) { buf.append(", possible causes: "); } buf.append("\r\n("); buf.append(i++); buf.append(") "); buf.append(entry.getKey()); buf.append(":\r\n"); buf.append(StringUtils.toString(entry.getValue())); } } if (i == 1) { buf.append(", no related exception was found, please check whether related SPI module is missing."); } return new IllegalStateException(buf.toString()); } @SuppressWarnings("unchecked") private T createExtension(String name, boolean wrap) { Class clazz = getExtensionClasses().get(name); if (clazz == null || unacceptableExceptions.contains(name)) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); if (wrap) { List> wrapperClassesList = new ArrayList<>(); if (cachedWrapperClasses != null) { wrapperClassesList.addAll(cachedWrapperClasses); wrapperClassesList.sort(WrapperComparator.COMPARATOR); Collections.reverse(wrapperClassesList); } if (CollectionUtils.isNotEmpty(wrapperClassesList)) { for (Class wrapperClass : wrapperClassesList) { Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class); if (wrapper == null || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } } } initExtension(instance); return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } } private boolean containsExtension(String name) { return getExtensionClasses().containsKey(name); } private T injectExtension(T instance) { if (objectFactory == null) { return instance; } try { for (Method method : instance.getClass().getMethods()) { if (!isSetter(method)) { continue; } /* * Check {@link DisableInject} to see if we need autowire injection for this property */ if (method.getAnnotation(DisableInject.class) != null) { continue; } Class pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) { continue; } /* * Check {@link Inject} to see if we need auto-injection for this property * {@link Inject#enable} == false will skip inject property phase * {@link Inject#InjectType#ByName} default inject by name */ String property = getSetterProperty(method); Inject inject = method.getAnnotation(Inject.class); if (inject == null) { injectValue(instance, method, pt, property); } else { if (!inject.enable()) { continue; } if (inject.type() == Inject.InjectType.ByType) { injectValue(instance, method, pt, null); } else { injectValue(instance, method, pt, property); } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; } private void injectValue(T instance, Method method, Class pt, String property) { try { Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } private void initExtension(T instance) { if (instance instanceof Lifecycle) { Lifecycle lifecycle = (Lifecycle) instance; lifecycle.initialize(); } } /** * get properties name for setter, for instance: setVersion, return "version" *

* return "", if setter name with length less than 3 */ private String getSetterProperty(Method method) { return method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; } /** * return true if and only if: *

* 1, public *

* 2, name starts with "set" *

* 3, only has one parameter */ private boolean isSetter(Method method) { return method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers()); } private Class getExtensionClass(String name) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (name == null) { throw new IllegalArgumentException("Extension name == null"); } return getExtensionClasses().get(name); } private Map> getExtensionClasses() { Map> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; } /** * synchronized in getExtensionClasses */ private Map> loadExtensionClasses() { cacheDefaultExtensionName(); Map> extensionClasses = new HashMap<>(); for (LoadingStrategy strategy : strategies) { loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); } return extensionClasses; } /** * extract and cache default extension name if exists */ private void cacheDefaultExtensionName() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation == null) { return; } String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); if (names.length > 1) { throw new IllegalStateException("More than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); } if (names.length == 1) { cachedDefaultName = names[0]; } } } private void loadDirectory(Map> extensionClasses, String dir, String type) { loadDirectory(extensionClasses, dir, type, false, false); } private void loadDirectory(Map> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) { String fileName = dir + type; try { Enumeration urls = null; ClassLoader classLoader = findClassLoader(); // try to load from ExtensionLoader's ClassLoader first if (extensionLoaderClassLoaderFirst) { ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader(); if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) { urls = extensionLoaderClassLoader.getResources(fileName); } } if (urls == null || !urls.hasMoreElements()) { if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages); } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", description file: " + fileName + ").", t); } } private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL, boolean overridden, String... excludedPackages) { try { try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) { String line; String clazz = null; while ((line = reader.readLine()) != null) { final int ci = line.indexOf('#'); if (ci >= 0) { line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); if (i > 0) { name = line.substring(0, i).trim(); clazz = line.substring(i + 1).trim(); } else { clazz = line; } if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) { loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden); } } catch (Throwable t) { IllegalStateException e = new IllegalStateException( "Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); } } private boolean isExcluded(String className, String... excludedPackages) { if (excludedPackages != null) { for (String excludePackage : excludedPackages) { if (className.startsWith(excludePackage + ".")) { return true; } } } return false; } private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name, boolean overridden) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface."); } if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz, overridden); } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { clazz.getConstructor(); if (StringUtils.isEmpty(name)) { name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException( "No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { cacheActivateClass(clazz, names[0]); for (String n : names) { cacheName(clazz, n); saveInExtensionClass(extensionClasses, clazz, n, overridden); } } } } /** * cache name */ private void cacheName(Class clazz, String name) { if (!cachedNames.containsKey(clazz)) { cachedNames.put(clazz, name); } } /** * put clazz in extensionClasses */ private void saveInExtensionClass(Map> extensionClasses, Class clazz, String name, boolean overridden) { Class c = extensionClasses.get(name); if (c == null || overridden) { extensionClasses.put(name, clazz); } else if (c != clazz) { // duplicate implementation is unacceptable unacceptableExceptions.add(name); String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName(); logger.error(duplicateMsg); throw new IllegalStateException(duplicateMsg); } } /** * cache Activate class which is annotated with Activate *

* for compatibility, also cache class with old alibaba Activate annotation */ private void cacheActivateClass(Class clazz, String name) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(name, activate); } else { // support com.alibaba.dubbo.common.extension.Activate com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class); if (oldActivate != null) { cachedActivates.put(name, oldActivate); } } } /** * cache Adaptive class which is annotated with Adaptive */ private void cacheAdaptiveClass(Class clazz, boolean overridden) { if (cachedAdaptiveClass == null || overridden) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getName() + ", " + clazz.getName()); } } /** * cache wrapper class *

* like: ProtocolFilterWrapper, ProtocolListenerWrapper */ private void cacheWrapperClass(Class clazz) { if (cachedWrapperClasses == null) { cachedWrapperClasses = new ConcurrentHashSet<>(); } cachedWrapperClasses.add(clazz); } /** * test if clazz is a wrapper class *

* which has Constructor with given class type as its only argument */ private boolean isWrapperClass(Class clazz) { try { clazz.getConstructor(type); return true; } catch (NoSuchMethodException e) { return false; } } @SuppressWarnings("deprecation") private String findAnnotationName(Class clazz) { org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class); if (extension != null) { return extension.value(); } String name = clazz.getSimpleName(); if (name.endsWith(type.getSimpleName())) { name = name.substring(0, name.length() - type.getSimpleName().length()); } return name.toLowerCase(); } @SuppressWarnings("unchecked") private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } } private Class getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); } private Class createAdaptiveExtensionClass() { String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); } @Override public String toString() { return this.getClass().getName() + "[" + type.getName() + "]"; } }