Dubbo SPI原理与设计精要

SPI全程Service Provider Interface,也就是服务发现机制。SPI分离服务接口和具体实现类,解耦服务调用方和实现者,提升应用框架可扩展性,实现可插拔微内核架构。SPI遵循基于接口编程而非具体实现,满足面向对象设计依赖倒置原则。同时,还支持开闭原则,也就是对扩展开放,对修改封闭。

在Java中,很多框架还使用SPI机制,如Spring框架、数据库驱动和日志接口,另外Dubbo还提供更高级SPI扩展机制。

01 Java SPI

1.1 Java SPI原理

Jdk 1.6引入SPI机制,提供数据库、日志框架和消息服务扩展接口,达到动态加载服务实现。底层原理主要包括四个步骤,比如服务接口、服务提供者、服务配置和服务加载器。

组件 描述
服务接口 定义服务抽象功能接口,规定服务提供者需要实现的功能
服务提供者 服务提供者实现服务接口,提供具体实现。例如,JDBC不同数据库驱动程序(MySQL、PostgreSQL等)提供各自实现类
配置文件 服务提供者在META-INF/services/ 目录下创建服务接口权限类名文件,然后写入服务提供者实现类的全限定类名到文件
服务加载器(ServiceLoader) 在Java中,提供ServiceLoader类扫描META-INF/services/目录配置文件,找到需要事务配置文件进行加载和实例化服务提供者实现类

1.2 Java SPI实现

1.2.1 服务接口

定义服务接口或业务抽象,规定服务提供者需要实现的功能。

java 复制代码
public interface PaymentService {
    void processPayment(double amount);
}

1.2.2 提供者服务实现

服务提供者实现服务接口,提供具体的实现。

java 复制代码
public class PayPalPaymentService implements PaymentService {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment via PayPal: $" + amount);
    }
}

public class CreditCardPaymentService implements PaymentService {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment via Credit Card: $" + amount);
    }
}

1.2.3 服务配置

接下来,创建 ETA-INF/services/com.feiyu.PaymentService 文件,文件回填服务实现类全限定类名,注意每个服务提供者实现类占据一行。

java 复制代码
com.feiyu.pay.PayPalPaymentService
com.feiyu.credit.CreditCardPaymentService

1.2.4 服务加载器

在Java中,提供 ServiceLoader 类扫描 META-INF/services/ 目录查找接口配置文件,然后动态加载和实例化服务提供者实现类。

java 复制代码
public class PaymentApp {
    public static void main(String[] args) {
        // 加载所有PaymentService实现
        ServiceLoader<PaymentService> services= ServiceLoader.load(PaymentService.class);
        // 遍历所有可用服务实现
        for (PaymentService service: services) {
            service.processPayment(100.0);
        }
    }
}

1.5 Java SPI总结

Java SPI底层通过java.util.ServiceLoader#load进行加载,每次调用重新扫描META-INF/services/目录服务接口配置文件返回循环迭代器,程序只需要循环调用Class.forName进行反射查找和实例化服务提供者即可。

通过加载逻辑直观看出存在一些弊端,比如重复加载不添加缓存存在性能开销,一次性加载全部服务提供者存在无效加载,以及不能指定特定服务提供者进行加载。

02 Dubbo SPI演进之路

Java SPI存在性能问题、无效加载和不能指定加载服务提供者,所以为了解决这些问题Dubbo重新设计一套SPI实现机制,而且还提供后置增强实例。Dubbo SPI底层提供指定参数条件、默认或者激活方式获取服务提供者(也即是,所谓的扩展类),实现服务提供者自动加载和切换,是构建可插拔、模块化服务架构的重要工具。

Dubbo SPI组件 Java SPI组件
扩展接口 服务接口
扩展类 服务提供者
扩展配置 服务配置
扩展加载器 服务加载器

03 Dubbo SPI规范

3.1 扩展工厂

Dubbo提供ExtensionFactory进行扩展类实例(setter)依赖注入处理,对扩展类实例进行加强。底层提供三种实现,包括AdaptiveExtensionFactory、SpiExtensionFactory和SpringExtensionFactory三种实现。

需要注意的是,扩展工厂底层是通过Java SPI进行服务发现加载,所以实现配置会全部进行加载。

java 复制代码
@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);
}

3.2 扩展接口

Dubbo SPI约定服务接口必须满足两个条件,也就是接口类和接口被@SPI修饰。

java 复制代码
@SPI
public interface PaymentService {
    void processPayment(double amount);
}

3.3 扩展包装器

Dubbo SPI提供包装器进行扩展类加强,格式就是包装器必须提供扩展接口构造函数。另外,可以通过@Wrapper额外指定需要包装范围,默认不添加都需要包装增强。

java 复制代码
// 申明方式一
public class WrapperPaymentService implements PaymentService {
    private final PaymentService paymentService;

    public WrapperPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    @Override
    public void processPayment(double amount) {
        this.paymentService.processPayment(amount);
    }
}

// 申明方式二
@Wrapper
public class WrapperPaymentService implements PaymentService {
    private final PaymentService paymentService;

    public WrapperPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    @Override
    public void processPayment(double amount) {
        this.paymentService.processPayment(amount);
    }
}

public @interface Wrapper {
    // 指定需要包装的扩展名
    String[] matches() default {};

    // 排除不进行包装的扩展名
    String[] mismatches() default {};
}

3.4 配置默认扩展名

Dubbo SPI申明使用@SPI注解注释接口就是扩展接口,以及通过@SPI(value=?)注解指定值就是默认扩展名。

java 复制代码
@SPI(value="pay") // 指定默认扩展名为pay
public interface PaymentService {
    void processPayment(double amount);
}

3.5 自适应扩展类

Dubbo SPI机制提供@Adaptive注解注释自适应扩展类,每个扩展接口仅支持唯一一个自适应扩展类,所以,需要当心选择META-INF/dubbo/internal/目录是不支持覆盖处理的。

java 复制代码
@Adaptive
public class PayPalPaymentService implements PaymentService {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment via PayPal: $" + amount);
    }
}

3.6 激活扩展类

Dubbo SPI提供@Activate注解用于标注激活扩展类,主要用途在于能通过URL参数模糊匹配到扩展点,然后返回符合条件的激活扩展类。

java 复制代码
public @interface Activate {
    // 激活组
    String[] group() default {};

    // 激活扩展配置, 例如 Activate(value="key1:value1, key2")
    String[] value() default {};
    
    // 排在哪些激活点迁
    @Deprecated
    String[] before() default {};

    // 排在哪些激活点后
    @Deprecated
    String[] after() default {};

    // 顺序
    int order() default 0;
}

3.6.1 激活扩展点配置

java 复制代码
@Activate(group={"mas", "ums"}, value={"key1:value1, key2:value2"})
public class WechatPaymentService implements PaymentService {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment via wechat: $" + amount);
    }
}

3.6.2 激活扩展注解匹配机制

激活选项 样例 内容
group {"mas", "ums"} 允许进行匹配的组,为空表示默认匹配,仅支持精确匹配
value {"key1:value1, key2"} 允许进行匹配的参数值,为空默认匹配,支持后缀匹配和精确匹配

接下来,就是细化激活配置与URL参数各种匹配规则。

URL参数 激活配置 匹配机制
?k1=v1&k2=v2 @Activate(value={"k1:v1, k2:v2"}) 精确匹配
?k1=v1&k2=v2 @Activate(value={"k1:v1, k2"}) 参数匹配,URL对应键参数不能为空
?k1=v1&k2=v2 @Activate(value={"k1:v1, g.k2"}) 参数后缀匹配,也就是激活配置参数后缀匹配="." + URL参数Key,URL对应Key参数不能为空

3.7 普通扩展类

普通扩展类无须通过@Activate@Adaptive注解修饰,也不是包装器扩展类,而且必须仅包含无参构造器。

3.8 扩展实例依赖注入

Dubbo SPI可以通过依赖注入增强扩展实例,主要针对就是setter方法。其中,可以额外指定@Inject注解设置注入方式和是否注入。另外,也可以通过@DisableInject禁止当前方法进行依赖注入。

java 复制代码
public @interface Inject {
    // 是否开启注入
    boolean enable() default true;

    // 注入方式
    InjectType type() default ByName;

    enum InjectType{
        ByName,
        ByType
    }
}

3.9 扩展初始化

Dubbo SPI可以Lifecycle生命周期管理接口,方便扩展类实现初始化过程。

csharp 复制代码
public interface Lifecycle {
    // 初始化方法
    void initialize() throws IllegalStateException;

    // 启动方法
    void start() throws IllegalStateException;

    // 销毁方法
    void destroy() throws IllegalStateException;
}

3.10 扩展配置

Dubbo提供META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/三个目录配置扩展,扩展配置文件规范为扩展接口全限定类名,配置文件内容格式为name1,name2,...,=扩展类全限定类名。同时,配置文件支持包装器配置。

例如,配置文件META-INF/dubbo/internal/com.feiyu.PaymentService进行扩展类配置,配置内容如下。

properties 复制代码
credit=com.feiyu.credit.CreditCardPaymentService
pay,pal,paypal=com.feiyu.pay.PayPalPaymentService
com.feiyu.pay.WechatPaymentService
com.feiyu.wrapper.WrapperPaymentService ## 包装类

扩展类名,Dubbo底层支持手动指定 或者框架自动规则解析 两种方式。其中,框架自动规则解析逻辑就是去除扩展接口名转换为小写字母获得,比如com.feiyu.pay.WechatPaymentService去除PaymentService得到wechat

需要注意,每个扩展接口可以配置<扩展名, 扩展类>映射,但是扩展名必须保证扩展接口级别唯一,否则就会抛出异常。

04 Dubbo SPI分析

类似Java SPI ServiceLoader功能,Dubbo SPI底层通过ExtensionLoader封装扩展类加载,以及提供指定参数条件、默认或者激活方式获取扩展类路径,所以ExtensionLoader也是源码重点研究对象。

4.1 ExtensionLoader实例化

ExtensionLoader没有提供公开构造函数,只能通过ExtensionLoader.getExtensionLoader(Class<T> type)获取ExtensionLoader实例。

java 复制代码
public class ExtensionLoader<T> {
    // 服务接口扩展加载器缓存
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
    // 名字分割匹配模式, 格式=n1,n2,...
    private static final Pattern NAME_SEPARATOR = Pattern.compile("\s*[,]+\s*");
    
    // 需要加载服务接口类型
    private final Class<?> type;
    // 扩展加载工厂: 用于自定义实现扩展加载
    private final ExtensionFactory objectFactory;

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        // 使用激活ExtensionFactory加载服务提供者
        if(type != ExtensionFactory.class) {
            objectFactory = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
        }
    }
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        // 仅支持接口
        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<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            // 为空创建并缓存
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            // 从缓存获取最新扩展加载器
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    
    private static <T> boolean withExtensionAnnotation(Class<T> type) {
        // 接口有添加@SPI注解
        return type.isAnnotationPresent(SPI.class);
    }
}

public @interface SPI {
    // 指定默认扩展名
    String value() default "";
}

获取ExtensionLoader实例过程可以看到,扩展接口必须是被@SPI注解修饰的接口,扩展接口加载器创建和初始化后缓存到ConcurresntHashMap结构。

4.2 基础解析

4.2.1 默认扩展名解析

方法 功能
ExtensionLoader#cacheDefaultExtensionName( ) 缓存默认扩展名
java 复制代码
public class ExtensionLoader<T> {
    // 扩展接口默认扩展名, 通过@SPI(value=?)指定, 仅支持1个默认扩展名
    private String cachedDefaultName;

    // 缓存默认扩展名
    private void cacheDefaultExtensionName() {
        // 获取扩展接口@SPI注解
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }
        // @SPI(value=?): value=默认扩展名(仅支持1个配置), 格式=n1,n2,...,
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            // 不能配置多个
            if (names.length > 1) {
                throw new IllegalStateException("More than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
            }
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }
}

4.2.2 初始化配置加载策略

Dubbo SPI申明<font style="color:#74B602;">ExtensionLoader#loadLoadingStrategies</font>方法读取扩展配置加载策略,底层使用Java SPI机制进行加载策略,然后通过排序后返回。另外,也提供额外方法手动设置加载策略。

java 复制代码
public class ExtensionLoader<T> {
    // 初始化加载策略
    private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();

    public static void setLoadingStrategies(LoadingStrategy... strategies) {
        if (ArrayUtils.isNotEmpty(strategies)) {
            ExtensionLoader.strategies = strategies;
        }
    }
    
    private static LoadingStrategy[] loadLoadingStrategies() {
        // 通过Java SPI加载LoadingStrategy服务提供者
        return stream(load(LoadingStrategy.class).spliterator(), false)
                .sorted()
                .toArray(LoadingStrategy[]::new);
    }
}

public interface LoadingStrategy extends Prioritized {
    // 扩展目录
    String directory();

    // 优先扩展加载器
    default boolean preferExtensionClassLoader() {
        return false;
    }
    
    // 排除包
    default String[] excludedPackages() {
        return null;
    }

    // 是否覆盖加载
    default boolean overridden() {
        return false;
    }
}

ExtensionLoader提供扩展加载策略支持,底层使用Java SPI扫描和排序这些扩展加载策略,默认实现包括DubboInternalLoadingStrategyDubboLoadingStrategyServicesLoadingStrategy三种实现。

加载策略 加载路径 优先级 是否支持覆盖
DubboInternalLoadingStrategy META-INF/dubbo/internal/ 最高 N
DubboLoadingStrategy META-INF/dubbo/ 中等 Y
ServicesLoadingStrategy META-INF/services/ 最低 Y

4.2.3 获取扩展类

ExtensionLoader提供getExtensionClasses方法获取扩展类,标签缓存起来避免二次加载。

java 复制代码
public class ExtensionLoader<T> {
    // 缓存已加载扩展类, 格式: n1,n2,...=扩展类全限定名
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    
    private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
}

4.2.4 读取扩展配置

ExtensionLoader提供=DubboInternalLoadingStrategyDubboLoadingStrategyServicesLoadingStrategy三种加载扩展策略,分别调用ExtensionLoader#loadDirectory执行不同目录配置扩展类加载。

java 复制代码
public class ExtensionLoader<T> {
    // 初始化加载策略
    private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
    // 每行解析异常信息
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
    
    // 读取加载策略加载扩展配置
    private Map<String, Class<?>> loadExtensionClasses() {
        // 解析和缓存默认扩展名
        cacheDefaultExtensionName();

        // 扩展类
        Map<String, Class<?>> extensionClasses = new HashMap<>();

        // 循环扩展策略进行加载
        for (LoadingStrategy strategy : strategies) {
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
            // 兼容处理: 执行旧版本加载
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        }

        return extensionClasses;
    }
    
    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        loadDirectory(extensionClasses, dir, type, false, false);
    }

    // 读取扩展目录文件加载扩展类
    // @param extensionLoaderClassLoaderFirst: 首先选择ExtensionLoader类加载器
    private void loadDirectory(Map<String, Class<?>> extensionClasses, 
                               String dir, 
                               String type,
                               boolean extensionLoaderClassLoaderFirst, 
                               boolean overridden, String... excludedPackages) {
        // 文件路径, 比如:META-INF/dubbo/com.feiyu.PaymentService
        String fileName = dir + type;
        try {
            Enumeration<URL> urls = null;
            // 获取类加载器
            ClassLoader classLoader = findClassLoader();

            // 通过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, overridden, excludedPackages);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
    
    // 读取扩展配置文件加载扩展类
    private void loadResource(Map<String, Class<?>> extensionClasses, 
                              ClassLoader classLoader,
                              java.net.URL resourceURL, 
                              boolean overridden, 
                              String... excludedPackages) {
        try {
            // 读取文件
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                String clazz = null;
                while ((line = reader.readLine()) != null) {
                    // 读取配置, 格式:n1,n2=com.feiyu.credit.CreditCardPaymentService
                    // 截断行注释内容
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                clazz = line.substring(i + 1).trim();
                            } else {
                                clazz = line;
                            }
                            if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
                                loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }
}

4.3 解析扩展配置

4.3.1 解析扩展类

实现扩展类分类解析,比如自适应扩展类、包装器扩展类、激活扩展类和普通扩展类,然后缓存到对应位置方便查找。

java 复制代码
public class ExtensionLoader<T> {
    // 初始化加载策略
    private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
    // 每行解析异常信息
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
    // 扩展类名字, 仅缓存第一个配置扩展类名
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
    
    // 加载约定扩展类
    private void loadClass(Map<String, Class<?>> extensionClasses, 
                           java.net.URL resourceURL, 
                           Class<?> clazz, 
                           String name,
                           boolean overridden) 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, overridden);
        } 
        else if (isWrapperClass(clazz)) {
            // 扩展类是包装类型
            cacheWrapperClass(clazz);
        } else {
            // 获取无参构造器
            clazz.getConstructor();
            
            // 没有配置名字的扩展器
            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);
                }
            }
            
            // 已配置扩展名字
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                // 缓存接活扩展类
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
    }
    // 是否包装类
    private boolean isWrapperClass(Class<?> clazz) {
        try {
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }
    private String findAnnotationName(Class<?> clazz) {
        // 获取扩展器Extension注解
        org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
        if (extension != null) {
            return extension.value();
        }
        // 旧逻辑: 通过类名获取, 截取扩展接口后缀即为扩展类名
        String name = clazz.getSimpleName();
        if (name.endsWith(type.getSimpleName())) {
            name = name.substring(0, name.length() - type.getSimpleName().length());
        }
        return name.toLowerCase();
    }
    private void cacheName(Class<?> clazz, String name) {
        // 只缓存第一个扩展类名
        if (!cachedNames.containsKey(clazz)) {
            cachedNames.put(clazz, name);
        }
    }
    private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, 
                                      Class<?> clazz, 
                                      String name, 
                                      boolean overridden) {
        // 扩展类名 => 扩展类
        Class<?> c = extensionClasses.get(name);
        // 没有解析或者可以覆盖
        if (c == null || overridden) {
            extensionClasses.put(name, clazz);
        } 
        // 扩展名冲突, 抛出异常
        else if (c != clazz) {
            unacceptableExceptions.add(name);
            String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
            logger.error(duplicateMsg);
            throw new IllegalStateException(duplicateMsg);
        }
    }
}

4.3.2 解析自适应扩展类

Dubbo SPI提供@Adaptive注解注释自适应扩展类,其中,META-INF/dubbo/internal/扩展类不支持覆盖配置。

java 复制代码
public class ExtensionLoader<T> {
    // 自适应扩展类
    private volatile Class<?> cachedAdaptiveClass;
    
    private void cacheAdaptiveClass(Class<?> clazz, boolean overridden) {
        if (cachedAdaptiveClass == null || overridden) {
            cachedAdaptiveClass = clazz;
        } 
        // 仅支持一次设置
        else if (!cachedAdaptiveClass.equals(clazz)) {
            throw new IllegalStateException("More than 1 adaptive class found: "
                    + cachedAdaptiveClass.getName() + ", " + clazz.getName());
        }
    }
}

4.3.3 解析包装器扩展类

java 复制代码
public class ExtensionLoader<T> {
    // 包装类扩展类
    private Set<Class<?>> cachedWrapperClasses;
    
    private void cacheWrapperClass(Class<?> clazz) {
        if (cachedWrapperClasses == null) {
            cachedWrapperClasses = new ConcurrentHashSet<>();
        }
        cachedWrapperClasses.add(clazz);
    }
}

4.3.4 解析激活扩展类

Dubbo SPI提供<font style="color:#74B602;">@Activate</font>注解用于标注激活扩展类,通过cachedActivates属性缓存注解值。

typescript 复制代码
public class ExtensionLoader<T> {
    // 激活扩展点: 扩展类标注@Activate, 如果存在多个扩展名只会缓存第一个扩展名作为激活点
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
    
    private void cacheActivateClass(Class<?> clazz, String name) {
        // 获取扩展类@Activate注解
        Activate activate = clazz.getAnnotation(Activate.class);
        // 缓存激活注解
        if (activate != null) {
            cachedActivates.put(name, activate);
        } else {
            // support com.alibaba.dubbo.common.extension.Activate
            com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
            if (oldActivate != null) {
                cachedActivates.put(name, oldActivate);
            }
        }
    }
}

4.4 创建实例

Dubbo提供ExtensionLoader#createExtension创建扩展实例,通过ExtensionLoader#injectExtension进行实例增强(setter),然后进行包装器类增强处理,最后进行扩展类实例初始化。

java 复制代码
public class ExtensionLoader<T> {
    // 缓存扩展点实例
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    // 缓存扩展名原始实例
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
    // 异常扩展名
    private Set<String> unacceptableExceptions = new ConcurrentHashSet<>();
    
    // 创建扩展实例
    private T createExtension(String name, boolean wrap) {
        // 获取扩展类
        Class<?> clazz = getExtensionClasses().get(name);
        // 没有找到直接抛出异常
        if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            // 获取扩展名原始实例
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                // 创建并缓存扩展类原始实例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            
            // 扩展实例依赖注入增强处理
            injectExtension(instance);

            // 保证处理
            if (wrap) {
                List<Class<?>> wrapperClassesList = new ArrayList<>();
                // 包装类排序
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                    Collections.reverse(wrapperClassesList);
                }

                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        // 获取包装器扩展类@Wrapper注解
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        if (wrapper == null 
                            // 包含名称 以及 不在排除名称之外
                            || (ArrayUtils.contains(wrapper.matches(), name) 
                                && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                            // 创建包装扩展实例, 然后进行依赖注入
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        }
                    }
                }
            }

            // 扩展类实例初始化
            initExtension(instance);
            
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }
}

4.4.1 扩展实例依赖注入

java 复制代码
public class ExtensionLoader<T> {
    // 扩展工厂
    private final ExtensionFactory objectFactory;
    
    // 扩展实例依赖注入
    private T injectExtension(T instance) {
        if (objectFactory == null) {
            return instance;
        }

        try {
            // 循环所有方法
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {
                    continue;
                }
                
                // 方法标注@DisableInject注解, 不进行依赖注入
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }

                // 获取依赖注入类型参数
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }
                
                // 获取方法名
                String property = getSetterProperty(method);
                
                // 获取方法@Inject注解
                Inject inject = method.getAnnotation(Inject.class);
                if (inject == null) {
                    injectValue(instance, method, pt, property);
                } else {
                    // 禁止依赖注入
                    if (!inject.enable()) {
                        continue;
                    }

                    if (inject.type() == Inject.InjectType.ByType) {
                        injectValue(instance, method, pt, null);
                    } else {
                        injectValue(instance, method, pt, property);
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
    
    // 获取方法名
    private String getSetterProperty(Method method) {
        return method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
    }

    private void injectValue(T instance, Method method, Class<?> pt, String property) {
        try {
            // 获取依赖注入值
            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);
        }
    }
}

4.4.2 初始化扩展实例

java 复制代码
public class ExtensionLoader<T> {
    // 扩展工厂
    private final ExtensionFactory objectFactory;
    
    // 初始化扩展实例
    private void initExtension(T instance) {
        // 如果扩展实现Lifecycle, 调用initialize进行初始化
        if (instance instanceof Lifecycle) {
            Lifecycle lifecycle = (Lifecycle) instance;
            lifecycle.initialize();
        }
    }
}

4.5 获取扩展点

4.5.1 获取指定名称扩展点

Dubbo SPI重载getExtension方法对外获取指定名称扩展类,其中,底层使用Holder持有当前扩展点实例,不过可以直接设计缓存实例对象。

java 复制代码
public class ExtensionLoader<T> {
    // 缓存扩展点实例
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    // 缓存扩展名原始实例
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
    // 异常扩展名
    private Set<String> unacceptableExceptions = new ConcurrentHashSet<>();
    
    public T getExtension(String name) {
        return getExtension(name, true);
    }

    public T getExtension(String name, boolean wrap) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        // 获取或者创建持有Holder
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 创建和缓存扩展
                    instance = createExtension(name, wrap);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
    
    private Holder<Object> getOrCreateHolder(String name) {
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }
}

4.5.2 获取默认扩展点

java 复制代码
public class ExtensionLoader<T> {
    // 扩展接口默认扩展名, 通过@SPI(value=?)指定, 仅支持1个默认扩展名
    private String cachedDefaultName;
    
    public T getDefaultExtension() {
        // 加载扩展类
        getExtensionClasses();
        // 缓存默认扩展名不能为空或者true
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        
        // 指定名称获取扩展: @SPI(value=?)注解值就是默认扩展名
        return getExtension(cachedDefaultName);
    }
}

4.5.3 获取自适应扩展点

java 复制代码
public class ExtensionLoader<T> {
    // 自适应实例缓存: 每个扩展接口仅支持1个自适应扩展点
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
    // 自适应扩展创建异常
    private volatile Throwable createAdaptiveInstanceError;
    
    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 {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
}

4.5.4 获取激活扩展点

Dubbo底层提供4个getActivateExtension重载方法,方便用户根据URL获取激活扩展点。获取激活扩展点实例,需要匹配激活类匹配,以及常规扩展点。

匹配类型 匹配规则
激活扩展点 激活扩展点解析逻辑,就是循环激活扩展点与URL参数进行匹配,如果匹配就添加到激活扩展集合。 激活扩展点匹配逻辑,包括是否允许激活扩展点匹配和激活扩展匹配。 1. 是否进行激活扩展点匹配:获取扩展名不能包含"-default" 2. 激活扩展点匹配 (1)激活扩展点注解匹配:@Activate(group={}, value={}) (2)激活扩展名匹配:参数扩展名 not_exsited (激活扩展名, -激活扩展名)
常规扩展点 常规扩展点解析逻辑:循环传入参数扩展名进行匹配,如果解析到扩展名=default就添加到已解析扩展到最前面,否则都追加到后面。 扩展名是否匹配:参数扩展名不是以去除符号(-)为前缀,以及扩展名不存在去除参数,如values={"timeout", "-timeout"}
java 复制代码
public class ExtensionLoader<T> {
    // 激活扩展点: 扩展类标注@Activate, 如果存在多个扩展名只会缓存第一个扩展名作为激活点
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
    
    public List<T> getActivateExtension(URL url, String key) {
        return getActivateExtension(url, key, null);
    }
    
    public List<T> getActivateExtension(URL url, String[] values) {
        return getActivateExtension(url, values, null);
    }
    
    public List<T> getActivateExtension(URL url, String key, String group) {
        // 获取URL参数key=?对应值, 也就是扩展点名称
        String value = url.getParameter(key);
        return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
    }

    // @param values=扩展名称集合, group=扩展组
    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
        // 创建TreeMap: 也就是红黑树排序
        TreeMap<Class, T> activateExtensionsMap = new TreeMap<>(ActivateComparator.COMPARATOR);
        
        Set<String> loadedNames = new HashSet<>();
        Set<String> names = CollectionUtils.ofSet(values);

        // 激活扩展点匹配: 扩展点不包含"-default"值, 也就是去除(-)默认配置
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            // 加载扩展类
            getExtensionClasses();
            // 循环当前扩展接口所有激活扩展点
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Object activate = entry.getValue();

                // 激活组和激活扩展点配置
                String[] activateGroup, activateValue;
                
                // apache注解
                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;
                }
                // 1. paramGroup in (activateGroup)
                // 2. activateName not_in (paramNames)
                // 3. -activateName not_in (paramNames)
                // 4. @Activate.value match url.params
                // 5. 不重复加载
                if (isMatchGroup(group, activateGroup)
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)
                        && !loadedNames.contains(name)) {
                    // 缓存扩展类与扩展实例映射
                    activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
                    loadedNames.add(name);
                }
            }
            if (!activateExtensionsMap.isEmpty()) {
                activateExtensions.addAll(activateExtensionsMap.values());
            }
        }
        
        List<T> loadedExtensions = new ArrayList<>();
        // 扩展点匹配
        for (String name : names) {
            // -: 表示不匹配
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                // 控制不进行重复加载
                if (!loadedNames.contains(name)) {
                    // DEFAULT_KEY = default
                    if (DEFAULT_KEY.equals(name)) {
                        // 将扩展点添加到激活扩展点之前
                        if (!loadedExtensions.isEmpty()) {
                            activateExtensions.addAll(0, loadedExtensions);
                            loadedExtensions.clear();
                        }
                    } else {
                        // 缓存扩展点实例
                        loadedExtensions.add(getExtension(name));
                    }
                    loadedNames.add(name);
                } else {
                    // 重复加载, warn提示
                    String simpleName = getExtensionClass(name).getSimpleName();
                    logger.warn("Catch duplicated filter, ExtensionLoader will ignore one of them. Please check. Filter Name: " + name + ". Ignored Class Name: " + simpleName);
                }
            }
        }
        // 末尾添加到激活扩展点
        if (!loadedExtensions.isEmpty()) {
            activateExtensions.addAll(loadedExtensions);
        }
        return activateExtensions;
    }

    // 组匹配
    private boolean isMatchGroup(String group, String[] groups) {
        if (StringUtils.isEmpty(group)) {
            return true;
        }
        // 找到相同组即可
        if (groups != null && groups.length > 0) {
            for (String g : groups) {
                if (group.equals(g)) {
                    return true;
                }
            }
        }
        return false;
    }

    // 激活点匹配
    // 例如:@Active(value="key1:value1, key2:value2"), URL参数能模糊匹配参数即可
    private boolean isActive(String[] keys, URL url) {
        if (keys.length == 0) {
            return true;
        }
        for (String key : keys) {
            // @Active(value="key1:value1, key2:value2")
            String keyValue = null;
            if (key.contains(":")) {
                String[] arr = key.split(":");
                key = arr[0];
                keyValue = arr[1];
            }

            for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
                String k = entry.getKey();
                String v = entry.getValue();
                if ((k.equals(key) || k.endsWith("." + key))
                     && ((keyValue != null && keyValue.equals(v)) 
                         || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {
                    return true;
                }
            }
        }
        return false;
    }
}

4.6 手动添加扩展点

java 复制代码
public class ExtensionLoader<T> {
    // 缓存已加载扩展类, 格式: n1,n2,...=扩展类全限定名
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    // 扩展点对应扩展名称, 如果配置存在多个名称只会第一个解析值
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();

    
    public void addExtension(String name, Class<?> clazz) {
        // 先执行扫描添加扩展
        getExtensionClasses();

        // 扩展类必须经常扩展接口
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Input type " + clazz + " doesn't implement the Extension " + type);
        }
        // 扩展类不能是接口
        if (clazz.isInterface()) {
            throw new IllegalStateException("Input type " + clazz + " can't be interface!");
        }
        // 非自适应扩展
        if (!clazz.isAnnotationPresent(Adaptive.class)) {
            if (StringUtils.isBlank(name)) {
                throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
            }
            if (cachedClasses.get().containsKey(name)) {
                throw new IllegalStateException("Extension name " + name + " already exists (Extension " + type + ")!");
            }
            
            // 缓存扩展类和扩展名映射关系
            cachedNames.put(clazz, name);
            cachedClasses.get().put(name, clazz);
        } else {
            // 不能重复添加自适应扩展
            if (cachedAdaptiveClass != null) {
                throw new IllegalStateException("Adaptive Extension already exists (Extension " + type + ")!");
            }
            cachedAdaptiveClass = clazz;
        }
    }
}

4.7 其他

4.7.1 是否包含扩展

java 复制代码
public class ExtensionLoader<T> {
    public boolean hasExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        // 获取扩展类
        Class<?> c = this.getExtensionClass(name);
        return c != null;
    }
}

4.7.2 获取扩展类和名称

java 复制代码
public class ExtensionLoader<T> {
    // 扩展点对应扩展名称, 如果配置存在多个名称只会第一个解析值
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();

    // 获取扩展实例对应扩展点名称
    public String getExtensionName(T extensionInstance) {
        return getExtensionName(extensionInstance.getClass());
    }

    // 获取扩展类对应扩展点名称
    public String getExtensionName(Class<?> extensionClass) {
        getExtensionClasses();// load class
        return cachedNames.get(extensionClass);
    }

    // 获取默认扩展名
    public String getDefaultExtensionName() {
        getExtensionClasses();
        return cachedDefaultName;
    }

    // 获取扩展类
    private Class<?> getExtensionClass(String name) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        if (name == null) {
            throw new IllegalArgumentException("Extension name == null");
        }
        return getExtensionClasses().get(name);
    }
}

4.7.3 获取已加载扩展点

java 复制代码
public class ExtensionLoader<T> {
    // 缓存扩展点实例
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    
    // 获取已加载扩展实例
    public T getLoadedExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        Holder<Object> holder = getOrCreateHolder(name);
        return (T) holder.get();
    }

    private Holder<Object> getOrCreateHolder(String name) {
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }
    
    public Set<String> getLoadedExtensions() {
        return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet()));
    }

    public List<T> getLoadedExtensionInstances() {
        List<T> instances = new ArrayList<>();
        cachedInstances.values().forEach(holder -> instances.add((T) holder.get()));
        return instances;
    }
}

4.7.4 获取扩展点原始实例

java 复制代码
public class ExtensionLoader<T> {
    // 缓存扩展名原始实例
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
 
    public T getOriginalInstance(String name) {
        getExtension(name);
        Class<?> clazz = getExtensionClasses().get(name);
        return (T) EXTENSION_INSTANCES.get(clazz);
    }
}

4.7.5 获取支持扩展

java 复制代码
public class ExtensionLoader<T> {
    // 获取支持扩展名
    public Set<String> getSupportedExtensions() {
        // 获取扩展类
        Map<String, Class<?>> clazzes = getExtensionClasses();
        return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
    }

    // 获取支持扩展实例
    public Set<T> getSupportedExtensionInstances() {
        List<T> instances = new LinkedList<>();
        Set<String> supportedExtensions = getSupportedExtensions();
        if (CollectionUtils.isNotEmpty(supportedExtensions)) {
            for (String name : supportedExtensions) {
                instances.add(getExtension(name));
            }
        }
        // 排序后返回
        sort(instances, Prioritized.COMPARATOR);
        return new LinkedHashSet<>(instances);
    }
}

05 总结

SPI作为提供良好的扩展和可插拔实现,插件式微内核架构可以重要考虑的设计方案。Dubbo SPI机制比较灵活,提供默认、自适应、激活和普通扩展点支持,相比Java SPI更加灵活多变。

相关推荐
努力的小雨3 小时前
还在为调试提示词头疼?一个案例教你轻松上手!
后端
魔都吴所谓3 小时前
【go】语言的匿名变量如何定义与使用
开发语言·后端·golang
陈佬昔没带相机3 小时前
围观前后端对接的 TypeScript 最佳实践,我们缺什么?
前端·后端·api
Livingbody5 小时前
大模型微调数据集加载和分析
后端
Livingbody5 小时前
第一次免费使用A800显卡80GB显存微调Ernie大模型
后端
Goboy6 小时前
Java 使用 FileOutputStream 写 Excel 文件不落盘?
后端·面试·架构
Goboy6 小时前
讲了八百遍,你还是没有理解CAS
后端·面试·架构
麦兜*7 小时前
大模型时代,Transformer 架构中的核心注意力机制算法详解与优化实践
jvm·后端·深度学习·算法·spring·spring cloud·transformer
树獭叔叔7 小时前
Python 多进程与多线程:深入理解与实践指南
后端·python
阿华的代码王国7 小时前
【Android】PopupWindow实现长按菜单
android·xml·java·前端·后端