Mecanismo de diseño de componentes enchufables—SPI

Autor: Jingdong Logística Kong Xiangdong

1. ¿Qué es SPI?

El nombre completo de SPI es Interfaz de proveedor de servicios, que proporciona una interfaz de servicio; es un mecanismo de descubrimiento de servicios. La esencia de SPI es configurar el nombre completo de la clase de implementación de la interfaz en el archivo, y el cargador de servicios lee la configuración. y carga la clase de implementación. . De esta forma, la clase de implementación se puede reemplazar dinámicamente por la interfaz en tiempo de ejecución. Debido a esta característica, podemos proporcionar fácilmente funciones extendidas para nuestros programas a través del mecanismo SPI.

Como se muestra abajo:

Cada abstracción del diseño del sistema a menudo tiene muchos esquemas de implementación diferentes. En el diseño orientado a objetos, generalmente se recomienda programar entre módulos en función de las interfaces, y la implementación de los módulos no está codificada. Una vez que el código involucra clases de implementación específicas, viola el enchufable El principio de sacar. Java SPI proporciona un mecanismo de este tipo para encontrar la implementación de un servicio para una determinada interfaz, que es algo similar a la idea de IOC, que mueve el control del ensamblaje fuera del programa, lo cual es especialmente importante en la modularización. En lugar de decir que SPI es un mecanismo de descubrimiento de servicios proporcionado por Java, es mejor decir que es una idea de desacoplamiento.

2. ¿Escenario de uso?

  • La interfaz de carga del controlador de la base de datos realiza la carga de clases, tales como: JDBC carga Mysql, Oracle...
  • La interfaz de fachada de registro implementa la carga de clases, como: SLF4J admite log4j y logback
  • SPI es muy utilizado en Spring, especialmente la implementación de configuración automática en spring-boot
  • Dubbo también usa mucho SPI para implementar la extensión del marco, encapsula el SPI original y permite a los usuarios extender e implementar la interfaz de filtro.

3. Introducción al uso

Para usar Java SPI, se deben seguir las siguientes convenciones:

  • Cuando el proveedor de servicios proporciona una implementación específica de la interfaz, es necesario crear un archivo llamado "nombre completo de la interfaz" en el directorio META-INF/services del paquete JAR, y el contenido es el nombre completo de la implementación. clase;
  • El JAR donde se encuentra la clase de implementación de la interfaz se coloca bajo el classpath del programa principal, es decir, se introduce la dependencia.
  • El programa principal carga dinámicamente el módulo de implementación a través de java.util.ServiceLoder. Encontrará el nombre completo de la clase de implementación escaneando los archivos en el directorio META-INF/services, cargará la clase en la JVM y la instanciará;
  • La clase de implementación del SPI debe llevar un constructor sin parámetros.

Ejemplo:

definición del módulo de interfaz spi

定义一组接口:public interface MyDriver 

controlador spi-jd

spi-ali-conductor

实现为:public class JdDriver implements MyDriver
  public class AliDriver implements MyDriver 

Cree un directorio /META-INF/services en src/main/resources/ y agregue un archivo con el nombre de la interfaz (archivo org.MyDriver)

El contenido es la clase de implementación que se aplicará respectivamente com.jd.JdDriver y com.ali.AliDriver

spi-núcleo

Generalmente, es el paquete central proporcionado por la plataforma, incluida la estrategia de carga y uso de clases de implementación, etc. Aquí simplemente implementamos la lógica: a. No se encuentra una implementación específica y se lanza una excepción b. Si se encuentran múltiples implementaciones , imprímelos por separado

public void invoker(){
    ServiceLoader<MyDriver>  serviceLoader = ServiceLoader.load(MyDriver.class);
    Iterator<MyDriver> drivers = serviceLoader.iterator();
    boolean isNotFound = true;
    while (drivers.hasNext()){
        isNotFound = false;
        drivers.next().load();
    }
    if(isNotFound){
        throw new RuntimeException("一个驱动实现类都不存在");
    }
}

prueba de spi

public class App 
{
    public static void main( String[] args )
    {
        DriverFactory factory = new DriverFactory();
        factory.invoker();
    }
}

1. Introducir el paquete spi-core y ejecutar el resultado

2. Introducir paquetes spi-core, spi-jd-driver

3. Spi-core entrante, spi-jd-driver, spi-ali-driver

4. Análisis de principios

¿Ves cómo obtuvimos la clase de implementación específica en este momento?

Sólo dos líneas de código:

ServiceLoader<MyDriver>  serviceLoader = ServiceLoader.load(MyDriver.class);
Iterator<MyDriver> drivers = serviceLoader.iterator();

Entonces, primero miramos la clase ServiceLoader:

public final class ServiceLoader<S> implements Iterable<S>{
//配置文件的路径
 private static final String PREFIX = "META-INF/services/";
    // 代表被加载的类或者接口
    private final Class<S> service;
    // 用于定位,加载和实例化providers的类加载器
    private final ClassLoader loader;
    // 创建ServiceLoader时采用的访问控制上下文
    private final AccessControlContext acc;
    // 缓存providers,按实例化的顺序排列
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 懒查找迭代器,真正加载服务的类
    private LazyIterator lookupIterator;
  
 //服务提供者查找的迭代器
    private class LazyIterator
        implements Iterator<S>
    {
 .....
private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
//全限定名:com.xxxx.xxx
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                }
            }
            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);
            }
            if (!service.isAssignableFrom(c)) {
                fail(service, "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            }
        }
........

El proceso aproximado es el siguiente:

  • La aplicación llama al método ServiceLoader.load

  • Cuando la aplicación obtiene una instancia de objeto a través de un iterador, primero determinará si hay un objeto de ejemplo almacenado en caché en el objeto de proveedores y lo devolverá directamente si existe.

  • Si no existe, realice la reimpresión de clases y lea el archivo de configuración en META-INF/services para obtener los nombres de todas las clases que se pueden instanciar, y el archivo de configuración se puede obtener a través de JAR Cargue el objeto a través del método de reflexión Clase .forName() y use Instance() El método de clase instanciada almacena en caché la clase instanciada en el objeto del proveedor y la devuelve sincrónicamente.

5. Resumen

Ventajas: desacoplamiento

El uso de SPI separa la lógica de control de ensamblado del módulo de servicio de terceros del código comercial de la persona que llama y no se acoplará. La aplicación puede habilitar extensiones de marco y reemplazar componentes de marco de acuerdo con las condiciones comerciales reales.

El uso de SPI hace innecesario obtener la clase de implementación de las siguientes maneras

  • Importación de importación codificada de código

  • Especifique el nombre completo de la clase para la adquisición de reflexión, como antes de JDBC4.0; Class.forName("com.mysql.jdbc.Driver")

defecto:

Aunque ServiceLoader se puede considerar como carga diferida, solo se puede obtener a través del recorrido, es decir, todas las clases de implementación de la interfaz se cargan y se instancian una vez. Si no desea utilizar alguna clase de implementación, también se carga y se crea una instancia, lo que genera desperdicio. La forma de obtener una determinada clase de implementación no es lo suficientemente flexible, solo se puede obtener en forma de iterador y la clase de implementación correspondiente no se puede obtener de acuerdo con un determinado parámetro.

6. Contraste

{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/4090830/blog/8564660
Recomendado
Clasificación