千家信息网

dubbo的SPI应用与原理是什么

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,dubbo的SPI应用与原理是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。dubboSPI(Service Provider In
千家信息网最后更新 2026年11月02日dubbo的SPI应用与原理是什么

dubbo的SPI应用与原理是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

dubbo

SPI(Service Provider Interface)

  • 本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

  • 在Java中SPI是被用来设计给服务提供商做插件使用的。基于策略模式 来实现动态加载的机制 。我们在程序只定义一个接口,具体的实现交个不同的服务提供者;在程序启动的时候,读取配置文件,由配置确定要调用哪一个实现;

  • 通过 SPI 机制为我们的程序提供拓展功能,在dubbo中,基于 SPI,我们可以很容易的对 Dubbo 进行拓展。例如dubbo当中的protocol,LoadBalance等都是通过SPI机制扩展。

想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们了解下JAVA SPI与dubbo SPI的用法,再分析DUBBO SPI的源码,本文的dubbo源码是基于2.7.5版本。

JAVA 原生SPI 示例

  • 先简单介绍JAVA SPI的应用。首先,我们定义一个Car接口

public interface Car {    String getBrand();}
  • 定义该接口的两个实现类。

public class BM implements Car {    public String getBrand() {        System.out.println("BM car");        return "BM";    }}public class Benz implements Car {    public String getBrand() {        System.out.println("benz car");        return "Benz";    }}
  • 再在resources下创建META-INF/services 文件夹,并创建一个文件,文件名称为Car接口的全限定名com.dubbo.dp.spi.Car。内容为接口实现类的全限定类名。

    com.dubbo.dp.spi.Benzcom.dubbo.dp.spi.BM
  • 使用如下,调用Car接口在配置文件中的所有实现类。

    public class JavaSPITest {  @Test  public void sayHello() throws Exception {      ServiceLoader serviceLoader = ServiceLoader.load(Car.class);      serviceLoader.forEach(car -> System.out.println(car.getBrand()));  }}

    JAVA SPI实现了接口的定义与具体业务实现解耦,应用进程可以根据实际业务情况启用或替换具体组件。

举例:JAVA的java.sql包中就定义一个接口Driver,各个服务提供商实现该接口。当我们需要使用某个数据库时就导入相应的jar包。

缺点
  • 不能按需加载。Java SPI在加载扩展点的时候,会一次性加载所有可用的扩展点,很多是不需要的,会浪费系统资源;

  • 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。

  • 不支持AOP与依赖注入。

  • JAVA SPI可能会丢失加载扩展点异常信息,导致追踪问题很困难;

dubbo SPI示例

  • dubbo重新实现了一套功能更强的 SPI 机制,支持了AOP与依赖注入,并且利用缓存提高加载实现类的性能,同时支持实现类的灵活获取,文中接下来将讲述SPI的应用与原理。

Dubbo的SPI接口都会使用@SPI注解标识,该注解的主要作用就是标记这个接口是一个SPI接口。源码如下:

@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})public @interface SPI {    /**     * default extension name     * 设置默认拓展类     */    String value() default "";}

该注解只作用在接口上,value用来设置默认拓展类

  • 首先讲解下dubbo SPI的使用。在上述Car接口加上@SPI注解,它的实现类暂时不变,配置文件的路径与文件名也暂时不变,文件内容调整如下:

    @SPIpublic interface Car {  String getBrand();}
    benz=com.dubbo.dp.spi.Benzbm=com.dubbo.dp.spi.BM

    配置文件通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。使用如下:

public class JavaSPITest {    @Test    public void sayHello() throws Exception {        ExtensionLoader carExtensionLoader = ExtensionLoader.getExtensionLoader(Car.class);        //按需获取实现类对象        Car car = carExtensionLoader.getExtension("benz");        System.out.println(car.getBrand());    }}

输出结果为

benz carBenz

Dubbo SPI 源码分析

在dubbo SPI示例方法中,我们首先通过ExtensionLoader的 getExtensionLoader 方法获取一个接口的 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象,源码如下,首先是getExtensionLoader 方法:

    /**     * 扩展类加载器缓存,就是扩展点ExtendsLoader实例缓存; key=扩展接口 value=扩展类加载器     */    private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>();    public static  ExtensionLoader getExtensionLoader(Class type) {        //校验传进的type类是否为空        if (type == null) {            throw new IllegalArgumentException("Extension type == null");        }        //校验传进的type类是否为接口        if (!type.isInterface()) {            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");        }        //校验传进的type类是否有@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实例        ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);        if (loader == null) {            //没有就new一个ExtensionLoader实例,并存入本地缓存            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));            loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);        }        return loader;    }

getExtensionLoader会对传进的接口进行校验,其中包括是否有@SPI注解校验,这也是在接口上需加@SPI的原因。然后从EXTENSION_LOADERS缓存中获取该接口类型的ExtensionLoader,如果获取不到,则创建一个该接口类型的ExtensionLoader放入到缓存中,并返回该ExtensionLoader。

注意这里创建ExtensionLoader对象的构造方法如下:ExtensionLoader.getExtensionLoader获取ExtensionFactory接口的拓展类,再通过getAdaptiveExtension从拓展类中获取目标拓展类。它会设置该接口对应的 objectFactory常量为AdaptiveExtensionFactory。因为AdaptiveExtensionFactory类上加了@Adaptive注解,为什么是AdaptiveExtensionFactory原因在之后的文章会解释,且objectFactory也会在后面用到。

    private ExtensionLoader(Class type) {        this.type = type;        //type通常不为ExtensionFactory类,则objectFactory为ExtensionFactory接口的默认扩展类AdaptiveExtensionFactory        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());    }

当通过ExtensionLoader.getExtensionLoader取到接口的加载器Loader之后,在通过getExtension方法获取需要拓展类对象。该方法的整个执行流程如下图所示

参照执行流程图,拓展类对象的获取源码如下:

    /**    * 扩展点实例缓存 key=扩展点名称,value=扩展实例的Holder实例    */    private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>();    /**     * 获取接口拓展类实例     * 1.检查缓存中是否存在     * 2.创建并返回拓展类实例     * @param name  需要获取的配置文件中拓展类的key     * @return     */ public T getExtension(String name) {        if (StringUtils.isEmpty(name)) {            throw new IllegalArgumentException("Extension name == null");        }        if ("true".equals(name)) {            // 获取默认的拓展实现类,即@SPI注解上的默认实现类, 如@SPI("benz")            return getDefaultExtension();        }        // Holder,顾名思义,用于持有目标对象,从缓存中拿,没有则创建        final Holder holder = getOrCreateHolder(name);        Object instance = holder.get();        // 双重检查        if (instance == null) {            synchronized (holder) {                instance = holder.get();                if (instance == null) {                    // 创建拓展实例                    instance = createExtension(name);                    // 设置实例到 holder 中                    holder.set(instance);                }            }        }        return (T) instance;    }    /**     * 获取或者创建一个Holder对象     */    private Holder getOrCreateHolder(String name) {        // 首先通过扩展名从扩展实例缓存中获取Holder对象        Holder holder = cachedInstances.get(name);        if (holder == null) {            //如果没有获取到就new一个空的Holder实例存入缓存            cachedInstances.putIfAbsent(name, new Holder<>());            holder = cachedInstances.get(name);        }        return holder;    }

上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。dubbo中包含了大量的扩展点缓存。这个就是典型的使用空间换时间的做法。也是Dubbo性能强劲的原因之一,包括

  1. 扩展点Class缓存 ,Dubbo SPI在获取扩展点时,会优先从缓存中读取,如果缓存中不存在则加载配置文件,根据配置将Class缓存到内存中,并不会直接初始化。

  2. 扩展点实例缓存 ,Dubbo不仅会缓存Class,还会缓存Class的实例。每次取实例的时候会优先从缓存中取,取不到则从配置中加载,实例化并缓存到内存中。

下面我们来看一下创建拓展对象的过程

    /**     * 扩展实例存入内存中缓存起来; key=扩展类 ; value=扩展类实例     */    private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();    /**     * 创建拓展类实例,包含如下步骤     * 1. 通过 getExtensionClasses 获取所有的拓展类,从配置文件加载获取拓展类的map映射     * 2. 通过反射创建拓展对象     * 3. 向拓展对象中注入依赖(IOC)     * 4. 将拓展对象包裹在相应的 Wrapper 对象中(AOP)     * @param name 需要获取的配置文件中拓展类的key     * @return 拓展类实例     */    private T createExtension(String name) {        // 从配置文件中加载所有的拓展类,可得到"配置项名称"到"配置类"的map,再根据拓展项名称从map中取出相应的拓展类即可        Class clazz = getExtensionClasses().get(name);        if (clazz == null) {            throw findException(name);        }        try {            //从扩展点缓存中获取对应实例对象            T instance = (T) EXTENSION_INSTANCES.get(clazz);            if (instance == null) {                // //如果缓存中不存在此类的扩展点,就通过反射创建实例,并存入缓存                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());                //然后从缓存中获取对应实例                instance = (T) EXTENSION_INSTANCES.get(clazz);            }            // 向实例中注入依赖,通过setter方法自动注入对应的属性实例            injectExtension(instance);            //从缓存中取出所有的包装类,形成包装链            Set> wrapperClasses = cachedWrapperClasses;            if (CollectionUtils.isNotEmpty(wrapperClasses)) {                // 循环创建 Wrapper 实例,形成Wrapper包装链                for (Class wrapperClass : wrapperClasses) {                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));                }            }            //初始化实例并返回            initExtension(instance);            return instance;        } catch (Throwable t) {            throw new IllegalStateException(".....");        }    }

创建拓展类对象步骤分别为:

  1. 通过 getExtensionClasses 从配置文件中加载所有的拓展类,再通过名称获取目标拓展类

  2. 通过反射创建拓展对象

  3. 向拓展对象中注入依赖

  4. 将拓展对象包裹在相应的 Wrapper 对象中

第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。我们先重点分析getExtensionClasses 方法的逻辑getExtensionClasses 方法的逻辑。

从配置文件中加载所有的拓展类
  • 在通过name获取拓展类之前,首先需要根据配置文件解析出拓展项名称与拓展类的映射map,之后再根据拓展项名称从map中取出相应的拓展类即可。getExtensionClasses 方法源码如下

      /**   * 扩展点Class缓存 key=扩展名 ,value=对应的class对象   */  private final Holder>> cachedClasses = new Holder<>();  /**   * 解析配置文件中接口的拓展项名称与拓展类的映射表map   * @return   */  private Map> getExtensionClasses() {      // 从缓存中获取已加载的拓展点class      Map> classes = cachedClasses.get();      //双重检查      if (classes == null) {          synchronized (cachedClasses) {              classes = cachedClasses.get();              if (classes == null) {                  // 加载拓展类                  classes = loadExtensionClasses();                  cachedClasses.set(classes);              }          }      }      return classes;  }

    这里也是先检查缓存,若缓存未命中,则通过loadExtensionClasses 加载拓展类,缓存避免了多次读取配置文件的耗时。下面分析loadExtensionClasses方法加载配置文件的逻辑

      /**   * 三个dubbo SPI默认扫描的路径   */  private static final String SERVICES_DIRECTORY = "META-INF/services/";  private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";  private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";  private Map> loadExtensionClasses() {      //获取并缓存接口的@SPI注解上的默认实现类,@SPI("value")中的value      cacheDefaultExtensionName();      Map> extensionClasses = new HashMap<>();      // 加载指定文件夹下的配置文件,常量包含META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/三个文件夹      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;  }

    loadExtensionClasses 方法总共做了两件事情。首先该方法调用cacheDefaultExtensionName对 SPI 注解进行解析,获取并缓存接口的@SPI注解上的默认拓展类在cachedDefaultName。再调用 loadDirectory 方法加载指定文件夹配置文件。

SPI 注解解析过程比较简单,源码如下。只允许一个默认拓展类。

    private void cacheDefaultExtensionName() {        // 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入,代表接口类        final SPI defaultAnnotation = type.getAnnotation(SPI.class);        if (defaultAnnotation == null) {            return;        }        String value = defaultAnnotation.value();        if ((value = value.trim()).length() > 0) {            String[] names = NAME_SEPARATOR.split(value);            // 检测 SPI 注解内容是否合法(至多一个默认实现类),不合法则抛出异常            if (names.length > 1) {                throw new IllegalStateException("...");            }            // 设置默认拓展类名称            if (names.length == 1) {                cachedDefaultName = names[0];            }        }    }

从源码中可以看出loadExtensionClasses方法加载配置文件的路径有3个,分别为META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/三个文件夹。方法源码如下:

    private void loadDirectory(Map> extensionClasses, String dir, String type) {        loadDirectory(extensionClasses, dir, type, false);    }    /**     * 加载配置文件内容     * @param extensionClasses 拓展类map     * @param dir 文件夹路径     * @param type 接口名称     * @param extensionLoaderClassLoaderFirst 是否先加载ExtensionLoader的ClassLoader     */    private void loadDirectory(Map> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) {        // fileName = 文件夹路径 + type 全限定名        String fileName = dir + type;        try {            Enumeration urls = null;            //获取当前线程的类加载器            ClassLoader classLoader = findClassLoader();            // try to load from ExtensionLoader's ClassLoader first            if (extensionLoaderClassLoaderFirst) {                //获取加载ExtensionLoader.class这个类的类加载器                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();                //如果extensionLoaderClassLoaderFirst=true时,且这两个类加载器不同,就优先使用 extensionLoaderClassLoader                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();                    // 解析并加载配置文件中配置的实现类到extensionClasses中去                    loadResource(extensionClasses, classLoader, resourceURL);                }            }        } catch (Throwable t) {            logger.error("").", t);        }    }

首先找到文件夹下的配置文件,文件名需为接口全限定名。利用类加载器获取文件资源链接,再解析配置文件中配置的实现类添加到extensionClasses中。我们继续看loadResource是如何加载资源的。

    private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {        try {            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {                String line;                // 按行读取配置内容                while ((line = reader.readLine()) != null) {                    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();                                line = line.substring(i + 1).trim();                            }                            if (line.length() > 0) {                                // 通过反射加载类,并通过 loadClass 方法对类进行缓存                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);                            }                        } catch (Throwable t) {                            .....                        }                    }                }            }        } catch (Throwable t) {            logger.error(....);        }    }

loadResource 方法用于读取和解析配置文件,按行读取配置文件,每行以等于号 = 为界,截取键与值,并通过反射加载类,最后通过loadClass方法加载扩展点实现类的class到map中,并对加载到的class进行分类缓存。loadClass方法实现如下

    /**     * 加载扩展点实现类的class到map中,并对加载到的class进行分类缓存     * 比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等     * @param extensionClasses 装载配置文件类的容器     * @param resourceURL 配置文件资源URL     * @param clazz 扩展点实现类的class     * @param name  扩展点实现类的名称,配置文件一行中的key     * @throws NoSuchMethodException     */    private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name) throws NoSuchMethodException {        //判断配置的实现类是否是实现了type接口        if (!type.isAssignableFrom(clazz)) {            throw new IllegalStateException("...");        }        //根据配置中实现类的类型来分类缓存起来        // 检测目标类上是否有 Adaptive 注解,表示这个类就是一个自适应实现类,缓存到cachedAdaptiveClass        if (clazz.isAnnotationPresent(Adaptive.class)) {            cacheAdaptiveClass(clazz);        // 检测 clazz 是否是 Wrapper 类型,判断依据是是否有参数为该接口类的构造方法,缓存到cachedWrapperClasses        } else if (isWrapperClass(clazz)) {            cacheWrapperClass(clazz);        } else {            // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常            clazz.getConstructor();            // 如果配置文件中key的name 为空,则尝试从Extension注解中获取 name,或使用小写的类名作为name。            // 已经弃用,就不在讨论这种方式            if (StringUtils.isEmpty(name)) {                name = findAnnotationName(clazz);                if (name.length() == 0) {                    throw new IllegalStateException("...");                }            }            //使用逗号将name分割为字符串数组            String[] names = NAME_SEPARATOR.split(name);            if (ArrayUtils.isNotEmpty(names)) {                //如果扩展点配置的实现类使用了@Activate注解,就将对应的注解信息缓存起来                cacheActivateClass(clazz, names[0]);                for (String n : names) {                    //缓存扩展点实现类class和扩展点名称的对应关系                    cacheName(clazz, n);                    //最后将class存入extensionClasses                    saveInExtensionClass(extensionClasses, clazz, n);                }            }        }    }

loadClass方法实现了扩展点的分类缓存功能,如包装类,自适应扩展点实现类,普通扩展点实现类等分别进行缓存。需要注意的是自适应扩展点实现类@Adaptive注解,该注解源码如下

     *For example, given String[] {"key1", "key2"}:     * 
    *
  1. find parameter 'key1' in URL, use its value as the extension's name
  2. *
  3. try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL
  4. *
  5. use default extension if 'key2' doesn't exist either
  6. *
  7. otherwise, throw {@link IllegalStateException}
  8. * @return */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; }

该注解的作用是决定哪个自适应拓展类被注入,该目标拓展类是由URL中的参数决定,URL中参数key由该注解的value给出,该key的value作为目标拓展类名称。

  • 如果注解中有多个值,则根据下标从小到大去URL中查找有无对应的key,一旦找到就用该key的value作为目标拓展类名称。

  • 如果这些值在url中都没有对应的key,使用spi上的默认值。

@Adaptive注解可以作用的类上与方法上,绝大部分情况下,该注解是作用在方法上,当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理类。Adaptive 注解在接口方法上,表示拓展的加载逻辑需由框架自动生成。注解在类上,表示拓展的加载逻辑由人工编码完成。

上述的loadClass扫描的是作用在类上。在 Dubbo 中,仅有两个类被@Adaptive注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。
loadClass方法设置缓存cacheAdaptiveClass会导致接口的cacheAdaptiveClass不为空,后面都会默认用这个拓展类,优先级最高。

回到主线,当执行完loadClass方法,配置文件中的所有拓展类已经被加载到map中,到此,关于缓存类加载的过程就分析完了。

Dubbo IOC

当getExtensionClasses()方法执行流程完成后,再根据拓展项name从map中取出相应的拓展类即可获取扩展类Class,通过反射创建实例,并通过injectExtension(instance);方法向实例中注入依赖 这一部分在下一篇文章中将会介绍。

DUBBO AOP

当执行完injectExtension(T instance)方法,在createExtension(String name)就开始执行wrapper的包装,类似于spring中的AOP,dubbo运用了装饰器模式。

        Set> wrapperClasses = cachedWrapperClasses;        if (CollectionUtils.isNotEmpty(wrapperClasses)) {            // 循环创建 Wrapper 实例,形成Wrapper包装链            for (Class wrapperClass : wrapperClasses) {                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));            }        }

这里的cachedWrapperClasses通过前面的分析已经知道,就是在解析配置文件时判断是否是Wrapper类型的拓展类,++判断依据为构造方法中是否有参数为该接口类++,则缓存到cachedWrapperClasses。

执行wrapperClass.getConstructor(type).newInstance(instance)将获取包装类的构造方法,方法的参数就是该接口类型,并通过反射生成一个包含该拓展类实例的包装对象,再通过injectExtension注入包装对象的依赖。如此循环,得到成Wrapper包装链。这里需注意的是,配置文件中内容靠后的包装类会包装在相对外层。下面是DUBBO AOP的例子,我们继续使用上面的Car接口与实现类,同时新增一个实现类,代码如下

public class CarWrapper implements Car{    private Car car;    /**     * 有一个包含该接口类参数的构造方法     */    public CarWrapper(Car car) {        this.car = car;    }    @Override    public String getBrand() {        System.out.println("校验");        String result = car.getBrand();        System.out.println("记录日志");        return result;    }}

该接口实现了Car,并且持有一个Car对象,同时拥有一个构造方法且该构造方法的参数为Car接口类型,那么该类会被识别为接口的Wrapper类。则可以在方法中做一些切面功能的扩展,再调用car对象执行其方法实现AOP功能。

将配置文件内容中添加wrapper实现类,如下

benz=com.xiaoju.automarket.energy.scm.rpc.Benzbm=com.xiaoju.automarket.energy.scm.rpc.BMcom.xiaoju.automarket.energy.scm.rpc.CarWrapper #包装类

执行如下代码获取benz的拓展类实例后,调用其方法,将会被Wrapper包装类

public class TestAOP {    public static void main(String[] args) {        ExtensionLoader carExtensionLoader = ExtensionLoader.getExtensionLoader(Car.class);        Car car = carExtensionLoader.getExtension("benz");        System.out.println(car.getBrand(null));    }}

结果如下

校验benz car记录日志Benz

与我们预想的一致,实现了Wrapper类的切面功能。

看完上述内容,你们掌握dubbo的SPI应用与原理是什么的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!

文件 缓存 方法 接口 配置 实例 注解 对象 包装 源码 内容 名称 参数 文件夹 类型 目标 反射 作用 功能 就是 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技公司 中智农互联网科技 互联网科技公司排名 北京路客互联网科技有限公司 智能科技公司是互联网公司吗 互联网为科技 没上市的科技互联网公司 互联网科技公司发展 广州互联网络科技有限公司 互联网科技公司名称 济南华为互联网科技产业总部基地 佳云科技和互联网广告 宜昌元轩互联网科技有限公司 长沙茂晟互联网科技有限公司 无锡工业互联网科技有限公司 互联网与数字科技关系 数字科技和互联网金融哪个好 长沙易易互联网科技有限公司 现代科技的标志之一的互联网 知域互联网科技有限公司官网 互联网黑科技 深圳九邦互联网科技有限公司 互联网科技集团 互联网科技视频 中国的互联网科技企业的崛起 中国互联网有什么科技含量 工业互联网山东科技大学 互联网科技手机排行榜 互联网科技博主张洲 宁波允成互联网科技有限公司
0