Java SPI机制与延伸

1)使用场景

日常开发中,常根据接口调用实现类,但是如何确定某个具体的实现类包名,进而实例化实现类呢?

如果扫描所有的实现类class文件,然后去看其是否属于该接口,效率无疑很低。

所以便有了“基于约定”的方式,SPI 的出现便是基于约定配置实现 接口实现类 的解耦。

2)概述

SPI 全名 (Service Provider Interface)

服务提供者根据提供的接口完成一种实现后,在jar包的/META-INF/services下新建一个文件,文件名为接口的全限定名,内容为实现类的全限定名

接口提供者方可以使用serviceloader加载该实现类。实现了接口和实现类的解耦。

3)Mysql的jar包实现

Class.forName("xxx")

早期使用mysql的jar包时,总是需要先手动Class.forName来加载mysql驱动。
现在可以直接使用 DriverManager.getConnection来获取连接。

/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
   loadInitialDrivers();
   println("JDBC DriverManager initialized");
}

如同,该静态代码块存在于DriverManager中,当调用DriverManage.getConnection时,由于类的加载机制会先调用该类的父类和本身的静态变量、静态代码段等等。在该代码段里,DriverManager完成了驱动的加载。(loadInitialDrivers

private static void loadInitialDrivers() {
		String drivers;
		try {
		  drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
		      public String run() {
		          return System.getProperty("jdbc.drivers");
		      }
		  });
		} catch (Exception ex) {
		   drivers = null;
		}
	
	AccessController.doPrivileged(new PrivilegedAction<Void>() {
	   public Void run() {
	//使用serviceloader来加载驱动
	       ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
	       Iterator<Driver> driversIterator = loadedDrivers.iterator();
	       
	       try{
	           while(driversIterator.hasNext()) {
	               driversIterator.next();
	           }
	       } catch(Throwable t) {
	       // Do nothing
	       }
	       return null;
	   }
	});
	
	println("DriverManager.initialize: jdbc.drivers = " + drivers);
	
	if (drivers == null || drivers.equals("")) {
	   return;
	}
	//如果设置了驱动的全限定类名,则完成手动加载
	String[] driversList = drivers.split(":");
	println("number of Drivers:" + driversList.length);
	for (String aDriver : driversList) {
	   try {
	       println("DriverManager.Initialize: loading " + aDriver);
	       Class.forName(aDriver, true,
	               ClassLoader.getSystemClassLoader());
	   } catch (Exception ex) {
	       println("DriverManager.Initialize: load failed: " + ex);
	   }
	}
}

分析图中代码:

  • System.getProperty(“jdbc.drivers”)查找有jvm变量是否存在全限定类名。
    因为可能手动System.setProperty来指明了驱动的全限定类名。
    如果存在这种情况,则手动加载驱动 Class.forName(aDriver, true, ClassLoader.getSystemClassLoader())

  • 主要使用了Serviceloader来加载Driver类的实现类,即mysql驱动。
    ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);

所以在DriverManager里面最终是调用的ServiceLoader来加载驱动。

4)ServiceLoader内部实现

点进ServiceLoader来观察如何加载的驱动

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

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();
         }

     };
 }

ServiceLoader实现了iterator接口,查看其重写的iterator()方法

  • 主要是调用了LazyIterator ,而该类是ServiceLoader的一个内部类。
  • 除了完成加载功能外,还有着懒加载的特性,即调用hasNext时才开始加载驱动。

继续观察LazyIterator的next和hasNext方法的实现内容。

//定义文件扫描的约定路径
private static final String PREFIX = "META-INF/services/";
	//....
	
	private class LazyIterator implements Iterator<S> {
	 
	Class<S> service;
	ClassLoader loader;
	Enumeration<URL> configs = null;
	Iterator<String> pending = null;
	String nextName = null;
	 
	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);
	    }
	}
	
	private boolean hasNextService() {
	    if (nextName != null) {
	        return true;
	    }
	    if (configs == null) {
	        try {
	        // 加载 "META-INF/services/"下的文件
	            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;
	}
	}

hasNextService中:

  • String fullName = PREFIX + service.getName();
    ClassLoader.getSystemResources(fullName);

加载 META-INF/services/ 下的文件获取 实现类的全限定类名

	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);
	    }
	}
	//...
	//...
	//...
	 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
	 }

在next方法中 使用Class.forName(cn, false, loader);最终完成了实现类的加载(cn即是实现类的全限定类名)

5)Spring SPI——SpringBoot自动装载

在SpringBoot中,可以在项目的 META/spring.factories下指定 装载类的全限定类名

Java spi一样,SpringBoot 的自动装载也使用了这种约定的设计理念。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

如下图SpringBoot 通过SpringFactoriesLoader 来加载第三方(各种Starter)的类。

通过这种方式可以将第三方的类注册到spring容器中

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

    private SpringFactoriesLoader() {
    }

    public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryType, "'factoryType' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
        }

        List<T> result = new ArrayList(factoryImplementationNames.size());
        Iterator var5 = factoryImplementationNames.iterator();

        while(var5.hasNext()) {
            String factoryImplementationName = (String)var5.next();
            result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
        }

        AnnotationAwareOrderComparator.sort(result);
        return result;
     }
    //...
    //...
    //...   
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值