架构设计-java spi实现模块间的松耦合
一。前言
在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改源代码,然后重新编译、发布。
为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似spring IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
很多开源项目都使用这种机制。
java spi的具体约定为:
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
java.sql.Driver就是这么实现的,我们可以看看mysql是如何扩展它的。
打开mysql的jdbc jar包,在META-INF/services下有一个java.sql.Drvier的文件,
文件内容如下:
二。实例开发
在代码中,如何使用java的spi机制,这时就要借助java.util.ServiceLoader了。
开发步骤如下:
1、编写一个接口
2、编写接口的实现类
3、在/src/META-INF/services下新建一个文件,文件名为接口的包名+接口名,如果是maven构建,就在src/main/resources/META-INF/services
4、把实现类的全名写在该文件中,一个实现类占一行。
package com.wxj.spi; /** * 保存接口 * @author wxj * */ public interface Store { /** * 保存数据 * @param data 数据 * @return 是否保存成功 */ public boolean store(Object data); }
package com.wxj.spi; /** * 用文件保存数据 * @author wxj * */ public class FileStore implements Store { @Override public boolean store(Object data) { System.out.println("file store successfully"); return true; } }
package com.wxj.spi; /** * 用数据库保存数据 * @author wxj * */ public class DBStore implements Store { @Override public boolean store(Object data) { System.out.println("db store successfully"); return true; } }
src/META-INF/services/com.wxj.spi.Store的文件内容:
com.wxj.spi.FileStore com.wxj.spi.DBStore
测试类
package com.wxj.spi; import java.util.Iterator; import java.util.ServiceLoader; /** * 测试类 * @author wxj * */ public class TestStore { public static void main(String[] args) { ServiceLoader<Store> stores = ServiceLoader.load(Store.class); Iterator<Store> it = stores.iterator(); Store s = null; String data = "datas"; while(it.hasNext()) { s = it.next(); s.store(data); } } }