Java-SPI机制详解

简介

SPI 全称为 Service Provider Interface,是一种服务发现机制。

SPI 的本质是将接口实现类全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。

这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。


Java SPI

  1. 定义接口

    1
    2
    3
    public interface Robot {
    void sayHello();
    }
  2. 定义实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class OptimusPrime implements Robot {

    @Override
    public void sayHello() {
    System.out.println("Hello, I am Optimus Prime.");
    }
    }

    public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
    System.out.println("Hello, I am Bumblebee.");
    }
    }
  3. 添加配置项(META-INF/services/ 下)

    1
    2
    org.apache.spi.OptimusPrime
    org.apache.spi.Bumblebee
  4. 实现测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class JavaSPITest {

    @Test
    public void sayHello() throws Exception {
    ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
    System.out.println("Java SPI");
    serviceLoader.forEach(Robot::sayHello);
    }
    }
  5. 结果展示

    1
    2
    3
    Java SPI
    Hello, I am Optimus Prime.
    Hello, I am Bumblebee.

    从测试结果来看,两个实现类均可以成功的被调用,并且输出了对应的内容。


用途

数据库DriverManager、 Spring、 ConfigurableBeanFactory 等都用到了SPI机制,这里以数据库DriverManager 为例,看一下其实现的内幕。

DriverManager 是jdbc 里管理和注册不同数据库driver 的工具类。针对一个数据库,可能会存在着不同的数据库驱动实现。
我们在使用特定的驱动实现时,不希望修改现有的代码,而希望通过一个简单的配置就可以达到效果。
在使用mysql驱动的时候,会有一个疑问,DriverManager 是怎么获得某确定驱动类的?我们在运用Class.forName(“com.mysql.jdbc.Driver”) 加载mysql 驱动后,就会执行其中的静态代码把driver 注册到DriverManager 中,以便后续的使用。
在JDBC4.0 之前,连接数据库的时候,通常会用Class.forName(“com.mysql.jdbc.Driver”) 这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0 之后不需要Class.forName 来加载驱动,直接获取连接即可,这里使用了Java 的SPI 扩展机制来实现。
在java 中定义了接口java.sql.Driver ,并没有具体的实现,具体的实现都是由不同厂商来提供的。


源码原理

  1. 应用程序调用 ServiceLoader.load 方法
    ServiceLoader.load 方法内先创建一个新的ServiceLoader,并实例化该类中的成员变数,包括:
  • ClassLoader loader(类载入器)
  • AccessControlContext acc(访问控制器)
  • LinkedHashMap<String, S> providers(用于缓存载入成功的类)
  • LazyIterator lookupIterator(实现迭代器功能)
  1. 应用程序通过迭代器获取对象实例
    ServiceLoader 先判断成员变量 providers 对象中否有缓存实例对象,如果有缓存,直接返回。
    如果没有缓存,执行类的装载:读取 META-INF/services/ 下的配置文件,获得所有能被实例化的类的名称,通过反射方法 Class.forName() 载入类对象,并用 instance() 方法将类实例化。把实例化后的类缓存到providers 对象中然后返回实例对象。

  2. ServiceLoader 源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
    560
    561
    562
    563
    564
    565
    566
    /*
    * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
    * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
    */

    package java.util;

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.URL;
    import java.security.AccessController;
    import java.security.AccessControlContext;
    import java.security.PrivilegedAction;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.Iterator;
    import java.util.List;
    import java.util.NoSuchElementException;


    /**
    * A simple service-provider loading facility.
    *
    * <p> A <i>service</i> is a well-known set of interfaces and (usually
    * abstract) classes. A <i>service provider</i> is a specific implementation
    * of a service. The classes in a provider typically implement the interfaces
    * and subclass the classes defined in the service itself. Service providers
    * can be installed in an implementation of the Java platform in the form of
    * extensions, that is, jar files placed into any of the usual extension
    * directories. Providers can also be made available by adding them to the
    * application's class path or by some other platform-specific means.
    *
    * <p> For the purpose of loading, a service is represented by a single type,
    * that is, a single interface or abstract class. (A concrete class can be
    * used, but this is not recommended.) A provider of a given service contains
    * one or more concrete classes that extend this <i>service type</i> with data
    * and code specific to the provider. The <i>provider class</i> is typically
    * not the entire provider itself but rather a proxy which contains enough
    * information to decide whether the provider is able to satisfy a particular
    * request together with code that can create the actual provider on demand.
    * The details of provider classes tend to be highly service-specific; no
    * single class or interface could possibly unify them, so no such type is
    * defined here. The only requirement enforced by this facility is that
    * provider classes must have a zero-argument constructor so that they can be
    * instantiated during loading.
    *
    * <p><a name="format"> A service provider is identified by placing a
    * <i>provider-configuration file</i> in the resource directory
    * <tt>META-INF/services</tt>.</a> The file's name is the fully-qualified <a
    * href="../lang/ClassLoader.html#name">binary name</a> of the service's type.
    * The file contains a list of fully-qualified binary names of concrete
    * provider classes, one per line. Space and tab characters surrounding each
    * name, as well as blank lines, are ignored. The comment character is
    * <tt>'#'</tt> (<tt>'&#92;u0023'</tt>,
    * <font style="font-size:smaller;">NUMBER SIGN</font>); on
    * each line all characters following the first comment character are ignored.
    * The file must be encoded in UTF-8.
    *
    * <p> If a particular concrete provider class is named in more than one
    * configuration file, or is named in the same configuration file more than
    * once, then the duplicates are ignored. The configuration file naming a
    * particular provider need not be in the same jar file or other distribution
    * unit as the provider itself. The provider must be accessible from the same
    * class loader that was initially queried to locate the configuration file;
    * note that this is not necessarily the class loader from which the file was
    * actually loaded.
    *
    * <p> Providers are located and instantiated lazily, that is, on demand. A
    * service loader maintains a cache of the providers that have been loaded so
    * far. Each invocation of the {@link #iterator iterator} method returns an
    * iterator that first yields all of the elements of the cache, in
    * instantiation order, and then lazily locates and instantiates any remaining
    * providers, adding each one to the cache in turn. The cache can be cleared
    * via the {@link #reload reload} method.
    *
    * <p> Service loaders always execute in the security context of the caller.
    * Trusted system code should typically invoke the methods in this class, and
    * the methods of the iterators which they return, from within a privileged
    * security context.
    *
    * <p> Instances of this class are not safe for use by multiple concurrent
    * threads.
    *
    * <p> Unless otherwise specified, passing a <tt>null</tt> argument to any
    * method in this class will cause a {@link NullPointerException} to be thrown.
    *
    *
    * <p><span style="font-weight: bold; padding-right: 1em">Example</span>
    * Suppose we have a service type <tt>com.example.CodecSet</tt> which is
    * intended to represent sets of encoder/decoder pairs for some protocol. In
    * this case it is an abstract class with two abstract methods:
    *
    * <blockquote><pre>
    * public abstract Encoder getEncoder(String encodingName);
    * public abstract Decoder getDecoder(String encodingName);</pre></blockquote>
    *
    * Each method returns an appropriate object or <tt>null</tt> if the provider
    * does not support the given encoding. Typical providers support more than
    * one encoding.
    *
    * <p> If <tt>com.example.impl.StandardCodecs</tt> is an implementation of the
    * <tt>CodecSet</tt> service then its jar file also contains a file named
    *
    * <blockquote><pre>
    * META-INF/services/com.example.CodecSet</pre></blockquote>
    *
    * <p> This file contains the single line:
    *
    * <blockquote><pre>
    * com.example.impl.StandardCodecs # Standard codecs</pre></blockquote>
    *
    * <p> The <tt>CodecSet</tt> class creates and saves a single service instance
    * at initialization:
    *
    * <blockquote><pre>
    * private static ServiceLoader&lt;CodecSet&gt; codecSetLoader
    * = ServiceLoader.load(CodecSet.class);</pre></blockquote>
    *
    * <p> To locate an encoder for a given encoding name it defines a static
    * factory method which iterates through the known and available providers,
    * returning only when it has located a suitable encoder or has run out of
    * providers.
    *
    * <blockquote><pre>
    * public static Encoder getEncoder(String encodingName) {
    * for (CodecSet cp : codecSetLoader) {
    * Encoder enc = cp.getEncoder(encodingName);
    * if (enc != null)
    * return enc;
    * }
    * return null;
    * }</pre></blockquote>
    *
    * <p> A <tt>getDecoder</tt> method is defined similarly.
    *
    *
    * <p><span style="font-weight: bold; padding-right: 1em">Usage Note</span> If
    * the class path of a class loader that is used for provider loading includes
    * remote network URLs then those URLs will be dereferenced in the process of
    * searching for provider-configuration files.
    *
    * <p> This activity is normal, although it may cause puzzling entries to be
    * created in web-server logs. If a web server is not configured correctly,
    * however, then this activity may cause the provider-loading algorithm to fail
    * spuriously.
    *
    * <p> A web server should return an HTTP 404 (Not Found) response when a
    * requested resource does not exist. Sometimes, however, web servers are
    * erroneously configured to return an HTTP 200 (OK) response along with a
    * helpful HTML error page in such cases. This will cause a {@link
    * ServiceConfigurationError} to be thrown when this class attempts to parse
    * the HTML page as a provider-configuration file. The best solution to this
    * problem is to fix the misconfigured web server to return the correct
    * response code (HTTP 404) along with the HTML error page.
    *
    * @param <S>
    * The type of the service to be loaded by this loader
    *
    * @author Mark Reinhold
    * @since 1.6
    */

    public final class ServiceLoader<S>
    implements Iterable<S>
    {

    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

    /**
    * Clear this loader's provider cache so that all providers will be
    * reloaded.
    *
    * <p> After invoking this method, subsequent invocations of the {@link
    * #iterator() iterator} method will lazily look up and instantiate
    * providers from scratch, just as is done by a newly-created loader.
    *
    * <p> This method is intended for use in situations in which new providers
    * can be installed into a running Java virtual machine.
    */
    public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
    }

    private static void fail(Class<?> service, String msg, Throwable cause)
    throws ServiceConfigurationError
    {
    throw new ServiceConfigurationError(service.getName() + ": " + msg,
    cause);
    }

    private static void fail(Class<?> service, String msg)
    throws ServiceConfigurationError
    {
    throw new ServiceConfigurationError(service.getName() + ": " + msg);
    }

    private static void fail(Class<?> service, URL u, int line, String msg)
    throws ServiceConfigurationError
    {
    fail(service, u + ":" + line + ": " + msg);
    }

    // Parse a single line from the given configuration file, adding the name
    // on the line to the names list.
    //
    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
    List<String> names)
    throws IOException, ServiceConfigurationError
    {
    String ln = r.readLine();
    if (ln == null) {
    return -1;
    }
    int ci = ln.indexOf('#');
    if (ci >= 0) ln = ln.substring(0, ci);
    ln = ln.trim();
    int n = ln.length();
    if (n != 0) {
    if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
    fail(service, u, lc, "Illegal configuration-file syntax");
    int cp = ln.codePointAt(0);
    if (!Character.isJavaIdentifierStart(cp))
    fail(service, u, lc, "Illegal provider-class name: " + ln);
    for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
    cp = ln.codePointAt(i);
    if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
    fail(service, u, lc, "Illegal provider-class name: " + ln);
    }
    if (!providers.containsKey(ln) && !names.contains(ln))
    names.add(ln);
    }
    return lc + 1;
    }

    // Parse the content of the given URL as a provider-configuration file.
    //
    // @param service
    // The service type for which providers are being sought;
    // used to construct error detail strings
    //
    // @param u
    // The URL naming the configuration file to be parsed
    //
    // @return A (possibly empty) iterator that will yield the provider-class
    // names in the given configuration file that are not yet members
    // of the returned set
    //
    // @throws ServiceConfigurationError
    // If an I/O error occurs while reading from the given URL, or
    // if a configuration-file format error is detected
    //
    private Iterator<String> parse(Class<?> service, URL u)
    throws ServiceConfigurationError
    {
    InputStream in = null;
    BufferedReader r = null;
    ArrayList<String> names = new ArrayList<>();
    try {
    in = u.openStream();
    r = new BufferedReader(new InputStreamReader(in, "utf-8"));
    int lc = 1;
    while ((lc = parseLine(service, u, r, lc, names)) >= 0);
    } catch (IOException x) {
    fail(service, "Error reading configuration file", x);
    } finally {
    try {
    if (r != null) r.close();
    if (in != null) in.close();
    } catch (IOException y) {
    fail(service, "Error closing configuration file", y);
    }
    }
    return names.iterator();
    }

    // Private inner class implementing fully-lazy provider lookup
    //
    private class LazyIterator
    implements Iterator<S>
    {

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
    this.service = service;
    this.loader = loader;
    }

    private boolean hasNextService() {
    if (nextName != null) {
    return true;
    }
    if (configs == null) {
    try {
    String fullName = PREFIX + service.getName();
    if (loader == null)
    configs = ClassLoader.getSystemResources(fullName);
    else
    configs = loader.getResources(fullName);
    } catch (IOException x) {
    fail(service, "Error locating configuration files", x);
    }
    }
    while ((pending == null) || !pending.hasNext()) {
    if (!configs.hasMoreElements()) {
    return false;
    }
    pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
    }

    private S nextService() {
    if (!hasNextService())
    throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
    c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
    fail(service,
    "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
    fail(service,
    "Provider " + cn + " not a subtype");
    }
    try {
    S p = service.cast(c.newInstance());
    providers.put(cn, p);
    return p;
    } catch (Throwable x) {
    fail(service,
    "Provider " + cn + " could not be instantiated",
    x);
    }
    throw new Error(); // This cannot happen
    }

    public boolean hasNext() {
    if (acc == null) {
    return hasNextService();
    } else {
    PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
    public Boolean run() { return hasNextService(); }
    };
    return AccessController.doPrivileged(action, acc);
    }
    }

    public S next() {
    if (acc == null) {
    return nextService();
    } else {
    PrivilegedAction<S> action = new PrivilegedAction<S>() {
    public S run() { return nextService(); }
    };
    return AccessController.doPrivileged(action, acc);
    }
    }

    public void remove() {
    throw new UnsupportedOperationException();
    }

    }

    /**
    * Lazily loads the available providers of this loader's service.
    *
    * <p> The iterator returned by this method first yields all of the
    * elements of the provider cache, in instantiation order. It then lazily
    * loads and instantiates any remaining providers, adding each one to the
    * cache in turn.
    *
    * <p> To achieve laziness the actual work of parsing the available
    * provider-configuration files and instantiating providers must be done by
    * the iterator itself. Its {@link java.util.Iterator#hasNext hasNext} and
    * {@link java.util.Iterator#next next} methods can therefore throw a
    * {@link ServiceConfigurationError} if a provider-configuration file
    * violates the specified format, or if it names a provider class that
    * cannot be found and instantiated, or if the result of instantiating the
    * class is not assignable to the service type, or if any other kind of
    * exception or error is thrown as the next provider is located and
    * instantiated. To write robust code it is only necessary to catch {@link
    * ServiceConfigurationError} when using a service iterator.
    *
    * <p> If such an error is thrown then subsequent invocations of the
    * iterator will make a best effort to locate and instantiate the next
    * available provider, but in general such recovery cannot be guaranteed.
    *
    * <blockquote style="font-size: smaller; line-height: 1.2"><span
    * style="padding-right: 1em; font-weight: bold">Design Note</span>
    * Throwing an error in these cases may seem extreme. The rationale for
    * this behavior is that a malformed provider-configuration file, like a
    * malformed class file, indicates a serious problem with the way the Java
    * virtual machine is configured or is being used. As such it is
    * preferable to throw an error rather than try to recover or, even worse,
    * fail silently.</blockquote>
    *
    * <p> The iterator returned by this method does not support removal.
    * Invoking its {@link java.util.Iterator#remove() remove} method will
    * cause an {@link UnsupportedOperationException} to be thrown.
    *
    * @implNote When adding providers to the cache, the {@link #iterator
    * Iterator} processes resources in the order that the {@link
    * java.lang.ClassLoader#getResources(java.lang.String)
    * ClassLoader.getResources(String)} method finds the service configuration
    * files.
    *
    * @return An iterator that lazily loads providers for this loader's
    * service
    */
    public Iterator<S> iterator() {
    return new Iterator<S>() {

    Iterator<Map.Entry<String,S>> knownProviders
    = providers.entrySet().iterator();

    public boolean hasNext() {
    if (knownProviders.hasNext())
    return true;
    return lookupIterator.hasNext();
    }

    public S next() {
    if (knownProviders.hasNext())
    return knownProviders.next().getValue();
    return lookupIterator.next();
    }

    public void remove() {
    throw new UnsupportedOperationException();
    }

    };
    }

    /**
    * Creates a new service loader for the given service type and class
    * loader.
    *
    * @param <S> the class of the service type
    *
    * @param service
    * The interface or abstract class representing the service
    *
    * @param loader
    * The class loader to be used to load provider-configuration files
    * and provider classes, or <tt>null</tt> if the system class
    * loader (or, failing that, the bootstrap class loader) is to be
    * used
    *
    * @return A new service loader
    */
    public static <S> ServiceLoader<S> load(Class<S> service,
    ClassLoader loader)
    {
    return new ServiceLoader<>(service, loader);
    }

    /**
    * Creates a new service loader for the given service type, using the
    * current thread's {@linkplain java.lang.Thread#getContextClassLoader
    * context class loader}.
    *
    * <p> An invocation of this convenience method of the form
    *
    * <blockquote><pre>
    * ServiceLoader.load(<i>service</i>)</pre></blockquote>
    *
    * is equivalent to
    *
    * <blockquote><pre>
    * ServiceLoader.load(<i>service</i>,
    * Thread.currentThread().getContextClassLoader())</pre></blockquote>
    *
    * @param <S> the class of the service type
    *
    * @param service
    * The interface or abstract class representing the service
    *
    * @return A new service loader
    */
    public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
    }

    /**
    * Creates a new service loader for the given service type, using the
    * extension class loader.
    *
    * <p> This convenience method simply locates the extension class loader,
    * call it <tt><i>extClassLoader</i></tt>, and then returns
    *
    * <blockquote><pre>
    * ServiceLoader.load(<i>service</i>, <i>extClassLoader</i>)</pre></blockquote>
    *
    * <p> If the extension class loader cannot be found then the system class
    * loader is used; if there is no system class loader then the bootstrap
    * class loader is used.
    *
    * <p> This method is intended for use when only installed providers are
    * desired. The resulting service will only find and load providers that
    * have been installed into the current Java virtual machine; providers on
    * the application's class path will be ignored.
    *
    * @param <S> the class of the service type
    *
    * @param service
    * The interface or abstract class representing the service
    *
    * @return A new service loader
    */
    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    ClassLoader prev = null;
    while (cl != null) {
    prev = cl;
    cl = cl.getParent();
    }
    return ServiceLoader.load(service, prev);
    }

    /**
    * Returns a string describing this service.
    *
    * @return A descriptive string
    */
    public String toString() {
    return "java.util.ServiceLoader[" + service.getName() + "]";
    }

    }

优缺点

SPI 机制作为JDK 内置的一种服务发现机制,但由于其实现简单,也有不少缺点的。

优点:
使用 Java SPI 机制的优势是实现解耦,使得接口的定义与具体业务实现分离,而不是耦合在一起。应用程序可以根据实际业务情况启用或替换具体组件。

缺点:

  • 不能按需加载。虽然 ServiceLoader 做了延迟载入,但是基本只能通过遍历全部获取,也就是接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
  • 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用 ServiceLoader 类的实例是不安全的。
  • 加载不到实现类时抛出并不是真正原因的异常,错误很难定位。

鉴于Java SPI 的诸多缺点,很多系统都是自己实现了一套类加载机制,例如Dubbo 。用户也可以自定义classloader+ 反射机制来加载,实现并不复杂。此外开源的类加载解决方案有Plugin Framework for Java (PF4J) 等。


总结

最近这几天在看Spring 的官方文档,突然发现Java 的基础还是很重要的,官方文档也要坚持看,能对以前用的东西有更加深的理解。


个人备注

此博客内容均为作者学习所做笔记,侵删!
若转作其他用途,请注明来源!