组件化框架设计之Java SPI机制(三)
11/12号文档资料已全面更新!;《【阿里P7】移动互联网架构师高级教程+BAT面试题》,点击下方链接前往领取:
【阿里P7】移动互联网架构师进阶高级教程+BAT面试题
本篇文章将从深入理解java SPI机制来介绍组件化框架设计:
一、SPI机制定义
SPI机制(Service Provider Interface)其实源自服务提供者框架(Service Provider Framework,参考【EffectiveJava】page6),是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了spi接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。
二、典型实例:jdbc的设计
通常各大厂商(如Mysql、Oracle)会根据一个统一的规范(java.sql.Driver)开发各自的驱动实现逻辑。客户端使用jdbc时不需要去改变代码,直接引入不同的spi接口服务即可。
Mysql的则是com.mysql.jdbc.Drive,Oracle则是oracle.jdbc.driver.OracleDriver。
伪代码如下:
//注:从jdbc4.0之后无需这个操作,spi机制会自动找到相关的驱动实现 //Class.forName(driver); //1.getConnection()方法,连接MySQL数据库。有可能注册了多个Driver,这里通过遍历成功连接后返回。 con = DriverManager.getConnection(mysqlUrl,user,password); //2.创建statement类对象,用来执行SQL语句!! Statement statement = con.createStatement(); //3.ResultSet类,用来存放获取的结果集!! ResultSet rs = statement.executeQuery(sql);
jdbc连接源码分析
- java.sql.DriverManager静态块初始执行,其中使用spi机制加载jdbc具体实现
//java.sql.DriverManager.java //当调用DriverManager.getConnection(..)时,static会在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"); }
2.loadInitialDrivers()中完成了引入的数据库驱动的查找以及载入,本示例只引入了oracle厂商的mysql,我们具体看看。
//java.util.serviceLoader.java
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; } //如果spi 存在将使用spi方式完成提供的Driver的加载 // If the driver is packaged as a Service Provider, load it. // Get all the drivers through the classloader // exposed as a java.sql.Driver.class service. // ServiceLoader.load() replaces the sun.misc.Providers() AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { //查找具体的provider,就是在META-INF/services/***.Driver文件中查找具体的实现。 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 it's * packaged as service and that service is there in classpath. */ //查找具体的实现类的全限定名称 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(":"); .... } }
3.java.util.ServiceLoader 加载spi实现类.
上一步的核心代码如下,我们接着分析:
//java.util.serviceLoader.java ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ //查找具体的实现类的全限定名称 while(driversIterator.hasNext()) { //加载并初始化实现 driversIterator.next(); } } catch(Throwable t) { // Do nothing }
主要是通过ServiceLoader来完成的,我们按照执行顺序来看看ServiceLoader实现:
//初始化一个ServiceLoader,load参数分别是需要加载的接口class对象,当前类加载器 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); }
遍历所有存在的service实现
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 static final String PREFIX = "META-INF/services/"; private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); //通过相对路径读取classpath中META-INF目录的文件,也就是读取服务提供者的实现类全限定名 if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } //判断是否读取到实现类全限定名,比如mysql的“com.mysql.jdbc.Driver ” while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next();//nextName保存,后续初始化实现类使用 return true;//查到了 返回true,接着调用next() }
public S next() { if (acc == null) {//用来判断serviceLoader对象是否完成初始化 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 { //加载字节码返回class对象.但并不去初始化(换句话就是说不去执行这个类中的static块与static变量初始化) // 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 { //初始化这个实现类.将会通过static块的方式触发实现类注册到DriverManager(其中组合了一个CopyOnWriteArrayList的registeredDrivers成员变量)中 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 }
上一步中,Sp = service.cast(c.newInstance()) 将会导致具体实现者的初始化,比如mysqlJDBC,会触发如下代码:
//com.mysql.jdbc.Driver.java ...... private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>(); ...... static { try { //并发安全的想一个copyOnWriteList中方 java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
4.最终Driver全部注册并初始化完毕,开始执行DriverManager.getConnection(url, “root”, “root”)方法并返回。
使用实例
四个项目:spiInterface、spiA、spiB、spiDemo
spiInterface中定义了一个com.zs.IOperation接口。
spiA、spiB均是这个接口的实现类,服务提供者。
spiDemo作为客户端,引入spiA或者spiB依赖,面向接口编程,通过spi的方式获取具体实现者并执行接口方法。
├─spiA │ └─src │ ├─main │ │ ├─java │ │ │ └─com │ │ │ └─zs │ │ ├─resources │ │ │ └─META-INF │ │ │ └─services │ │ └─webapp │ │ └─WEB-INF │ └─test │ └─java ├─spiB │ └─src │ ├─main │ │ ├─java │ │ │ └─com │ │ │ └─zs │ │ ├─resources │ │ │ └─META-INF │ │ │ └─services │ │ └─webapp │ │ └─WEB-INF │ └─test │ └─java ├─spiDemo │ └─src │ ├─main │ │ ├─java │ │ │ └─com │ │ │ └─zs │ │ ├─resources │ │ └─webapp │ │ └─WEB-INF │ └─test │ └─java └─spiInterface └─src ├─main │ ├─java │ │ └─com │ │ └─zs │ ├─resources │ └─webapp │ └─WEB-INF └─test └─java └─spiInterface
spiDemo
package com.zs; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Iterator; import java.util.ServiceLoader; public class Launcher { public static void main(String[] args) throws Exception { // jdbcTest(); showSpiPlugins(); } private static void jdbcTest() throws SQLException { String url = "jdbc:mysql://localhost:3306/test"; Connection conn = DriverManager.getConnection(url, "root", "root"); Statement statement = conn.createStatement(); ResultSet set = statement.executeQuery("select * from test.user"); while (set.next()) { System.out.println(set.getLong("id")); System.out.println(set.getString("userName")); System.out.println(set.getInt("age")); } } private static void showSpiPlugins() { ServiceLoader<IOperation> operations = ServiceLoader.load(IOperation.class); Iterator<IOperation> operationIterator = operations.iterator(); while (operationIterator.hasNext()) { IOperation operation = operationIterator.next(); System.out.println(operation.operation(6, 3)); } } }
SPI示例 完整代码。
原文链接https://blog.csdn.net/lemon89/article/details/79189475
最后
我们今年整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。
主要包括腾讯,以及字节跳动,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。
以下为我们整理的资料免费分享;【阿里P7】Android高级教程+BAT面试题
1.Android高级技术脑图
2.P7级Android高级架构视频教程
3.Android核心高级技术PDF文档+BAT大厂面试真题解析
4.Android思维脑图(技能树)
1.Android高级技术脑图;
查漏补缺,体系化深入学习提升
2.【Android高级架构视频教程】;
全套部分展示;
java与Android内核进阶专题视频与源码
阿里P7级全套高级学习视频;
3.Android核心高级技术PDF文档,BAT大厂面试真题解析
4.Android思维脑图(技能树)
免费分享
为什么免费分享?
我的目的是让更多需要的Android开发朋友能够提升自己的技术水平
无论是Android,还是qq,微信,360等,想在互联网上最大程度推广,就必须免费!
如果我的学习资料对你有帮助,点个赞,谢谢!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
组件化框架设计之apt编译时期自动生成代码&动态类加载(二)
11/12号文档资料已全面更新!;《【阿里P7】移动互联网架构师高级教程+BAT面试题》,点击下方链接前往领取:【阿里P7】移动互联网架构师进阶高级教程+BAT面试题本篇文章将继续从以下两个内容来介绍组件化框架设计: apt编译时期自动生成代码 Android动态加载技术基础之类加载(ClassLoader) 一、apt编译时期自动生成代码 第一步新建一个android项目。第二步新建立一个java的Module。注意是javalib。这个lib用来专门写注解就好。 这个lib里面就先放一个注解,叫TestAnno。 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface...
- 下一篇
组件化框架设计之手写组件化架构(五)
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680本篇文章将通过手写组件化架构;路由框架原理与实现来阐述组件化框架设计 此次手写架构,解决的问题是: 1、让 App内 各个功能模块能够独立开发单元测试,也可以 所有模块集成打包,统一测试 独立开发更改gradle.properties的配置,使得每个功能模块都成为application, 可以独立打包成apk,单独运行。单个模块,独立测试。 集成打包更改gradle.properties的配置,使得原先每个单独模块,都变成library,被 主模块引用,这时候只有主模块能够打包apk,所有功能都集成在这个apk内。 2、实现 功能模块的整体移植,灵活拔插 故事背景当你们公司有多个安卓开发人员,开发出核心业务相同
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,8上快速安装Gitea,搭建Git服务器