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:
- 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.
- @ 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.
- 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()));