1. 理论参考
2. 案例提供一个简单的日志框架myslf4j
- ①定义核心接口
public interface MySlf4j {
void log(String msg);
}
- ②定义一个默认实现(有些框架不一定给默认实现,如果用户也引入厂商的实现就报错,这里还是给个默认实现比较好)
public class MySlf4jDefaultImpl implements MySlf4j {
@Override
public void log(String msg) {
System.out.println("MySlf4j的默认实现MySlf4jDefaultImpl:"+msg);
}
}
- ③resources/META-INF/services/下载配置这个实现,ServiceLoader#load()方法就会加载里面的实现。如上图,文件名为接口名 ,内容为实现类全路径,加载时会通过放射实例化
- ④再写一个类来获取MySlf4j的实现进行,以便使用MySlf4j的现在
public class LogFactory {
public static MySlf4j getFirstLogger(){
List<MySlf4j> allImpls = getProviders();// 如果引入2个厂商实现,且在maven中优先引入B实现,则这里加载到的是B的实现(maven就近原则也许就是这么玩的)
return allImpls.get(0);
};
public static MySlf4j getLastLogger(){
List<MySlf4j> allImpls = getProviders();
return allImpls.get(allImpls.size()-1);//要最后一个加载的
}
public static List<MySlf4j> getAllLogger(){ //默认及所有厂商实现都要
return getProviders();
}
private static List<MySlf4j> getProviders() {
ServiceLoader<MySlf4j> providers = ServiceLoader.load(MySlf4j.class);
Iterator<MySlf4j> it = providers.iterator();
List<MySlf4j> allImpls = new ArrayList<>();
while (it.hasNext()) {
allImpls.add(it.next());
}
return allImpls;
}
;
}
3.测试框架
就改项目mvn install到本地maven仓库,再在业务项目引入my-slf4j进行日志打印
<dependency>
<groupId>com.xxx</groupId>
<artifactId>my-slf4j</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
测试my-slf4j
4.厂商jwolf实现
- ①厂商实现需要先引入my-slf4j定义的接口
<dependency>
<groupId>com.xxx</groupId>
<artifactId>my-slf4j</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- ②自己实现MySlf4j接口
public class JwolfMySlf4j implements MySlf4j {
@Override
public void log(String msg) {
System.out.println("Jwolf厂商实现JwolfMySlf4j:"+msg);
}
}
- ③注册实现,注意文件名仍然为com.xx.MySlf4j 实现写Jwolf自己的实现的类全路径
5.业务项目同时引入my-slf4j,及其厂商实现jwolfmyslf4j,且my-slf4j先引入,则优先加载的是my-slf4j
6.交换my-slf4j,及其厂商实现jwolfmyslf4j引入顺序,providers加载顺序也变了
总结:
- 本案例利用spi机制实现了类似原生slf4j的日志框架,my-slf4j具有可扩展性,具体厂商实现具有可插拔性
- 多个实现时的加载顺序有点类似maven的就近原则,最短路径原则
- 使用该机制可实现市面上有些框架类似的plugin就可以利用spi实现,插件制造者实现统一的接口,并将包放入plugin下吗,serviceload就可加载。只是这些plugin在项目外,需要配置外置jar包加载路径