什么是 SPI ?
SPI(Service Provider Interface) 是一种服务发现机制, 主要原理是在运行时根据具体参数去查找约定路径(JDK 默认是在/META-INF/services/)下的配置实现类信息。 通过然后类加载机制(ClassLoader)实现对类信息的加载以及后面的实例化,实现黑盒扩展的作用。简单点来说就是在运行时动态指定并加载实现类,实现指定功能点扩展。
推荐连接: 高级开发必须理解的Java中SPI机制
Dubbo SPI 和JDK SPI 的区别
是的,Dubbo并没有沿用JDK内置的SPI机制。 而是自行实现了一套SPI机制,从Dubbo的官方描述来看,Dubbo是对JDK的SPI进行了一次改进。 那这里就有必要说明下。JDK SPI有哪些问题呢? Dubbo 又是如何对其进行改进的呢?
JDK SPI问题
- 需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要的实现。
- 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
- 扩展如果依赖其他的扩展,做不到自动注入和装配
- 不提供类似于Spring的IOC和AOP功能
- 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持
Dubbo SPI的增强
- 通过Map缓存的机制, 对Class类进行缓存,在运行时调用明确名称的类。 获取对应的类信息,进行实例化。 提高了启动性能
- Dubbo SPI 实现了类似IOC 和AOP 的机制。提供了查找和动态扩展的功能。
- Dubbo 也提供了从扩展点容器获取实例对象的功能, 如通过SpringExtensionFactory就实现了通过Spring的容器获取依赖的功能。 可以更好的和其他框架进行集成。
Dubbo SPI示例
代码示例
@SPI
public interface Car {
void test();
}
//宝马
public class BWMCar implements Car {
@Override
public void test() {
System.out.println("hi, 我是宝马车");
}
}
public static void main(String[] args) {
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
Car car = extensionLoader.getExtension("bwm");
car.test();
}
配置示例
在/META-INF/dubbo/目录下创建文件com.wgt.dubbo.samples.spi.demo1.car.Car文件,配置信息如下
bwm=com.wgt.dubbo.samples.spi.demo1.car.BWMCar
输出结果
hi, 我是宝马车
从示例中的来看。 流程时先ExtensionLoader.getExtensionLoader(Class class) 获取到一个ExtensionLoader示例, 然后调用ExtensionLoader的getExtension(String name)方法获取到目标实例, 那么我们从ExtensionLoader.getExtensionLoader(Car.class)开始,进入源码,弄清原理。
代码的示例很简单, 但是里面的学问有很多, 不过不需要心急,我们需要确保看源码的过程中不会迷失。 接下来的源码解析将围绕着本段示例代码来进行步步深入分析, 尽量做到细微入致。
Dubbo 加载拓展点
- 创建ExtensionLoader实例源码
@SuppressWarnings("unchecked")
public static ExtensionLoader getExtensionLoader(Class type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
// 判断type 是否为接口
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
// 判断是否标注了@SPI注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// 从缓存中获取ExtensionLoader。
ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
if (loader == null) {
//缓存中没有的话 则创建一个新的ExtensionLoader加入缓存中。
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}
//构造方法
private ExtensionLoader(Class> type) {
this.type = type;
//此处默认的objectFactory 为org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory,
// 该处的objectFactory 相当于一个IOC容器。 后面在IOC部分详细说明
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
从中可以看出创建ExtensionLoader实例的源码是比较简单的。 疑问点objectFactory对象工厂我们放到后面再说。接下来我们先看getExtension方法都做了什么。
- getExtension方法源码分析
@SuppressWarnings("unchecked")
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
// 缓存中根据name 获取加载过的实例
final Holder
此处我们可以看出获取扩展点实例的大致流程如下:
加载接口类型的所有实现类Class信息并缓存 ----> 根据name去获取指定扩展实现类Class ----> 通过反射机制对实现类Class进行实例化并缓存 ----> 基于dubbo 的IOC机制对实例对象进行依赖注入----> 判断是否有包装类(wrapperClasses),决定是否要生成代理对象 ----> 最后对对象进行初始化之后返回该实例对象
- 第一步实现类的加载 getExtensionClasses()
private Map> getExtensionClasses() {
Map> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 加载所有class
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
private Map> loadExtensionClasses() {
// 通过@SPI("DefaultExtensionName") 缓存默认的extension name
cacheDefaultExtensionName();
//根据type.name 查找各个配置文件路径下的实现类拓展配置
Map> extensionClasses = new HashMap<>();
// internal extension load from ExtensionLoader's ClassLoader first
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
private void loadDirectory(Map> extensionClasses,
String dir,
String type,
boolean extensionLoaderClassLoaderFirst) {
String fileName = dir + type;
try {
Enumeration urls = null;
// 获取ClassLoader
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
// 如果需要先尝试使用ExtensionLoader 查找文件资源
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
// 使用默认加载器获取资源文件
if(urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 读取类配置, 加载类信息
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
//.... 此处省略文件解析部分代码,不在主逻辑范畴,有兴趣的同学可以自行深入
// 遍历加载class实现类, 通过Class.forName(line, true, classLoader) 实现类加载
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
//.... 此处省略异常处理
}
//解析类信息
private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class> clazz, String name) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
// 缓存标注了@Adaptive的扩展实现类, 具体作用后面自适应问题详细说明
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
// 判断是否为包装类, 缓存包装类
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
// 如果没有指定name 生成name
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
// 分割name, 因为可以多个name 指向同一个实现类。例: aa, bb = org.apache.dubbo.extension.C
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 缓存@Activate标注的类信息
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 遍历缓存 name和class 信息
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
到此基本了解了拓展类的加载过程, 基本逻辑也非常简单。 获取到classLoader并调用Class.forname("className")将扩展类信息加载进JVM中, 之后就是从功能扩展的维度,对class进行不同纬度的缓存, 方便后面在某项功能点中快速获取类信息。
Dubbo IOC实现
-
基于IOC的依赖注入 injectExtension(instance)
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
从上面的逻辑很简单的看出大致逻辑就是先获取所有的setter方法,获取setter方法的参数的类型和名称, 最后通过我们前面看到过的objectFactory获取到依赖对象, 然后对其进行依赖注入。 那这里我们就需要深扒一下这个objectFactory 到底是个神马。前面我在objectfactory的注释说明了默认为AdaptiveExtensionFactory类, 我们先来看下他的源码
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
// 保存了ExtensionFactory的实现类。
private final List factories;
public AdaptiveExtensionFactory() {
// 初始化将 其他ExtensionFactory拓展类加载缓存
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List list = new ArrayList();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
@Override
public T getExtension(Class type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
很明显该类是一个策略类, 启动时候会讲其他的ExtensionFactory实现类加载进来, 然后再获取Extension实现的时候直接遍历各个容器来进行查找。那我们来看下ExtensionFactory默认实现的子类。
// SPI 默认的IOC实现,
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public T getExtension(Class type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
// Spring 实现
public class SpringExtensionFactory implements ExtensionFactory {
private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);
private static final Set CONTEXTS = new ConcurrentHashSet();
public static void addApplicationContext(ApplicationContext context) {
CONTEXTS.add(context);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).registerShutdownHook();
}
}
public static void removeApplicationContext(ApplicationContext context) {
CONTEXTS.remove(context);
}
public static Set getContexts() {
return CONTEXTS;
}
// currently for test purpose
public static void clearContexts() {
CONTEXTS.clear();
}
@Override
@SuppressWarnings("unchecked")
public T getExtension(Class type, String name) {
//SPI should be get from SpiExtensionFactory
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
for (ApplicationContext context : CONTEXTS) {
T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
if (bean != null) {
return bean;
}
}
logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
return null;
}
}
以上ExtensionFactory主要分为两类
SpiExtensionFactory是Dubbo 的IOC默认容器,对象是从前面加载缓存中获取的SPI的扩展对象。 这是Dubbo 的默认机制
SpringExtensionFactory是使用Spring的IOC容器。我们可以将Spring容器中的Bean注入到Extension实例中,通过该容器我们可以更好的继承Spring框架甚至其他组件。
Dubbo Aop实现
可能用过一些容器框架的同学都知道AOP的基本原理。 甚至有的同学可能首先想到的就是动态代理模式,这并没有错。 但是方法不是唯一的, Dubbo中获取的代理对象是通过装饰器模式实现的,接下来我们继续深入看下源码。
通过前面创建Extension我们可以知道, 是否需要生成对实力对象进行封装的依据是cachedWrapperClasses是否为空。同时通过类信息加载过程可以知道,在加载类的时候会对cachedWrapperClasses进行缓存, 那我们这边举例一个wrapper类,然后再次运行。
- CarWrapper
public class CarWrapper implements Car{
private Car car;
public CarWrapper(Car car){
this.car = car;
}
@Override
public void test(CarBrand brand) {
System.out.println("包装类前置执行");
car.test(brand);
System.out.println("包装类后置执行");
}
}
- 配置信息
bwm=com.wgt.dubbo.samples.spi.demo1.car.BWMCar
audi=com.wgt.dubbo.samples.spi.demo1.car.AudiCar
# 代理类配置
com.wgt.dubbo.samples.spi.demo1.car.CarWrapper
- 控制台输出
包装类前置执行
hi, 我是宝马车
包装类后置执行
从输出结果来看, 很明显在我根据(name="bwm"), 去获取Extension实例的时候,实际获取到的是CarWrapper类, 只不过BWMCar是作为一个代理对象传入到CarWrapper中。
- 源码参考
Set> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class> wrapperClass : wrapperClasses) {
// 获取CarWrapper类的有参构造起, 并将instance作为构造参数返回新的包装类。
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
当前方式可以获取到包装后的实例对象, 但是此种方法还是不够灵活。 Dubbo 中还有一种自适应的机制, 可以根据请求参数动态的获取拓展类进行调用, 通过策略+装饰器的模式实现更加完善的代理
Dubbo 自适应机制
第一次听到自适应机制难免有些陌生, 扩展的自适应实例其实就是一个Extension的代理,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。
-
@Adaptive
@Adaptive注解是作用在做自适应扩展点的注解类, 可以作用在类上和方法上
- 当@Adaptive标注在类上时,在调用getAdaptiveExtension方法时,直接返回该类,表示代理类由手工实现,并不需要Dubbo自动生成实现类。 可以参考 AdaptiveCompiler 和 AdaptiveExtensionFactory 实现
- 当@Adaptive标注在接口的方法上时, 表明调用该方法可以通过URL参数动态调用扩展实现类的对应方法。
- 被@Adaptive修饰得方法得参数 必须满足参数中有一个是URL类型,或者有至少一个参数有一个公共的返回URL的get方法
- 在调用动态生成代理类的非@Adaptive方法是, 默认会抛出UnsupportedOperationException异常
-
代码示例
说明: @Adaptive标注在类上的逻辑相当简单。 这里就不赘述了。 直接以标注在方法上的例子为例
@SPI public interface Car { //动态生成代理类接口 @Adaptive void test(CarBrand brand); } //自适应节点, 获取org.apache.dubbo.common.URL参数的接口 public interface CustomerAdaptiveNode { URL getUrl(); } //车品牌 public interface CarBrand extends CustomerAdaptiveNode { CarBrand BWM = getCarBrand("bwm"); CarBrand AUDI = getCarBrand("audi"); CarBrand BENZ = getCarBrand("benz"); static CarBrand getCarBrand(String brandName){ return new CarBrand() { @Override public URL getUrl() { URL url = new URL(null, null, 0); url = url.addParameter("car", brandName); return url; } }; } } //宝马车 public class BWMCar implements Car { @Override public void test(CarBrand brand) { System.out.println("hi, 我是宝马车"); } } //..... 此处省略 奔驰车和奥迪车源码 //Main方法 public static void main(String[] args) { ExtensionLoader
extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); //Car car = extensionLoader.getExtension("bwm"); // 获取自适应代理实例 Car car = extensionLoader.getAdaptiveExtension(); //通过车品牌调用接口 car.test(CarBrand.BENZ); car.test(CarBrand.BWM); car.test(CarBrand.AUDI); }
- 控制台输出
hi, 我是奔驰车
hi,我是宝马车
hi, 我是奥迪车
可以看到,我们在调用同一个实例的同一个方法, 我们根据不同的CarBrand 参数实现了自适应的去调用对应扩展实例的方法。
-
源码参考
public T getAdaptiveExtension() { // 一如既往的缓存套路 Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError != null) { throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { // 创建自适应实例 instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } return (T) instance; } private T createAdaptiveExtension() { try { // 获取自适应扩展类Class, 调用newInstance方法进行实例化,然后进行注入 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } } private Class> getAdaptiveExtensionClass() { // 眼熟的初始加载加载扩展累信息 getExtensionClasses(); // 判断是否有标注在类上的@Adaptive 扩展类 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } //动态生成自适应扩展累 return cachedAdaptiveClass = createAdaptiveExtensionClass(); } private Class> createAdaptiveExtensionClass() { // 自适应扩展累代码生成器 生成源代码, // 此处不在深入生成代码原理, 有兴趣的小伙伴自行研究, 下面会贴出生成好的代码 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); //获取classLoader ClassLoader classLoader = findClassLoader(); // 获取编译器 org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); // 将源代码进行编译生成Class 对象 return compiler.compile(code, classLoader); }
-
自适应扩展累源码
package com.wgt.dubbo.samples.spi.demo1.car; import org.apache.dubbo.common.extension.ExtensionLoader; public class Car$Adaptive implements com.wgt.dubbo.samples.spi.demo1.car.Car { public void test(com.wgt.dubbo.samples.spi.demo1.brand.CarBrand arg0) { if (arg0 == null) throw new IllegalArgumentException("com.wgt.dubbo.samples.spi.demo1.brand.CarBrand argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.wgt.dubbo.samples.spi.demo1.brand.CarBrand argument getUrl() == null"); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = url.getParameter("car", "bwm"); if (extName == null) throw new IllegalStateException("Failed to get extension (com.wgt.dubbo.samples.spi.demo1.car.Car) name from url (" + url.toString() + ") use keys([car])"); com.wgt.dubbo.samples.spi.demo1.car.Car extension = (com.wgt.dubbo.samples.spi.demo1.car.Car) ExtensionLoader.getExtensionLoader(com.wgt.dubbo.samples.spi.demo1.car.Car.class).getExtension(extName); extension.test(arg0); } }
从以上的源代码可以看出, 所谓的根据参数自适应,其实就是通过URL对象中的parameter 参数去获取对应的扩展实现。 获取的扩展的方式同样是通过策略模式进行实现。 不过此种方式可以确保在运行时才能确定具体执行的扩展累,对于SPI机制的灵活性来说非常的有意义的。
Dubbo @Activate
相较于前面的内容,都是根据一些特定参数获取具体的扩展类, 但其实除此之外, 我们还会有同时用到多个扩展类,最常见的就是Dubbo 的Filter 机制,当我们需要在执行rpc调用的前后做一个操作时, 我们就可以通过实现Filter接口,并将其配置到org.apache.dubbo.rpc.Filter下。 但是我们在一次rpc调用时可能会有多个filter, 那么如何决定每个filter 的作用域,以及执行顺序呢?
此时@Activate的作用的出现了,我们可以通过group, 和value 两者来决定是否此次的调用启用对应拦截器,并通过order 排序决定执行顺序。
- 源码参考
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
/**
* 根据分组
* 例如
* CommonConstants.PROVIDER
* CommonConstants.CONSUMER
* @return
*/
String[] group() default {};
/**
* 根据org.apache.dubbo.common.URL 中的parameters 参数决定是否启用
* @return
*/
String[] value() default {};
@Deprecated
String[] before() default {};
@Deprecated
String[] after() default {};
/**
* Filter 顺序
* @return
*/
int order() default 0;
}
- 拦截器获取源码逻辑
public List getActivateExtension(URL url, String[] values, String group) {
List activateExtensions = new ArrayList<>();
List names = values == null ? new ArrayList<>(0) : asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
activateExtensions.add(getExtension(name));
}
}
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
List loadedExtensions = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(0, loadedExtensions);
loadedExtensions.clear();
}
} else {
loadedExtensions.add(getExtension(name));
}
}
}
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(loadedExtensions);
}
return activateExtensions;
}