• 2.1 jdk-spi的实现原理


    dubbo-spi是在jdk-spi的基础上进行重写优化,下面看一下jdk-spi。

    一、作用

    • 为接口自动寻找实现类。

    二、实现方式

    • 标准制定者制定接口
    • 不同厂商编写针对于该接口的实现类,并在jar的“classpath:META-INF/services/全接口名称”文件中指定相应的实现类全类名
    • 开发者直接引入相应的jar,就可以实现为接口自动寻找实现类的功能

    三、使用方法

    注意:示例以Log体系为例,但是实际中的Log体系并不是这样来实现的。

    1、pom.xml

    1 <?xml version="1.0" encoding="UTF-8"?>
    2 <project xmlns="http://maven.apache.org/POM/4.0.0"
    3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5     <modelVersion>4.0.0</modelVersion>
    6     <groupId>com.hulk</groupId>
    7     <artifactId>java-spi</artifactId>
    8     <version>1.0-SNAPSHOT</version>
    9 </project>

    2、标准接口:com.hulk.javaspi.Log

    1 package com.hulk.javaspi;
    2 
    3 public interface Log {
    4     void execute();
    5 }

    3、具体实现1:com.hulk.javaspi.Log4j

    1 package com.hulk.javaspi;
    2 
    3 public class Log4j implements Log {
    4     @Override
    5     public void execute() {
    6         System.out.println("log4j ...");
    7     }
    8 }

    4、具体实现2:com.hulk.javaspi.Logback

    1 package com.hulk.javaspi;
    2 
    3 public class Logback implements Log {
    4     @Override
    5     public void execute() {
    6         System.out.println("logback ...");
    7     }
    8 }

    5、指定使用的实现文件:META-INF/services/com.hulk.javaspi.Log

    1 com.hulk.javaspi.Logback

    注意

    • 这里指定了实现类Logback,那么加载的时候就会自动为Log接口指定实现类为Logback。
    • 这里也可以指定两个实现类,那么在实际中使用哪一个实现类,就需要使用额外的手段来控制。
      1 com.hulk.javaspi.Logback
      2 com.hulk.javaspi.Log4j

    6、加载实现主类:com.hulk.javaspi.Main

     1 package com.hulk.javaspi;
     2 
     3 import java.util.Iterator;
     4 import java.util.ServiceLoader;
     5 
     6 public class Main {
     7     public static void main(String[] args) {
     8         ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
     9         Iterator<Log> iterator = serviceLoader.iterator();
    10         while (iterator.hasNext()) {
    11             Log log = iterator.next();
    12             log.execute();
    13         }
    14     }
    15 }

    注意:

    • ServiceLoader不是实例化以后,就去读取配置文件中的具体实现,并进行实例化。而是等到使用迭代器去遍历的时候,才会加载对应的配置文件去解析,调用hasNext方法的时候会去加载配置文件进行解析,调用next方法的时候进行实例化并缓存 - 具体见“源码分析”

    现在来解析Main的源码。

    四、源码解析

    1、获取ServiceLoader

    1 ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);

    源码:

    首先来看一下ServiceLoader的6个属性

    1      private static final String PREFIX = "META-INF/services/";//定义实现类的接口文件所在的目录
    2      private final Class<S> service;//接口
    3      private final ClassLoader loader;//定位、加载、实例化实现类
    4      private final AccessControlContext acc;//权限控制上下文
    5      private LinkedHashMap<String,S> providers = new LinkedHashMap<>();//以初始化的顺序缓存<接口全名称, 实现类实例>
    6      private LazyIterator lookupIterator;//真正进行迭代的迭代器

    其中LazyIterator是ServiceLoader的一个内部类,在迭代部分会说。

     1     public static <S> ServiceLoader<S> load(Class<S> service) {
     2         ClassLoader cl = Thread.currentThread().getContextClassLoader();
     3         return ServiceLoader.load(service, cl);
     4     }
     5 
     6     public static <S> ServiceLoader<S> load(Class<S> service,
     7                                             ClassLoader loader) {
     8         return new ServiceLoader<>(service, loader);
     9     }
    10 
    11     private ServiceLoader(Class<S> svc, ClassLoader cl) {
    12         service = Objects.requireNonNull(svc, "Service interface cannot be null");
    13         loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    14         acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    15         reload();
    16     }
    17 
    18     public void reload() {
    19         providers.clear();//清空缓存
    20         lookupIterator = new LazyIterator(service, loader);
    21     }

    这样一个ServiceLoader实例就创建成功了。在创建的过程中,我们看到还实例化了一个LazyIterator,该类下边会说。

    2、获取迭代器并迭代

    1          Iterator<Log> iterator = serviceLoader.iterator();
    2          while (iterator.hasNext()) {
    3              Log log = iterator.next();
    4              log.execute();
    5          }

    外层迭代器:

     1     public Iterator<S> iterator() {
     2         return new Iterator<S>() {
     3 
     4             Iterator<Map.Entry<String,S>> knownProviders
     5                 = providers.entrySet().iterator();
     6 
     7             public boolean hasNext() {
     8                 if (knownProviders.hasNext())
     9                     return true;
    10                 return lookupIterator.hasNext();
    11             }
    12 
    13             public S next() {
    14                 if (knownProviders.hasNext())
    15                     return knownProviders.next().getValue();
    16                 return lookupIterator.next();
    17             }
    18 
    19             public void remove() {
    20                 throw new UnsupportedOperationException();
    21             }
    22 
    23         };
    24     }

     从查找过程hasNext()和迭代过程next()来看。

    • hasNext():先从provider(缓存)中查找,如果有,直接返回true;如果没有,通过LazyIterator来进行查找
    • next():先从provider(缓存)中直接获取,如果有,直接返回实现类对象实例;如果没有,通过LazyIterator来进行获取

    下面来看一下,LazyIterator这个类。首先看一下他的属性:

    1         Class<S> service;//接口
    2         ClassLoader loader;//类加载器
    3         Enumeration<URL> configs = null;//存放配置文件
    4         Iterator<String> pending = null;//存放配置文件中的内容,并存储为ArrayList,即存储多个实现类名称
    5         String nextName = null;//当前处理的实现类名称

    其中,service和loader在上述实例化ServiceLoader的时候就已经实例化好了。

    下面看一下hasNext():

     1         public boolean hasNext() {
     2             if (acc == null) {
     3                 return hasNextService();
     4             } else {
     5                 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
     6                     public Boolean run() { return hasNextService(); }
     7                 };
     8                 return AccessController.doPrivileged(action, acc);
     9             }
    10         }
    11 
    12         private boolean hasNextService() {
    13             if (nextName != null) {
    14                 return true;
    15             }
    16             if (configs == null) {
    17                 try {
    18                     String fullName = PREFIX + service.getName();
    19                     if (loader == null)
    20                         configs = ClassLoader.getSystemResources(fullName);
    21                     else
    22                         configs = loader.getResources(fullName);
    23                 } catch (IOException x) {
    24                     fail(service, "Error locating configuration files", x);
    25                 }
    26             }
    27             while ((pending == null) || !pending.hasNext()) {
    28                 if (!configs.hasMoreElements()) {
    29                     return false;
    30                 }
    31                 pending = parse(service, configs.nextElement());
    32             }
    33             nextName = pending.next();
    34             return true;
    35         }

    hasNextService()中,核心实现如下:

    • 首先使用loader加载配置文件,此时找到了META-INF/services/com.hulk.javaspi.Log文件;
    • 然后解析这个配置文件,并将各个实现类名称存储在pending的ArrayList中; -->  此时[ com.hulk.javaspi.Logback ]
    • 最后指定nextName; --> 此时nextName=com.hulk.javaspi.Logback

     下面看一下next():

     1         public S next() {
     2             if (acc == null) {
     3                 return nextService();
     4             } else {
     5                 PrivilegedAction<S> action = new PrivilegedAction<S>() {
     6                     public S run() { return nextService(); }
     7                 };
     8                 return AccessController.doPrivileged(action, acc);
     9             }
    10         }
    11 
    12         private S nextService() {
    13             if (!hasNextService())
    14                 throw new NoSuchElementException();
    15             String cn = nextName;
    16             nextName = null;
    17             Class<?> c = null;
    18             try {
    19                 c = Class.forName(cn, false, loader);
    20             } catch (ClassNotFoundException x) {
    21                 fail(service,
    22                      "Provider " + cn + " not found");
    23             }
    24             if (!service.isAssignableFrom(c)) {
    25                 fail(service,
    26                      "Provider " + cn  + " not a subtype");
    27             }
    28             try {
    29                 S p = service.cast(c.newInstance());
    30                 providers.put(cn, p);
    31                 return p;
    32             } catch (Throwable x) {
    33                 fail(service,
    34                      "Provider " + cn + " could not be instantiated",
    35                      x);
    36             }
    37             throw new Error();          // This cannot happen
    38         }

    nextService()中,核心实现如下:

    • 首先加载nextName代表的类Class,这里为com.hulk.javaspi.Logback;
    • 之后创建该类的实例,并转型为所需的接口类型
    • 最后存储在provider中,供后续查找,最后返回转型后的实现类实例。

    再next()之后,拿到实现类实例后,就可以执行其具体的方法了。

    五、缺点

    • 查找一个具体的实现需要遍历查找,耗时;-->此时就体现出Collection相较于Map差的地方,map可以直接根据key来获取具体的实现 (dubbo-spi实现了根据key获取具体实现的方式)
  • 相关阅读:
    京东css三角形做法
    css三角
    java 优势和劣势
    windows常用命令
    25 abstract 抽象
    24static 和final关键字之final
    面试题----static
    定义变量在前和定义变量在后
    java注解
    23static 和final关键词 之static 和运行顺序
  • 原文地址:https://www.cnblogs.com/java-zhao/p/7617143.html
Copyright © 2020-2023  润新知