java中spi有什么作用,Java SPI机制

  java中spi有什么作用,Java SPI机制

  00-1010什么是Java SPI Java SPI在JDBC的应用使用demoSPI在分片中的应用扩展-jdbc

  00-1010 SPI的全称是:服务提供商接口。java.util.ServiceLoader的文档中有详细的介绍,简单总结一下Java SPI机制的思路。我们系统中的抽象模块往往有许多不同的实现方案,如日志模块、xml解析模块、jdbc模块等。在面向对象设计中,我们一般推荐模块间基于接口的编程,模块间的实现类不是硬编码的。一旦代码中涉及到特定的实现类,就违背了可插拔性原则。如果需要替换一个实现,就需要修改代码。为了实现组装时不能在程序中动态指定模块,需要一种服务发现机制。

  Java SPI提供了这样一种机制:一种为接口寻找服务实现的机制。和IOC的思路差不多,就是把集会的控制权移出程序。这种机制在模块化设计中尤为重要。Java SPI的具体约定是,作为服务提供者,在提供一个服务接口的实现后,在jar包的META-INF/services/目录下创建一个以服务接口命名的文件。这个文件是实现服务接口的具体实现类。当外部程序组装这个模块时,可以通过jar包META-INF/services/中的配置文件找到具体的实现类名,加载实例化,完成模块注入。基于这样的约定,可以很好地找到服务接口的实现类,而不必在代码中公式化。Jdk为服务实现查找提供了一个工具类:java.util.ServiceLoader

  00-1010定义一个接口:

  包com . hiwei . SPI . demo;公共接口动物{ void speak();}创建两个实现类:

  包com . hiwei . SPI . demo;类cat实现animal { @ override public void speak(){ system . out . println(喵喵喵!);} }包com . hiwei . SPI . demo;公共狗实现动物{ @ override public void speak(){ system . out . println( Wang Wang Wang!);}}在资源目录中创建一个META-INF/services目录:

  创建一个以接口类路径命名的文件,并将实现类路径添加到该文件:

  com . hiwei . SPI . demo . cat com . hiwei . SPI . demo . dog

  使用

  包com . hiwei . SPI;导入com . hiwei . SPI . demo . animal;导入Java . SQL . SQL exception;导入Java . util . service loader;类public spidemo application { public static void main(string[]args){//会根据文件找到对应的实现类serviceloadranimal load=service loader . load(animal . class);//执行(animal animal 3360 load){ animal . speak()的实现类方法;}}}执行结果:

  上面我们可以看到,java spi会帮助我们找到接口实现类。那么在实际生产中如何使用呢?将上述代码键入jar,然后引入其他项目,在同一个目录下创建文件,并写出自己实现类的路径:

  该项目的实现类:

  包com . example . demo;导入com . hiwei . SPI . demo . animal;公共类Pig实现Animal { @Override public void sp

  eak() { System.out.println("哼哼哼!"); }}代码中,我们调用jar中的main方法:

  

package com.example.demo;import com.hiwei.spi.SpiDemoApplication;public class DemoApplication { public static void main(String[] args) { SpiDemoApplication.main(args); }}

执行结果:

 

  

 

  可以看见自定义的实现类也被执行了。在实际生产中,我们就可以使用java spi面向接口编程,实现可插拔。

  

 

  

SPI在JDBC中的应用

以最新的mysql-connector-java-8.0.27.jar为例

 

  

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version></dependency>

在使用JDBC连接数据库时,只需要使用:

 

  

DriverManager.getConnection("url", "username", "password");

DriverManager有静态方法:

 

  

 static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }

看下loadInitialDrivers()方法,其中有:

 

  

AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() {//获取Driver.class的实现类 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); /* Load these drivers, so that they can be instantiated. * It may be the case that the driver class may not be there * i.e. there may be a packaged driver with the service class * as implementation of java.sql.Driver but the actual class * may be missing. In that case a java.util.ServiceConfigurationError * will be thrown at runtime by the VM trying to locate * and load the service. * * Adding a try catch block to catch those runtime errors * if driver not available in classpath but its * packaged as service and that service is there in classpath. */ try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } });

可以看见,会根据java spi获取Driver.class的实现类,可以在mysql-connector-java-8.0.27.jar下面看到,定义的文件:

 

  

 

  程序会根据文件找到对应的实现类,并连接数据库。

  

 

  

SPI在sharding-jdbc中的应用

    sharding-jdbc是一款用于分库分表的中间件,在数据库分布式场景中,对于主键生成要保证唯一性,主键生成策略有很多种实现。sharding-jsbc在主键生成上就使用了SPI进行扩展。

 

  下面看下sharding-jdbc源码在主键生成上是怎么应用的: 源码中的 ShardingRule.class主要封装分库分表的策略规则,包括主键生成。看下createDefaultKeyGenerator方法:

  

//生成默认主键生成策略private ShardingKeyGenerator createDefaultKeyGenerator(final KeyGeneratorConfiguration keyGeneratorConfiguration) { //SPI服务发现 ShardingKeyGeneratorServiceLoader serviceLoader = new ShardingKeyGeneratorServiceLoader(); return containsKeyGeneratorConfiguration(keyGeneratorConfiguration) ? serviceLoader.newService(keyGeneratorConfiguration.getType(), keyGeneratorConfiguration.getProperties()) : serviceLoader.newService(); }

继续看ShardingKeyGeneratorServiceLoader(),有静态代码块注册:

 

  

 static { //SPI: 加载主键生成策略 NewInstanceServiceLoader.register(ShardingKeyGenerator.class); }

看下register方法:

 

  

 public static <T> void register(final Class<T> service) { //服务发现 for (T each : ServiceLoader.load(service)) { registerServiceClass(service, each); } }

看到这,真相大白,就是应用java spi机制。

 

  我们再看下resources目录下:

  

 

  可以看到有对应接口命名的文件,文件内容:

  

 

  有两个实现,分别是雪花算法和UUID,这也对应了sharding-jdbc的提供的两种生成策略。我们在使用sharding-jdbc时,也可以自定义策略,便于扩展。 sharding-jdbc对于SPI的使用点还有很多,这里就不一一列举了。对于SPI机制,我们在工作中也可以实际应用,提升程序的可扩展性。

  

 

  

扩展

以上是Java SPI的解析。其实SPI机制在很多地方都有用到,只是以不同的形式应用,具体的实现略有不同。例如dubbo中也有类似的spi机制;springboot的自动装配,也使用了spi机制:

 

  springboot自动装配:

  定义文件:

  

 

  文件中声明需要发现的类:

  

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.hiwei.valve.ValveAutoConfiguration

 

  

springboot的扫描文件,装配对应的类:

 

  

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}result = new HashMap<>();try {//加载文件中的类Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue());for (String factoryImplementationName : factoryImplementationNames) {result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());}}}// Replace all lists with unmodifiable lists containing unique elementsresult.replaceAll((factoryType, implementations) -> implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));cache.put(classLoader, result);}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}return result;}

FACTORIES_RESOURCE_LOCATION的值:

 

  

 

  SPI在Java开发中是个很重要的设计,所以我们一定要熟练掌握。

  到此这篇关于Java深入讲解SPI的使用的文章就介绍到这了,更多相关Java SPI内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: