最近在一个日志标准化的项目中,使用了责任链模式来链接每一个具体的处理Handler.但是在实例化时,需要每一个都去创建实例。

如:

/**
* 初始化具体的处理类
*/
private void initConcreteHandler() {

handlers.add(new BasicParamHandler());
handlers.add(new CommonParamHandler());
handlers.add(new TestParamHandler());
……
}


这种,涉及了具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。老大看到我的代码,直接让用Java spi机制去做。

1. SPI机制简介

Service Provider Interface:服务提供者接口.例如,系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。

面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。在模块化设计中这个机制尤其重要。

java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。

2. 实现案例

1.common-logging

apache最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现, 发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory配置文件,通过读取该文件的内容找到日志提工商实现类。只要我们的日志实现里包含了这个文件,并在文件里制定 LogFactory工厂接口的实现类即可。

2. JDBC

jdbc4.0以前, 开发人员还需要基于Class.forName("xxx")的方式来装载驱动。

创建连接:

DriverManage.getConnection()中,有Connection con = aDriver.driver.connect(url, info);


driver成员变量,是java.sql.Driver接口,Java对外公开的一个加载驱动接口,Java并未实现,至于实现这个接口由各个Jdbc厂商去实现。

如MySQL,mysql-connector-java-5.1.38.jar包下面META-INF.services包下有个java.sql.Driver文件打开文件有下面两行

com.mysql.jdbc.Driver

com.mysql.fabric.jdbc.FabricMySQLDriver

3. 具体实现

(1)如,我们这里的处理模块。面向接口编程。这是我的一种实现方式。

public interface LogProcessHandler {

/**
* 解析日志,具体的给Bean赋值的逻辑
* @param context 上下文
*/
void process(ProcessorContext context);

/**
* 日志解析handler拓扑顺序,从小到大排列

*/
int order();
}


(2)每一个具体实现类,如BasicParamHandler、CommonParamHandler都实现了这个接口,里面具体的process()方法。

(3)在Maven工程的src/main/resources/下,创建META-INF/services/com.A.standard.chain.LogProcessHandler文件(UTF-8)

里面具体内容:

com.A.tools.BasicParamProcessHandler
com.A.tools.CommonParamProcessHandler
com.A.tools.TestParamProcessHandler


(4)主类中初始化

 /**
* 初始化具体的处理类
*/
private void initConcreteHandler() {
// 初始化
handlers = new ArrayList<>();
// 通过java spi机制load所有处理的hander
ServiceLoader<LogProcessHandler> loader = ServiceLoader.load(LogProcessHandler.class);
for (LogProcessHandler hander : loader ) {
handlers.add(hander);
}
// handler 排序
Collections.sort(handlers, new Comparator<LogProcessHandler>() {
@Override
public int compare(LogProcessHandler o1, LogProcessHandler o2) {
return o1.order() - o2.order();
}
});
}


Ps:

一般情况下,是按照文件中实现类顺序加载类,但是可能出现特殊情况。因此对集合中的实现类进行排序,这也是接口中定义了order方法的原因。

4. 原理

当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。

当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。