一、什么是可扩展性
可扩展性是一种设计理念,代表了我们对未来的一种预想,我们希望在现有的架构或设计基础上,当未来某些方面发生变化的时候,我们能够以最小的改动来适应这种变化。
二、可扩展性的优点
可扩展性的优点主要表现模块之间解耦,它符合开闭原则,对扩展开放,对修改关闭。当系统增加新功能时,不需要对现有系统的结构和代码进行修改,仅仅新增一个扩展即可。
三、扩展实现方式
一般来说,系统会采用 Factory、IoC、OSGI 等方式管理扩展(插件)生命周期。考虑到 Dubbo 的适用面,不想强依赖 Spring 等 IoC 容器。 而自己造一个小的 IoC 容器,也觉得有点过度设计,所以选择最简单的 Factory 方式管理扩展(插件)。在 Dubbo 中,所有内部实现和第三方实现都是平等的。
四、Dubbo 扩展的特性
- 按需加载。Dubbo 的扩展能力不会一次性实例化所有实现,而是用那个扩展类则实例化那个扩展类,减少资源浪费。
- 增加扩展类的 IOC 能力。Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类,而是在此基础上更进一步,如果该扩展类的属性依赖其他对象,则 Dubbo 会自动地完成该依赖对象的注入功能。
- 增加扩展类的 AOP 能力。Dubbo 扩展能力会自动地发现扩展类的包装类,完成包装类的构造,增强扩展类的功能。
- 具备动态选择扩展实现的能力。Dubbo 扩展会基于参数,在运行时动态选择对应的扩展类,提高了 Dubbo 的扩展能力。
- 可以对扩展实现进行排序。能够基于用户需求,指定扩展实现的执行顺序。
- 提供扩展点的 Adaptive 能力。该能力可以使得一些扩展类在 consumer 端生效,一些扩展类在 provider 端生效。
五、Dubbo 扩展加载流程
主要步骤为 4 个:
- 读取并解析配置文件
- 缓存所有扩展实现
- 基于用户执行的扩展名,实例化对应的扩展实现
- 进行扩展实例属性的 IOC 注入以及实例化扩展的包装类,实现 AOP 特性
六、Dubbo 扩展的应用
Dubbo 扩展能力使得 Dubbo 项目很方便地切分成一个一个的子模块,实现热插拔特性。用户完全可以基于自身需求,替换 Dubbo 原生实现,来满足自身业务需求。
七、源码分析
我们从流程的最上层ExtensionAccessor类来进行分析
其中比较核心的方法有3个:
getExtensionLoader:获取扩展加载组件
getExtension:获取扩展实现类对象
getAdaptiveExtension:获取自适应扩展实现类对象
1、获取扩展加载组件
这一块比较重要,我就直接贴代码了,其中最主要的是构建一个实现类,我们就直接跳到createExtensionLoader方法中去
/**
* 获取接口的ExtensionLoader,在去通过ExtensionLoader获取扩展实现类
* 第一步:先去缓存获取
* 第二步:缓存没有则会判断使用范围是否为SELF,是则尝试自己直接创建
* 第三步:尝试通过父组件获取
* 第四步:最后都没有获取则直接创建
* @param type 加了@SPI注解的接口class
* @param <T>
* @return
*/
@Override
public <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
checkDestroyed();
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() + "!");
}
// 1. find in local cache
/** 按照接口的class类型去获取extensionLoadersMap中的缓存数据 */
ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoadersMap.get(type);
/** 根据接口的class类型去获取接口的使用范围 */
ExtensionScope scope = extensionScopeMap.get(type);
if (scope == null) {
/** 如果为空获取接口class里打的SPI注解,去获取SPI中的使用范围赋值并保存至extensionScopeMap缓存 */
SPI annotation = type.getAnnotation(SPI.class);
scope = annotation.scope();
extensionScopeMap.put(type, scope);
}
/** 如果获取出来的ExtensionLoader为空,并且使用范围为SELF */
if (loader == null && scope == ExtensionScope.SELF) {
// create an instance in self scope
/** 构建ExtensionLoader */
loader = createExtensionLoader0(type);
}
// 2. find in parent
/** 如果loader为空,则说明使用范围不为SELF,上一个判断逻辑没有进去 */
if (loader == null) {
/** 判断父组件是否为空,不为空,则依赖父组件进行获取ExtensionLoader */
if (this.parent != null) {
loader = this.parent.getExtensionLoader(type);
}
}
// 3. create it
/** 最后面还是没有则直接创建 */
if (loader == null) {
loader = createExtensionLoader(type);
}
return loader;
}
在extensionLoadersMap中添加新构建的ExtensionLoader,并获取后返回。
ExtensionLoader初始化方法
2、获取扩展实现类对象
先从缓存中获取实现类的对象,如果为空则调用createExtension方法进行构建
这一块代码是这个里面最重要的一块,这个里面包含了获取类对象,根据类对象获取对象实例,对实例对象进行依赖注入,生成包装类等;
private T createExtension(String name, boolean wrap) {
/**
* 根据名称从加载出来的那些实现类里拿出来一个具体的class
* 根据接口拿到的实现类,在构建实现类的对象注入给接口类型的变量
* 通过指定的配置文件读取和解析去拿到ExtensionClasses,通过实现类的短名称去获取对应的class对象
*/
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
/** 获取缓存中的数据 */
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
/**
* 如果缓存没有命中则会去构建一个实现类的对象
* 其实就是通过反射技术拿到一个对应的实现类的对象实例
*/
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
/** 在根据clazz获取实例对象 */
instance = (T) extensionInstances.get(clazz);
/** 对实例对象进行初始化之前的后处理 */
instance = postProcessBeforeInitialization(instance, name);
/** 依赖注入:dubbo SPI机制也扮演了类试Spring容器的机制,会托管你的实现类的对象,对其进行依赖注入 */
injectExtension(instance);
/** 对实例对象进行初始化之后的后处理 */
instance = postProcessAfterInitialization(instance, name);
}
/** 如果需要对这个实现类的实例进行包装则进行后续处理 */
if (wrap) {
List<Class<?>> 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 wrapper = wrapperClass.getAnnotation(Wrapper.class);
boolean match = (wrapper == null) ||
((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
!ArrayUtils.contains(wrapper.mismatches(), name));
/** 如果需要通过Wrapper进行包装 */
if (match) {
/** 将instance做一层包装 */
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
/** 进行后处理操作 */
instance = postProcessAfterInitialization(instance, name);
}
}
}
}
// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
获取类对象
将各种数据缓存到入参extensionClasses的map数据结构中去
数据加载好了之后就会直接返回这个map,再从map中获取数据
再根据类对象获取对象实例,先从缓存中获取,如果为空则调用createExtensionInstance进行构建,构建完成之后放入extensionInstances进行缓存,在从缓存中获取
根据类对象创建的对象实例
获取完实例对象之后再进行依赖注入
依赖注入之后会判断实例对象是否需要进行包装,如果需要包装则将实例对象传入进去,作为属性值在生成一个包装类MockClusterWrapper
3、获取自适应扩展实现类对象
其中会有许多和获取扩展实现类对象一样的代码,我们只分析不同的代码流程
这里我们发现除了获取自适应的扩展类之外,其余的代码和之前都是一样的,实例化,依赖注入,初始化等,所以我们这里就只看getAdaptiveExtensionClass方法了。
这里就会根据一定的规则去通过代码去动态生成相应的类,后面我会贴这个类的所有代码细节,相当的繁琐。我们大致看一眼知道有这么一回事就可以了,在直接将类返回进行实例化,依赖注入等,在进行返回的;
public class AdaptiveClassCodeGenerator {
private static final Logger logger = LoggerFactory.getLogger(AdaptiveClassCodeGenerator.class);
private static final String CLASSNAME_INVOCATION = "org.apache.dubbo.rpc.Invocation";
private static final String CODE_PACKAGE = "package %s;\n";
private static final String CODE_IMPORTS = "import %s;\n";
private static final String CODE_CLASS_DECLARATION = "public class %s$Adaptive implements %s {\n";
private static final String CODE_METHOD_DECLARATION = "public %s %s(%s) %s {\n%s}\n";
private static final String CODE_METHOD_ARGUMENT = "%s arg%d";
private static final String CODE_METHOD_THROWS = "throws %s";
private static final String CODE_UNSUPPORTED = "throw new UnsupportedOperationException(\"The method %s of interface %s is not adaptive method!\");\n";
private static final String CODE_URL_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"url == null\");\n%s url = arg%d;\n";
private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s;\n";
private static final String CODE_EXT_NAME_NULL_CHECK = "if(extName == null) "
+ "throw new IllegalStateException(\"Failed to get extension (%s) name from url (\" + url.toString() + \") use keys(%s)\");\n";
private static final String CODE_INVOCATION_ARGUMENT_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"invocation == null\"); "
+ "String methodName = arg%d.getMethodName();\n";
private static final String CODE_SCOPE_MODEL_ASSIGNMENT = "ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), %s.class);\n";
private static final String CODE_EXTENSION_ASSIGNMENT = "%s extension = (%<s)scopeModel.getExtensionLoader(%s.class).getExtension(extName);\n";
private static final String CODE_EXTENSION_METHOD_INVOKE_ARGUMENT = "arg%d";
private final Class<?> type;
private String defaultExtName;
public AdaptiveClassCodeGenerator(Class<?> type, String defaultExtName) {
this.type = type;
this.defaultExtName = defaultExtName;
}
/**
* test if given type has at least one method annotated with <code>Adaptive</code>
*/
private boolean hasAdaptiveMethod() {
return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class));
}
/**
* generate and return class code
*/
public String generate() {
return this.generate(false);
}
/**
* generate and return class code
* @param sort - whether sort methods
*/
public String generate(boolean sort) {
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
/** 动态拼接类代码的字符串 */
StringBuilder code = new StringBuilder();
/** 根据接口的包名生成对应的包名 */
code.append(generatePackageInfo());
/** 生成对应的导入信息 */
code.append(generateImports());
/** 生成类声明 */
code.append(generateClassDeclaration());
Method[] methods = type.getMethods();
if (sort) {
/** 对方法进行排序 */
Arrays.sort(methods, Comparator.comparing(Method::toString));
}
for (Method method : methods) {
/** 生成对应的方法 */
code.append(generateMethod(method));
}
code.append('}');
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
/**
* generate package info
*/
private String generatePackageInfo() {
return String.format(CODE_PACKAGE, type.getPackage().getName());
}
/**
* generate imports
*/
private String generateImports() {
StringBuilder builder = new StringBuilder();
builder.append(String.format(CODE_IMPORTS, ScopeModel.class.getName()));
builder.append(String.format(CODE_IMPORTS, ScopeModelUtil.class.getName()));
return builder.toString();
}
/**
* generate class declaration
*/
private String generateClassDeclaration() {
return String.format(CODE_CLASS_DECLARATION, type.getSimpleName(), type.getCanonicalName());
}
/**
* generate method not annotated with Adaptive with throwing unsupported exception
*/
private String generateUnsupported(Method method) {
return String.format(CODE_UNSUPPORTED, method, type.getName());
}
/**
* get index of parameter with type URL
*/
private int getUrlTypeIndex(Method method) {
int urlTypeIndex = -1;
Class<?>[] pts = method.getParameterTypes();
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
return urlTypeIndex;
}
/**
* generate method declaration
*/
private String generateMethod(Method method) {
String methodReturnType = method.getReturnType().getCanonicalName();
String methodName = method.getName();
String methodContent = generateMethodContent(method);
String methodArgs = generateMethodArguments(method);
String methodThrows = generateMethodThrows(method);
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
/**
* generate method arguments
*/
private String generateMethodArguments(Method method) {
Class<?>[] pts = method.getParameterTypes();
return IntStream.range(0, pts.length)
.mapToObj(i -> String.format(CODE_METHOD_ARGUMENT, pts[i].getCanonicalName(), i))
.collect(Collectors.joining(", "));
}
/**
* generate method throws
*/
private String generateMethodThrows(Method method) {
Class<?>[] ets = method.getExceptionTypes();
if (ets.length > 0) {
String list = Arrays.stream(ets).map(Class::getCanonicalName).collect(Collectors.joining(", "));
return String.format(CODE_METHOD_THROWS, list);
} else {
return "";
}
}
/**
* generate method URL argument null check
*/
private String generateUrlNullCheck(int index) {
return String.format(CODE_URL_NULL_CHECK, index, URL.class.getName(), index);
}
/**
* generate method content
*/
private String generateMethodContent(Method method) {
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
return generateUnsupported(method);
} else {
int urlTypeIndex = getUrlTypeIndex(method);
// found parameter in URL type
if (urlTypeIndex != -1) {
// Null Point check
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// did not find parameter in URL type
code.append(generateUrlAssignmentIndirectly(method));
}
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
boolean hasInvocation = hasInvocationArgument(method);
code.append(generateInvocationArgumentNullCheck(method));
code.append(generateExtNameAssignment(value, hasInvocation));
// check extName == null?
code.append(generateExtNameNullCheck(value));
code.append(generateScopeModelAssignment());
code.append(generateExtensionAssignment());
// return statement
code.append(generateReturnAndInvocation(method));
}
return code.toString();
}
/**
* generate code for variable extName null check
*/
private String generateExtNameNullCheck(String[] value) {
return String.format(CODE_EXT_NAME_NULL_CHECK, type.getName(), Arrays.toString(value));
}
/**
* generate extName assigment code
*/
private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
// TODO: refactor it
String getNameCode = null;
for (int i = value.length - 1; i >= 0; --i) {
if (i == value.length - 1) {
if (null != defaultExtName) {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
}
} else {
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
}
} else {
getNameCode = "url.getProtocol()";
}
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
}
} else {
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
}
return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}
/**
* @return
*/
private String generateScopeModelAssignment() {
return String.format(CODE_SCOPE_MODEL_ASSIGNMENT, type.getName());
}
private String generateExtensionAssignment() {
return String.format(CODE_EXTENSION_ASSIGNMENT, type.getName(), type.getName());
}
/**
* generate method invocation statement and return it if necessary
*/
private String generateReturnAndInvocation(Method method) {
String returnStatement = method.getReturnType().equals(void.class) ? "" : "return ";
String args = IntStream.range(0, method.getParameters().length)
.mapToObj(i -> String.format(CODE_EXTENSION_METHOD_INVOKE_ARGUMENT, i))
.collect(Collectors.joining(", "));
return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args);
}
/**
* test if method has argument of type <code>Invocation</code>
*/
private boolean hasInvocationArgument(Method method) {
Class<?>[] pts = method.getParameterTypes();
return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName()));
}
/**
* generate code to test argument of type <code>Invocation</code> is null
*/
private String generateInvocationArgumentNullCheck(Method method) {
Class<?>[] pts = method.getParameterTypes();
return IntStream.range(0, pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName()))
.mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i))
.findFirst().orElse("");
}
/**
* get value of adaptive annotation or if empty return splitted simple name
*/
private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) {
String[] value = adaptiveAnnotation.value();
// value is not set, use the value generated from class name as the key
if (value.length == 0) {
String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
value = new String[]{splitName};
}
return value;
}
/**
* get parameter with type <code>URL</code> from method parameter:
* <p>
* test if parameter has method which returns type <code>URL</code>
* <p>
* if not found, throws IllegalStateException
*/
private String generateUrlAssignmentIndirectly(Method method) {
Class<?>[] pts = method.getParameterTypes();
Map<String, Integer> getterReturnUrl = new HashMap<>();
// find URL getter method
for (int i = 0; i < pts.length; ++i) {
for (Method m : pts[i].getMethods()) {
String name = m.getName();
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
getterReturnUrl.put(name, i);
}
}
}
if (getterReturnUrl.size() <= 0) {
// getter method not found, throw
throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}
Integer index = getterReturnUrl.get("getUrl");
if (index != null) {
return generateGetUrlNullCheck(index, pts[index], "getUrl");
} else {
Map.Entry<String, Integer> entry = getterReturnUrl.entrySet().iterator().next();
return generateGetUrlNullCheck(entry.getValue(), pts[entry.getValue()], entry.getKey());
}
}
/**
* 1, test if argi is null
* 2, test if argi.getXX() returns null
* 3, assign url with argi.getXX()
*/
private String generateGetUrlNullCheck(int index, Class<?> type, String method) {
// Null point check
StringBuilder code = new StringBuilder();
code.append(String.format("if (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");\n",
index, type.getName()));
code.append(String.format("if (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");\n",
index, method, type.getName(), method));
code.append(String.format("%s url = arg%d.%s();\n", URL.class.getName(), index, method));
return code.toString();
}
}