These days leave at home, just nothing can be crazy output, originally wanted to write DUBBO source code analysis, but found that write DUBBO source code too much, so find a write not so much framework, so choose SOFARPC this framework.
SOFARPC is an open source RPC framework of Ant Financial. Compared with DUBBO, SOFARPC does not have so much historical burden, and its code is more concise, its design ideas are clearer, and it is easier to understand the code.
So why rewrite the native SPI? The official explanation is as follows:
- According to the need to load
- You can have aliases
- You can have priorities for sorting and overwriting
- You can control whether it is a singleton
- You can use encoding in certain scenarios
- You can specify the extension configuration location
- Other extension points can be excluded
The whole process is as follows:
Take ConsumerBootstrap as an example:
Start with an abstract class:
@Extensible(singleton = false)
public abstract class ConsumerBootstrap<T> {... }Copy the code
Specify the extension implementation class:
@Extension("sofa")
public class DefaultConsumerBootstrap<T> extends ConsumerBootstrap<T> {... }Copy the code
Extension description file meta-inf/services/sofa – RPC/com. Alipay. Sofa. RPC. The bootstrap. ConsumerBootstrap
sofa=com.alipay.sofa.rpc.bootstrap.DefaultConsumerBootstrap
Copy the code
When these preparations are complete, call them directly.
ConsumerBootstrap sofa = ExtensionLoaderFactory.getExtensionLoader(ConsumerBootstrap.class).getExtension("sofa");
Copy the code
Let’s take a look at the source code for ExtensionLoaderFactory
/** * All extension loader {Class: ExtensionLoader} *
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) {
// if you can't get it, lock it
synchronized (ExtensionLoaderFactory.class) {
// Prevent other threads from operating on get again
loader = LOADER_MAP.get(clazz);
if (loader == null) {
loader = newExtensionLoader<T>(clazz, listener); LOADER_MAP.put(clazz, loader); }}}return loader;
}
Copy the code
Then we look at the constructor of the ExtensionLoader class
protected ExtensionLoader(Class<T> interfaceClass, boolean autoLoad, ExtensionLoaderListener<T> listener) {
// If closing is being performed, the property is empty 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;
}
// The interface is empty, neither an interface nor an abstract class
if (interfaceClass == null| |! (interfaceClass.isInterface() || Modifier.isAbstract(interfaceClass.getModifiers()))) {throw new IllegalArgumentException("Extensible class must be interface or abstract class!");
}
// The name of the currently loaded interface class
this.interfaceClass = interfaceClass;
// Interface name
this.interfaceName = ClassTypeUtils.getTypeStr(interfaceClass);
this.listener = listener;
// Interface must have Extensible annotations
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 null
this.factory = extensible.singleton() ? new ConcurrentHashMap<String, T>() : null;
// This property contains all the implementation classes of this 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 the file according to the pathloadFromFile(path); }}}Copy the code
Get all the extension point loading paths and go to loadFromFile to load the file
protected synchronized void loadFromFile(String path) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Loading extension of extensible {} from path: {}", interfaceName, path);
}
// By default, if the file name is not specified, the interface name is used
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);
// Multiple files may exist.
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();
}
}
}
}
}
Copy the code
ReadLine reads every line in the prop file, loads the implementation class file, and then adds the file to the All property
protected void readLine(URL url, String line) {
// Read a line in the file and split the line 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];
// Read the implementation class of the 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 if there is an extensible identifier
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 Extension implementation class is not configured with the @extension tag
throw new IllegalArgumentException("Error when load extension of extensible " + interfaceClass +
" from file:" + url + "," + className + "'s alias of @Extension is blank");
}
if (alias == null) {
// spi file is not configured, use code
alias = aliasInCode;
} else {
// spi file configuration is inconsistent with the code
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)."); }}// Interface number is required, implementation class is 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 + "."); }}// Cannot be default or *
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 if there are any names with the same name
ExtensionClass old = all.get(alias);
ExtensionClass<T> extensionClass = null;
if(old ! =null) {
// If the current extension can overwrite another extension with 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 overwrite another extension with the same nameextensionClass = buildClass(extension, implClass, alias); }}// If the old extension is overwritable
else {
if (old.isOverride() && old.getOrder() >= extension.order()) {
// If the override extension is already loaded, load it to the original extension
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, an existing exception is thrown
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 whether there are 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 the new extension excludes the old extension
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())) {
// is 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); }}Copy the code
We’ll go back to that after we load the file
ConsumerBootstrap sofa = ExtensionLoaderFactory.getExtensionLoader(ConsumerBootstrap.class).getExtension("sofa");
Copy the code
Go into 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 {
// If the class is a singleton, 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) {
/ / instantiate
t = extensionClass.getExtInstance();
// Add the singleton class to the factoryfactory.put(alias, t); }}}return t;
} else {
/ / instantiate
returnextensionClass.getExtInstance(); }}}Copy the code
Let’s go to ExtensionClass and look at the getExtInstance method
/** * Server instance objects (reserved only for singletons) * volatile to ensure visibility */
private volatile transient T instance;
/** * returns the server instance object if it is a singleton, or the newly created instance object ** if it is not@paramArgTypes constructor argument type *@paramThe args constructor argument is *@returnExtension point object instance ext Instance */
public T getExtInstance(Class[] argTypes, Object[] args) {
if(clazz ! =null) {
try {
if (singleton) { // If it is a singleton
if (instance == null) {
synchronized (this) {
if (instance == null) {
// Create an instance through reflectioninstance = ClassUtils.newInstanceWithArgs(clazz, argTypes, args); }}}return instance; // Keep the singleton
} else {
// Create an instance through reflection
returnClassUtils.newInstanceWithArgs(clazz, argTypes, args); }}catch (Exception e) {
throw new SofaRpcRuntimeException("create " + clazz.getCanonicalName() + " instance error", e); }}throw new SofaRpcRuntimeException("Class of ExtensionClass is null");
}
Copy the code
After reading SOFARPC’s extension class implementation, I feel that the code is written very clean, the logic is very clear, there are a lot of places to learn, such as thread safety using double-checked locking and volatile to ensure visibility.
The original link: www.cnblogs.com/luozhiyun/p…