6. SPI mechanism analysis of Dubbo 5-Activate

Keywords: Programming Dubbo

1. Dubbo's @Activate example

@SPI
public interface ActivateExt {
    String echo(String msg);
}
@Activate(group = {"default"})
public class ActivateExtImpl1 implements ActivateExt {
    @Override
    public String echo(String msg) {
        return msg;
    }
}

@Activate(group = {"group1", "group2"})
public class GroupActivateExtImpl implements ActivateExt {

    @Override
    public String echo(String msg) {
        return msg;
    }
}
@Activate(order = 2, group = {"order"})
public class OrderActivateExtImpl1 implements ActivateExt {

    @Override
    public String echo(String msg) {
        return msg;
    }
}

@Activate(order = 1, group = {"order"})
public class OrderActivateExtImpl2 implements ActivateExt {

    @Override
    public String echo(String msg) {
        return msg;
    }
}
@Activate(value = {"value"}, group = {"group"})
public class ValueActivateExtImpl implements ActivateExt {

    @Override
    public String echo(String msg) {
        return msg;
    }
}

New META-INF/dubbo/internal folder and com.alibaba.dubbo.demo.provider.activate.ActivateExt under resources, i.e. fully qualified name of interface, with file contents as follows:

group=com.alibaba.dubbo.demo.provider.activate.impl.GroupActivateExtImpl
key=com.alibaba.dubbo.demo.provider.activate.impl.ValueActivateExtImpl
order1=com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl1
order2=com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl2
com.alibaba.dubbo.demo.provider.activate.impl.ActivateExtImpl1

Following are several unit tests and their test results. Observing the test results, we can draw the following conclusions:

  1. If the group parameter in loader.getActivateExtension(URL url, String[] values, String group) can be matched, it is our choice. ActivateExtImpl1 is matched according to group=default in test1, which also needs to be activated under this condition.
  2. @ Value in Activate is the second-level validation parameter and group is the first level. If the group validation passes, the key parameter in the URL is the same as the value parameter in the class @Activate annotation and the value of the key parameter is not empty, then it will be selected.
  3. The smaller the order, the higher the priority
/**
 * 1
 * class com.alibaba.dubbo.demo.provider.activate.impl.ActivateExtImpl1
 */
@Test
public void test1(){
    ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    List<ActivateExt> list = loader.getActivateExtension(url, new String[]{}, "default");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}
/**
 * 1
 * class com.alibaba.dubbo.demo.provider.activate.impl.GroupActivateExtImpl
 */
@Test
public void test2(){
    ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    List<ActivateExt> list = loader.getActivateExtension(url, new String[]{}, "group1");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}
/**
 * 2
 * class com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl1
 * class com.alibaba.dubbo.demo.provider.activate.impl.ValueActivateExtImpl
 */
@Test
public void test3(){
    ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    // Note that URL reception is used here, not directly url.addParameter()
    url = url.addParameter("value", "test");
    List<ActivateExt> list = loader.getActivateExtension(url, new String[]{"order1", "default"}, "group");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}
/**
 * 2
 * class com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl2
 * class com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl1
 */
@Test
public void test4(){
    ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    List<ActivateExt> list = loader.getActivateExtension(url, new String[]{}, "order");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}

2. Source code analysis

public void test3(){
    ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    // Note that URL reception is used here, not directly url.addParameter()
    url = url.addParameter("value", "test");
    List<ActivateExt> list = loader.getActivateExtension(url, new String[]{"order1", "default"}, "group");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}
public List<T> getActivateExtension(URL url, String[] values, String group) {
    List<T> exts = new ArrayList<T>();
    // Wrap the passed values into List-type names
    List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
    // Packed data does not contain "-default"
    if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
        // Get all extension information for this type of data
        getExtensionClasses();
        for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
            // Get the extended name, and configure the key=value in the configuration file
            String name = entry.getKey();
            // Get annotations
            Activate activate = entry.getValue();

            // To determine whether the group matches, the group is the value passed in by the program, and if it is not set, it returns true to indicate the match.
            // If you set up a need for comparison matching
            if (isMatchGroup(group, activate.group())) {
                // Get the key value, that is, the instance corresponding to the name
                T ext = getExtension(name);
                if (!names.contains(name)
                        && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                        && isActive(activate, url)) {
                    exts.add(ext);
                }
            }
        }
        // sort
        Collections.sort(exts, ActivateComparator.COMPARATOR);
    }

    List<T> usrs = new ArrayList<T>();
    for (int i = 0; i < names.size(); i++) {
        String name = names.get(i);
        if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
            if (Constants.DEFAULT_KEY.equals(name)) {
                // If name is default, add usrs to exts header and empty usrs
                if (!usrs.isEmpty()) {
                    exts.addAll(0, usrs);
                    usrs.clear();
                }
            } else {
                // Get the extension of name and add usrs
                T ext = getExtension(name);
                usrs.add(ext);
            }
        }
    }
    if (!usrs.isEmpty()) {
        exts.addAll(usrs);
    }
    return exts;
}

The cachedActivates used in the above code are assigned in the getExtension Classes () method, for specific reference: SPI Mechanism Analysis of Dubbo 1-SPI Loading class

Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
    cachedActivates.put(names[0], activate);
}
// It's easy to see if the group matches.
private boolean isMatchGroup(String group, String[] groups) {
    if (group == null || group.length() == 0) {
        return true;
    }
    if (groups != null && groups.length > 0) {
        for (String g : groups) {
            if (group.equals(g)) {
                return true;
            }
        }
    }
    return false;
}
// Match the key in url with the value value in the Activate annotation, and the value corresponding to the key cannot be empty
private boolean isActive(Activate activate, URL url) {
    String[] keys = activate.value();
    if (keys.length == 0) {
        return true;
    }
    for (String key : keys) {
        for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
            String k = entry.getKey();
            String v = entry.getValue();
            if ((k.equals(key) || k.endsWith("." + key))
                    && ConfigUtils.isNotEmpty(v)) {
                return true;
            }
        }
    }
    return false;
}

Note that if there is - default in the incoming values in the following form, @Activate through which all matches can be passed will not be activated, only those specified in values can be activated. If "-" is passed in to match the initial extension class name, the extension point will not be activated.

// Output class com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl1
ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
URL url = URL.valueOf("test://localhost/test");
// Note that URL reception is used here, not directly url.addParameter()
url = url.addParameter("value", "test");
List<ActivateExt> list = loader.getActivateExtension(url, new String[]{"order1", "-default"}, "group");
System.out.println(list.size());
list.forEach(item -> System.out.println(item.getClass()));

Posted by putraaridana on Tue, 08 Oct 2019 21:32:49 -0700