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 test 1, which also needs to be activated under this condition.
2) Value in @Activate is the second level of 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 corresponding to 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()));