background

Next, let’s look at adaptive extension points that correspond to the code

if (clazz.isAnnotationPresent(Adaptive.class)) {
        cacheAdaptiveClass(clazz);

Copy the code

The Adaptive annotation can be applied to either a class or a method, but classes have more role and control over methods in terms of scope

Class adaptive extension point

So let’s go back to our original demo

public static void main(String[] args) {
    ExtensionLoader<Job> extensionLoader = ExtensionLoader.getExtensionLoader(Job.class);
    Job program = extensionLoader.getExtension("program");
    program.play();
}

Copy the code

Let’s imagine a scenario, in a distributed scenario, where we want to get some dynamically delivered value, but there are multiple registries, and our code will write this. Of course, the scenario I simulated is relatively simple, but there are more complex scenarios in practice

//file:com.poizon.study.provider.spi.ConfigCenter
apollo=com.poizon.study.provider.spi.ApolloConfigCenter
nacos=com.poizon.study.provider.spi.NacosConfigCenter
//SPITest.java
public static void main(String[] args) {
    ExtensionLoader<ConfigCenter> extensionLoader = ExtensionLoader.getExtensionLoader(ConfigCenter.class);
    ConfigCenter configCenter = null;
    if (apollo) {
        configCenter = extensionLoader.getExtension("apollo");
    } else {
        configCenter = extensionLoader.getExtension("nacos");
    }
    configCenter.get("key");
}

Copy the code

Ok, let’s go ahead and extract the code into util

public static void main(String[] args) { ExtensionLoader<ConfigCenter> extensionLoader = ExtensionLoader.getExtensionLoader(ConfigCenter.class); String value = ConfigCenterUtil.get("key", extensionLoader); } //ConfigCenterUtil.java public class ConfigCenterUtil { private static boolean apollo; public static String get(String key, ExtensionLoader<ConfigCenter> extensionLoader) { ConfigCenter configCenter = null; if (apollo) { configCenter = extensionLoader.getExtension("apollo"); } else { configCenter = extensionLoader.getExtension("nacos"); } return configCenter.get("key"); }}Copy the code

Can this utility class be registered as an implementation class for the ConfigCenter interface

//file:com.poizon.study.provider.spi.ConfigCenter
apollo=com.poizon.study.provider.spi.ApolloConfigCenter
nacos=com.poizon.study.provider.spi.NacosConfigCenter
util=com.poizon.study.provider.spi.ConfigCenterUtil

public class ConfigCenterUtil implements ConfigCenter{
    private static boolean apollo;
    @Override
    public String get(String key) {
        ExtensionLoader<ConfigCenter> extensionLoader = ExtensionLoader.getExtensionLoader(ConfigCenter.class);
        ConfigCenter configCenter = null;
        if (apollo) {
            configCenter = extensionLoader.getExtension("apollo");
        } else {
            configCenter = extensionLoader.getExtension("nacos");
        }
        return configCenter.get("key");
    }
}

public static void main(String[] args) {
    ExtensionLoader<ConfigCenter> extensionLoader = ExtensionLoader.getExtensionLoader(ConfigCenter.class);
    ConfigCenter util = extensionLoader.getExtension("util");
    util.get("key");
}

Copy the code

In this way, we mask all the implementation details of getting the configuration to the ConfigCenterUtil implementation class. The top layer does not need the relational implementation. The purpose of this class can be called an Adaptive extension class, and there is another way to write it in dubbo. ExtensionLoader also provides the getAdaptiveExtension method, and this value is assigned by this code, and an interface can only have one adaptive extension point, multiple errors will be reported, and of course will not be thrown, because dubbo encapsulates the error into the map. The error stack will only be spit out if the appropriate implementation class cannot be found

If (clazz. IsAnnotationPresent (Adaptive. Class)) {/ / do skip this first analysis cacheAdaptiveClass (clazz); } private void cacheAdaptiveClass(Class<? > clazz) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (! cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException(); }}Copy the code

Finally our version becomes

@Adaptive public class ConfigCenterUtil implements ConfigCenter{ //......... public static void main(String[] args) { ExtensionLoader<ConfigCenter> extensionLoader = ExtensionLoader.getExtensionLoader(ConfigCenter.class); ConfigCenter util = extensionLoader.getAdaptiveExtension(); String key = util.get("key"); System.out.println(key); // print: nacos key}Copy the code

The dubbo framework also has a class ExtensionFactory implementation, which I’ll cover in more detail later.

Method ADAPTS extension points

Like the above implementation, it is very complex, we will extend a lot of class, will be implemented, the answer is not, we are in for a scene, login, Internet software have mushroomed as much now, login way also constantly expanding from my personal experience, from mobile phone verification code login to WeChat facebook and other social login, Let’s go back to the phone number local login, of course there will be more login methods in the future how can we extend our code better?

//com.poizon.study.provider.spi.Login weixin=com.poizon.study.provider.spi.WeixinLogin phone=com.poizon.study.provider.spi.PhoneLogin public static void main(String[] args) { String loginType = "weixin"; ExtensionLoader<Login> extensionLoader = ExtensionLoader.getExtensionLoader(Login.class); if (loginType.equals("weixin")) { extensionLoader.getExtension("weixin").doLogin(); } else if (loginType.equals("phone")) { extensionLoader.getExtension("phone").doLogin(); }}Copy the code

The code above is very simple, and it’s very easy to extend if else; This is a method that experienced developers will choose to upgrade to factory mode. Let’s try it. Before we try it, we introduce the URI, pass the login method to the factory as a URI, and use this form to eliminate branches.

public static void main(String[] args) {
    String loginType = "weixin";
    URL url = new URL(loginType, null, (Integer) null);
    doLogin(url);
}

public static void doLogin(URL url) {
    ExtensionLoader<Login> extensionLoader = ExtensionLoader.getExtensionLoader(Login.class);
    String protocol = url.getProtocol();
    if (StringUtils.isEmpty(protocol)) {
        protocol = "hupu";
    }
    extensionLoader.getExtension(protocol).doLogin();
}

Copy the code

If we look at the code in doLogin, if we don’t set the default value “hupu”, can we think that it has nothing to do with the interface, but is just a specification to find the implementation class? Yes, Dubbo also finds the implementation class in this way, exposing @Adaptive to the user to set. Let’s look at writing using the Dubbo adaptive extension point method

@SPI public interface Login { @Adaptive("protocol") boolean doLogin(URL url); } public static void main(String[] args) { String loginType = "weixin"; URL url = new URL(loginType, "8888", 80); ExtensionLoader<Login> extensionLoader = ExtensionLoader.getExtensionLoader(Login.class); extensionLoader.getAdaptiveExtension().doLogin(url); // Print: do weixin login}Copy the code

“Login$Adapter” is a class we haven’t defined yet. We guess that the implementation mode should be a dynamic proxy, so let’s have a look

Follow getAdaptiveExtension()

Public T getAdaptiveExtension() {// omit.. instance = createAdaptiveExtension(); return (T) instance; } private T createAdaptiveExtension() { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } private Class<? > getAdaptiveExtensionClass() { getExtensionClasses(); 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); }Copy the code

Compiler.compile () was found at last. To compile code code into Java objects, Compiler is also an extension point, implemented as Javassist () by default, and can be set with <dubbo: Application Compiler =” JDK “/>

@spi ("javassist") // Default extension javassist public interface Compiler {Class<? > compile(String code, ClassLoader classLoader); }Copy the code

Dubbo also recommends using javassist bytecode as a more efficient way, as you can see in JDK 1.6

Instead of going into the compilation process, let’s look at how the value of this code variable is generated.

//org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate public String generate() { StringBuilder code = new StringBuilder(); code.append(generatePackageInfo()); code.append(generateImports()); code.append(generateClassDeclaration()); Method[] methods = type.getMethods(); for (Method method : methods) { code.append(generateMethod(method)); } code.append("}"); return code.toString(); } //org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generateMethod 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); } //org.... common.extension.AdaptiveClassCodeGenerator#generateMethodContent 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); if (urlTypeIndex ! = -1) { code.append(generateUrlNullCheck(urlTypeIndex)); } else {/ / to explain in the following code. Append (generateUrlAssignmentIndirectly (method)); String[] value = getMethodEvalue (adaptiveAnnotation); boolean hasInvocation = hasInvocationArgument(method); code.append(generateInvocationArgumentNullCheck(method)); / / to explain in the following code. Append (generateExtNameAssignment (value, hasInvocation)); Throw new Exception(); code.append(generateExtNameNullCheck(value)); / / to explain in the following code. Append (generateExtensionAssignment ()); / / packaging return code. Append (generateReturnAndInvocation (method)); } return code.toString(); } private int getUrlTypeIndex(Method method) { int urlTypeIndex = -1; // doLogin uses the URL as the first parameter Class<? >[] pts = method.getParameterTypes(); for (int i = 0; i < pts.length; ++i) { if (pts[i].equals(URL.class)) { urlTypeIndex = I; break; } } return urlTypeIndex; } //org.... common.extension.AdaptiveClassCodeGenerator#generateUrlAssignmentIndirectly private String generateUrlAssignmentIndirectly(Method method) { Class<? >[] pts = method.getParameterTypes(); For (int I = 0; int I = 0; 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) { return generateGetUrlNullCheck(i, pts[i], name); }}}} # mon. This method is too important to highlight the org.apache.dubbo.com extension. AdaptiveClassCodeGenerator# generateExtNameAssignment private String generateExtNameAssignment(String[] value, Boolean hasInvocation) {// The protocol String getNameCode = null; for (int i = value.length - 1; i >= 0; -- I) {//defaultExtName = if (null! = defaultExtName) { if (!" protocol".equals(value[i])) { if (hasInvocation) { getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); } else {// If the extension point is not protocol, use url.getParameter to get it. // This is also supported. loginType); //@Adaptive("loginType") //boolean doLogin(URL url); getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName); }} else {// If key is protocol, 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()"; Return string. format(CODE_EXT_NAME_ASSIGNMENT, getNameCode); } private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s; \n"; / / this way or through regular assembly call ExtensionLoader. GetExtensionLoader extension point (extName) get really wants to call private String generateExtensionAssignment () { return String.format(CODE_EXTENSION_ASSIGNMENT, type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); } static String CODE_EXTENSION_ASSIGNMENT = "%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName); \n";Copy the code

Finally, let’s dubug look at the code generated by the Login class

Test your assumptions step by step.

conclusion

The source code section is mainly about debugging more than 10 times. Adaptive extension points are written here, followed by analysis of dependency injection in Dubbo.