Links to the original text: https://www.cnblogs.com/luozh...
After leaving home these days, I just can output something crazily. I wanted to write the source code of DUBBO. But I found that there were too many sources of DUBBO. So I found a framework that didn't write so much. So I chose SOFARPC as the framework.
SOFARPC is an open source RPC framework of Ant Golden Clothing. Compared with DUBBO, it has no so many historical burdens, the code is more concise, the design idea is clearer, and the code is easier to understand.
So why rewrite the native SPI? The official explanation is as follows:
- On-demand loading
- Can have aliases
- Priorities can be prioritized for sorting and coverage
- Can control whether it is singleton or not
- Coding can be used in some scenarios
- Extension configuration location can be specified
- Other extension points can be excluded
The whole process is as follows:
Let's take Consumer Bootstrap as an example:
First, there must be an abstract class:
@Extensible(singleton = false) public abstract class ConsumerBootstrap<T> { .... }
Specify an extended implementation class:
@Extension("sofa") public class DefaultConsumerBootstrap<T> extends ConsumerBootstrap<T> { ... }
Extended description file META-INF/services/sofa-rpc/com.alipay.sofa.rpc.bootstrap.ConsumerBootstrap
sofa=com.alipay.sofa.rpc.bootstrap.DefaultConsumerBootstrap
When these preparations are complete, call them directly.
ConsumerBootstrap sofa = ExtensionLoaderFactory.getExtensionLoader(ConsumerBootstrap.class).getExtension("sofa");
Next, let's look at the source code for Extension LoaderFactory
/** * All extension loader {Class : ExtensionLoader} * This map contains all Extension Loader */ private static final ConcurrentMap<Class, ExtensionLoader> LOADER_MAP = new ConcurrentHashMap<Class, ExtensionLoader>(); public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz, ExtensionLoaderListener<T> listener) { ExtensionLoader<T> loader = LOADER_MAP.get(clazz); if (loader == null) { //Lock if not get synchronized (ExtensionLoaderFactory.class) { //Prevent other thread operations from get ting again loader = LOADER_MAP.get(clazz); if (loader == null) { loader = new ExtensionLoader<T>(clazz, listener); LOADER_MAP.put(clazz, loader); } } } return loader; }
Then let's look at the constructor of the Extension Loader class.
protected ExtensionLoader(Class<T> interfaceClass, boolean autoLoad, ExtensionLoaderListener<T> listener) { //If closing is being performed, the property is emptied and returned directly if (RpcRunningState.isShuttingDown()) { this.interfaceClass = null; this.interfaceName = null; this.listener = null; this.factory = null; this.extensible = null; this.all = null; return; } // Interfaces are empty, neither interfaces nor abstract classes if (interfaceClass == null || !(interfaceClass.isInterface() || Modifier.isAbstract(interfaceClass.getModifiers()))) { throw new IllegalArgumentException("Extensible class must be interface or abstract class!"); } //The name of the interface class currently loaded this.interfaceClass = interfaceClass; //Interface name this.interfaceName = ClassTypeUtils.getTypeStr(interfaceClass); this.listener = listener; //Extensible annotations are required on the interface Extensible extensible = interfaceClass.getAnnotation(Extensible.class); if (extensible == null) { throw new IllegalArgumentException( "Error when load extensible interface " + interfaceName + ", must add annotation @Extensible."); } else { this.extensible = extensible; } // If it is a singleton, then factory is not empty this.factory = extensible.singleton() ? new ConcurrentHashMap<String, T>() : null; //Inside this property are all the implementation classes of the interface. this.all = new ConcurrentHashMap<String, ExtensionClass<T>>(); if (autoLoad) { //Get the path to the extension point load List<String> paths = RpcConfigs.getListValue(RpcOptions.EXTENSION_LOAD_PATH); for (String path : paths) { //Load files according to path loadFromFile(path); } } }
Get all the extension point loading paths and go to loadFromFile to load the files.
protected synchronized void loadFromFile(String path) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Loading extension of extensible {} from path: {}", interfaceName, path); } // By default, if no file name is specified, it is the interface name. String file = StringUtils.isBlank(extensible.file()) ? interfaceName : extensible.file().trim(); String fullFileName = path + file; try { ClassLoader classLoader = ClassLoaderUtils.getClassLoader(getClass()); loadFromClassLoader(classLoader, fullFileName); } catch (Throwable t) { if (LOGGER.isErrorEnabled()) { LOGGER.error("Failed to load extension of extensible " + interfaceName + " from path:" + fullFileName, t); } } } protected void loadFromClassLoader(ClassLoader classLoader, String fullFileName) throws Throwable { Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fullFileName) : ClassLoader.getSystemResources(fullFileName); // There may be multiple files. if (urls != null) { while (urls.hasMoreElements()) { // Read a file URL url = urls.nextElement(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Loading extension of extensible {} from classloader: {} and file: {}", interfaceName, classLoader, url); } BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8")); String line; while ((line = reader.readLine()) != null) { readLine(url, line); } } catch (Throwable t) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Failed to load extension of extensible " + interfaceName + " from classloader: " + classLoader + " and file:" + url, t); } } finally { if (reader != null) { reader.close(); } } } } }
Next, enter readLine, which reads each row of records in the prop file, loads the class file of the implementation class and adds the file to all attribute after verification.
protected void readLine(URL url, String line) { //Read a row of records in the file and divide the row with a = sign String[] aliasAndClassName = parseAliasAndClassName(line); if (aliasAndClassName == null || aliasAndClassName.length != 2) { return; } //alias String alias = aliasAndClassName[0]; //Package name String className = aliasAndClassName[1]; // Implementation Class of Read Configuration Class tmp; try { tmp = ClassUtils.forName(className, false); } catch (Throwable e) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Extension {} of extensible {} is disabled, cause by: {}", className, interfaceName, ExceptionUtils.toShortString(e, 2)); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Extension " + className + " of extensible " + interfaceName + " is disabled.", e); } return; } if (!interfaceClass.isAssignableFrom(tmp)) { throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName + " from file:" + url + ", " + className + " is not subtype of interface."); } Class<? extends T> implClass = (Class<? extends T>) tmp; // Check for extensible identifiers Extension extension = implClass.getAnnotation(Extension.class); if (extension == null) { throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName + " from file:" + url + ", " + className + " must add annotation @Extension."); } else { String aliasInCode = extension.value(); if (StringUtils.isBlank(aliasInCode)) { // The extended implementation class has no @Extension tag configured throw new IllegalArgumentException("Error when load extension of extensible " + interfaceClass + " from file:" + url + ", " + className + "'s alias of @Extension is blank"); } if (alias == null) { // There is no configuration in the spi file. alias = aliasInCode; } else { // Inconsistencies between configuration and code in spi files if (!aliasInCode.equals(alias)) { throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName + " from file:" + url + ", aliases of " + className + " are " + "not equal between " + aliasInCode + "(code) and " + alias + "(file)."); } } // Interfaces need to be numbered, implementation classes are not set if (extensible.coded() && extension.code() < 0) { throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName + " from file:" + url + ", code of @Extension must >=0 at " + className + "."); } } // Can't be default sum* if (StringUtils.DEFAULT.equals(alias) || StringUtils.ALL.equals(alias)) { throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName + " from file:" + url + ", alias of @Extension must not \"default\" and \"*\" at " + className + "."); } // Check for homonyms ExtensionClass old = all.get(alias); ExtensionClass<T> extensionClass = null; if (old != null) { // If the current extension can cover other extensions of the same name if (extension.override()) { // If the priority is not as high as the old one, ignore it if (extension.order() < old.getOrder()) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Extension of extensible {} with alias {} override from {} to {} failure, " + "cause by: order of old extension is higher", interfaceName, alias, old.getClazz(), implClass); } } else { if (LOGGER.isInfoEnabled()) { LOGGER.info("Extension of extensible {} with alias {}: {} has been override to {}", interfaceName, alias, old.getClazz(), implClass); } // If the current extension can cover other extensions of the same name extensionClass = buildClass(extension, implClass, alias); } } // If old extensions are covered else { if (old.isOverride() && old.getOrder() >= extension.order()) { // If coverage extensions are loaded, then the original extensions are loaded if (LOGGER.isInfoEnabled()) { LOGGER.info("Extension of extensible {} with alias {}: {} has been loaded, ignore origin {}", interfaceName, alias, old.getClazz(), implClass); } } else { // If it cannot be overridden, throw an exception that already exists throw new IllegalStateException( "Error when load extension of extensible " + interfaceClass + " from file:" + url + ", Duplicate class with same alias: " + alias + ", " + old.getClazz() + " and " + implClass); } } } else { extensionClass = buildClass(extension, implClass, alias); } if (extensionClass != null) { // Check for mutually exclusive extension points for (Map.Entry<String, ExtensionClass<T>> entry : all.entrySet()) { ExtensionClass existed = entry.getValue(); if (extensionClass.getOrder() >= existed.getOrder()) { // New Priority > = Old Priority, check whether new extensions exclude old ones String[] rejection = extensionClass.getRejection(); if (CommonUtils.isNotEmpty(rejection)) { for (String rej : rejection) { existed = all.get(rej); if (existed == null || extensionClass.getOrder() < existed.getOrder()) { continue; } ExtensionClass removed = all.remove(rej); if (removed != null) { if (LOGGER.isInfoEnabled()) { LOGGER.info( "Extension of extensible {} with alias {}: {} has been reject by new {}", interfaceName, removed.getAlias(), removed.getClazz(), implClass); } } } } } else { String[] rejection = existed.getRejection(); if (CommonUtils.isNotEmpty(rejection)) { for (String rej : rejection) { if (rej.equals(extensionClass.getAlias())) { // Excluded by other extensions if (LOGGER.isInfoEnabled()) { LOGGER.info( "Extension of extensible {} with alias {}: {} has been reject by old {}", interfaceName, alias, implClass, existed.getClazz()); return; } } } } } } loadSuccess(alias, extensionClass); } }
After loading the file, we'll go back to it.
ConsumerBootstrap sofa = ExtensionLoaderFactory.getExtensionLoader(ConsumerBootstrap.class).getExtension("sofa");
Enter the getExtension method
public ExtensionClass<T> getExtensionClass(String alias) { return all == null ? null : all.get(alias); } public T getExtension(String alias) { //Get the loaded class from the all attribute ExtensionClass<T> extensionClass = getExtensionClass(alias); if (extensionClass == null) { throw new SofaRpcRuntimeException("Not found extension of " + interfaceName + " named: \"" + alias + "\"!"); } else { //When loading the class, verify whether it is a singleton, and if it is a singleton, then factory is not null if (extensible.singleton() && factory != null) { T t = factory.get(alias); if (t == null) { synchronized (this) { t = factory.get(alias); if (t == null) { //instantiation t = extensionClass.getExtInstance(); //Put it in the factory, and the class of the singleton will be ready next time. No need to recreate it. factory.put(alias, t); } } } return t; } else { //instantiation return extensionClass.getExtInstance(); } } }
Let's go into Extension Class and see the getExtInstance method
/** * Server-side instance objects (reserved only when they are singletons) * Modified with volatile to ensure visibility */ private volatile transient T instance; /** * Get the server-side instance object, return the singleton object if it is singleton, and return the newly created instance object if it is not. * * @param argTypes Constructor parameter type * @param args Constructor parameter values * @return Extended Point Object Instance Extinstance */ public T getExtInstance(Class[] argTypes, Object[] args) { if (clazz != null) { try { if (singleton) { // If it is a singular case if (instance == null) { synchronized (this) { if (instance == null) { //Create instances by reflection instance = ClassUtils.newInstanceWithArgs(clazz, argTypes, args); } } } return instance; // Retention of singletons } else { //Create instances by reflection return ClassUtils.newInstanceWithArgs(clazz, argTypes, args); } } catch (Exception e) { throw new SofaRpcRuntimeException("create " + clazz.getCanonicalName() + " instance error", e); } } throw new SofaRpcRuntimeException("Class of ExtensionClass is null"); }
After looking at the implementation of SOFARPC extension class, I feel that the code is written very neatly and the logic is very clear. There are many things to learn, such as thread security using double check lock and volatile to ensure visibility.