[置顶] 浅谈 Java 中的 SPI 机制

40人阅读 评论(4) 收藏 举报
分类:

面向接口编程

面向接口编程就是先把客户的业务逻辑先提取出来,作为接口,业务具体实现通过该接口的实现类来完成。
当客户需求变化时,只需编写该业务逻辑的新的实现类,不需要改写现有代码,减少对系统的影响。
其遵循的思想是:对扩展开放,对修改关闭。在使用面向接口的编程过程中,将具体逻辑与实现分开,减少了各个类之间的相互依赖。
面向接口编程的优点:

  • 降低程序的耦合性
  • 易于程序的扩展
  • 有利于程序的维护

面向接口编程更多的是体现在服务器端,但是在我们客户端还是需要将具体的业务处理逻辑(接口或者抽象类的实现类)传递给服务器端。所以在某种程度上面向接口编程只是把服务器端的耦合度降低了,客户端依旧存在过高的耦合度。一个典型的例子就是 JDBC 。

更进一步

如何不把实现类硬编码到程序中?
或者说我们要在更改具体实现时不需要重新编译源代码?


import java.sql.*;

public class jdbc {
    public static void main(String[] args) {
        String driverClass = "com.mysql.jdbc.Driver";
        String jdbcUrl = "jdbc:mysql:///test?useUnicode=true&characterEncoding=UTF-8&useSSL=true";
        String user = "root";
        String password = "root";
        String sql = "SELECT * FROM persons";
        Connection conn = null;

        // 有碍观瞻
        try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            conn = DriverManager.getConnection(jdbcUrl, user, password);
            System.out.println(conn);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Class.forName("com.mysql.jdbc.Driver"); 这句代码的存在多少有点让人以外?因为此处又和实现耦合一起了。为了解耦,JDBC使用了SPI机制,也就是服务发现机制。
详细解析流程见:
JDBC解析1
JDBC解析2


ServiceLoader 源码解析

SPI 的全名为Service Provider Interface。

简单来说就是通过配置文件指定接口的实现类。

当我们开发一套框架、一套机制、一个插件或一套API时候,如果需要第三方的服务支持(接口或者抽象类的实现类),可以直接写死到代码里面,但这种方式耦合太强,不利于切换到其它服务,好的方法是写一个配置文件指定服务的实现方,幸运的是 java 的 SPI 机制已经帮我们做好了。通过阅读源码我们也会了解到 SPI 机制的弊端。

核心属性:

// 默认读取 META-INF/services/XXX (XXX-->接口或者抽象类的全限定类名) 
private static final String PREFIX = "META-INF/services/";
// 接口或者抽象类所对应的 Class
private final Class<S> service;
// 存放已经加载的接口或者抽象类(以 String 保存)以及其实现类对应的对象
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懒加载迭代器
private LazyIterator lookupIterator;

核心方法:

// 唯一的私有构造方法(故 ServiceLoader 是以单例运行的)
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();                                                                          
}
// 重置缓存以及懒加载器
public void reload() {                                 
    providers.clear();                                 
    lookupIterator = new LazyIterator(service, loader);
}
// 构造 ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service) {          
    ClassLoader cl = Thread.currentThread().getContextClassLoader(); 
    return ServiceLoader.load(service, cl);                          
}                                                                     
public static <S> ServiceLoader<S> load(Class<S> service,   
                                        ClassLoader loader) 
{                                                           
    return new ServiceLoader<>(service, loader);            
}
// 迭代器
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();   
        }                                                

    };                                                   
}                                                                                                                                                                                                                                                             

懒加载器

private class LazyIterator                                                              
        implements Iterator<S>                                                          
{                                                                                       

    Class<S> service;                                                                   
    ClassLoader loader;
    // 保存 "META-INF/services/" 里的文件(可以有多个)                                                                 
    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());                            
        } //while
        // 缓存,避免下一次的解析                                                                               
        nextName = pending.next();                                                      
        return true;                                                                    
    }                                                                                   

    private S nextService() {                                                           
        if (!hasNextService())                                                          
            throw new NoSuchElementException();                                         
        // 实现类名称
        String cn = nextName;                                                           
        nextName = null;                                                                
        Class<?> c = null;                                                              
        try {
            // false 表示不进行初始化操作,只进行链接                                                                           
            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();                                      
    }                                                                                   

}                                                                                       

使用:
这里写图片描述

ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = serviceLoader.iterator();                  
while (iterator.hasNext()) {                                           
    System.out.println(iterator.next());                               
}                                                                      

处理流程:

  • 根据给定的参数(接口或者抽象类)就能定位到该接口或者抽象类与实现类的映射配置文件的路径
  • 读取该配置文件
  • 创建该接口或者抽象类的实现类

可以看到 SPI 机制就可以保证我们在更换业务实现类的时候不再需要重新编译源代码。


抛砖引玉

在 Java 中根据一个子类获取其父类或接口信息非常方便,但是根据一个接口或者抽象类获取它的所有实现类却没那么容易。而 SPI 实现了这个诉求。

这在字节码层面就保证了。
这里写图片描述

java 层面本质就是读取方法区中的运行时常量池(Class常量池在加载阶段进入运行时常量池)。

import org.junit.Test;

class Father {

}

class Son extends Father {

}

public class T {
    @Test
    public void test() {
        Son son = new Son();
        System.out.println(son.getClass().getSuperclass());
    }
}

可以看到 SPI 与 IOC/DI 有异曲同工之妙。
原来觉得 SPI 与 IOC/DI 是对面向接口编程的更进一步,现在仔细想想:

  • 面向接口编程其实更多的是针对框架来说,也就是服务器端。
  • 而 SPI 与 IOC/DI 更多的是解耦客户层。
查看评论

浅谈spi机制

看到公司的项目代码中,在META-INF下service中定义了一些文件。文件名都是以全限定类名的方式命名的,而且没每个文件里的内容也是一堆全限定类名的值。搞不懂这些是什么用途,遂百度了一下  看...
  • yll_358918552
  • yll_358918552
  • 2015-09-10 11:31:10
  • 690

Java SPI机制

有这样的一个应用场景,在某个JAR包内,有一个接口IA,然后有3个IA接口的具体实现,分别是AIA,BIA,CIA。 那么如果在该JAR包内其他的类中使用接口IA的时候,硬编码IA对象对应的具体实现类...
  • DSLZTX
  • DSLZTX
  • 2015-07-30 10:19:52
  • 3469

(精)Java的SPI机制

SPI的全名为Service Provider Interface.普通开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。究其...
  • it_man
  • it_man
  • 2012-05-17 23:58:40
  • 4622

Java的SPI机制浅析与简单示例

一、SPI机制         这里先说下SPI的一个概念,SPI英文为Service Provider Interface单从字面可以理解为Service提供者接口,正如从SPI的名字去理解S...
  • zmx729618
  • zmx729618
  • 2016-11-22 14:33:38
  • 2624

Java的Spi机制心得

首先说一下问题。 昨日在看JDBC源码当看到DriverManage.getConnection()这个方法,点进去DriverManage类看到getConnection()方法里核心语句确实下面...
  • a1135721184
  • a1135721184
  • 2016-03-24 14:49:38
  • 5109

Java spi机制浅谈

最近看spring 3的官方参考文档的Spring 3 Type Conversion时看到了SPI ----------------------------------------------...
  • glory1234work2115
  • glory1234work2115
  • 2016-05-09 22:32:53
  • 247

Java和dubbo中的SPI机制学习

关于java的SPI机制,可以参考:https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html   为了实现在模块装配时的时候不在程...
  • clamaa
  • clamaa
  • 2017-04-11 10:03:08
  • 463

Dubbo中SPI扩展机制解析

dubbo的SPI机制类似与Java的SPI,Java的SPI会一次性的实例化所有扩展点的实现,有点显得浪费资源。 - dubbo的扩展机制可以方便的获取某一个想要的扩展实现,每个实现都有自己的na...
  • dachengxi
  • dachengxi
  • 2017-03-15 17:53:02
  • 565

SPI的使用场景

背景:面对分布式的开发,很多系统之间的调用都是使用rpc直接调用,但是有的时候上游的系统需要调用下游系统很多的接口,导致开发工作量很大。因此上游系统使用spi的方式在jar包中打一个spi接口,让下游...
  • u011955252
  • u011955252
  • 2017-11-05 22:35:33
  • 235

Dubbo源码分析 ---- 基于SPI的扩展实现机制

Dubbo源码分析–基于SPI的可扩展框架 dubbo是阿里巴巴开源出来的一套分布式服务框架,该框架可以比较方便的实现分布式服务的开发,调用等,该框架的教程地址为 http://dubbo.io/...
  • guhong5153
  • guhong5153
  • 2017-03-11 00:25:26
  • 1062
    个人资料
    专栏达人 持之以恒
    等级:
    访问量: 115万+
    积分: 1万+
    排名: 528
    博客专栏
    最新评论