服务定位模式(Service Locator Pattern)。
1. 前言
Java开源社区的火爆让诸如资源读取这样的普遍需求,在每个比较流行的组件中都有一套自己的实现方式,就笔者看过相关的源码的就有Spring,Struts2,Apache Camel等等。
选择太多了等于没得选,因此笔者一直想要整一个独立的,但功能强大,且占用小的资源读取,兼顾扩展的实现方式,恰巧最近在公司内部推进liquibase时,为了减少推进阻力遂对liquibase进行了一定程度的解读,在这个过程中发现其ServiceLocator的设计和实现堪堪好能够满足笔者上面的这些期待。
虽然最终不一定就选择了其作为笔者所构思的技术解决方案的基座,但多研究一些为之后的解决方案搭建提供参考也是相当有裨益的。
2. 简介
关于ServiceLocator,其本身是一种软件开发中的设计模式,通过应用强大的抽象层,可对涉及尝试获取一个服务的过程进行封装。关于这种模式的更多细节读者可以参考网络上更为详尽的资料。
本文要研究的liquibase中的ServiceLocator,其作用可以直接参考ServiceLocator 类上的注释:
- Entry point to the Liquibase specific ServiceLocator framework.(是liquibase指定的ServiceLocator实现的入口)
- Services (concrete instances of interfaces) are located by scanning nominated packages on the classpath for implementations of the interface.(通过扫描位于classpath下指定的pakcage来获取特定接口的实现类,进而达到扩展功能的实现,以及对外提供服务)
3. 使用方法
ServiceLocator使用很简单,其以采用单例模式对外暴露的方法并不多,以下是从liquibase源码抽取的一段使用范例:
public class CommandFactory {
private static CommandFactory instance;
private List<LiquibaseCommand> commands;
private CommandFactory() {
Class<? extends LiquibaseCommand>[] classes;
commands = new ArrayList<>();
try {
classes = ServiceLocator.getInstance().findClasses(LiquibaseCommand.class);
for (Class<? extends LiquibaseCommand> clazz : classes) {
register(clazz.getConstructor().newInstance());
}
} catch (Exception e) {
throw new UnexpectedLiquibaseException(e);
}
}
......
}
liquibase内部大量采用了这类 ServiceLocator + Factory模式的方式来实现服务/接口的实例和使用分离,同时对外提供扩展机制。
3. 源码解析
因为ServiceLocator 类本身的代码量并不多(当然也是因为其将逻辑下放给了专门的实现类),因此本文采取一种穷举式的分析方法。
3.1 字段
首先让我们来看看ServiceLocator 类中的字段。
private ResourceAccessor resourceAccessor;
private Map<Class, List<Class>> classesBySuperclass;
private List<String> packagesToScan;
private PackageScanClassResolver classResolver;
以上分析可以得出:
- 有两个关键字段
resourceAccessor ,classResolver 。前者负责抽象资源加载,后者负责封装扫描指定package,获取相关服务的实现类的逻辑。
3.2 静态构造函数
对于常年从事业务代码CRUD开发的同学来说,可能静态构造函数都已经很陌生了。
ServiceLocator 类中的静态构造函数完成了一件很重要的事情:自身的实例化,实现了单体模式。(当然这个里面还有一些诸如OSGI的判断逻辑等)
既然静态构造函数中完成了自身实例化过程,那自身的构造函数势必需要探查下。
ServiceLocator 存在多个构造函数,但无一例外都是完成了两件事:
- 关键字段
classResolver 的赋值。默认值为DefaultPackageScanClassResolver 实例。 - 关键字段
resourceAccessor 的赋值。默认值为ClassLoaderResourceAccessor 实例。
这里需要专门介绍是的对于字段resourceAccessor 的赋值 —— setResourceAccessor(ResourceAccessor resourceAccessor) 方法的调用:
setResourceAccessor 在每个构造函数中都会被调用,确保其被调用。- liquibase在该方法中完成了对于字段
packagesToScan 的赋值
public void setResourceAccessor(ResourceAccessor resourceAccessor) {
this.resourceAccessor = resourceAccessor;
this.classesBySuperclass = new HashMap<>();
this.classResolver.setClassLoaders(new HashSet<>(Arrays.asList(new ClassLoader[]{resourceAccessor.toClassLoader()})));
if (packagesToScan == null) {
packagesToScan = new ArrayList<>();
String packagesToScanSystemProp = System.getProperty("liquibase.scan.packages");
if ((packagesToScanSystemProp != null) &&
((packagesToScanSystemProp = StringUtils.trimToNull(packagesToScanSystemProp)) != null)) {
for (String value : packagesToScanSystemProp.split(",")) {
addPackageToScan(value);
}
} else {
Set<InputStream> manifests;
try {
manifests = resourceAccessor.getResourcesAsStream("META-INF/MANIFEST.MF");
if (manifests != null) {
for (InputStream is : manifests) {
Manifest manifest = new Manifest(is);
String attributes = StringUtils.trimToNull(manifest.getMainAttributes().getValue("Liquibase-Package"));
if (attributes != null) {
for (Object value : attributes.split(",")) {
addPackageToScan(value.toString());
}
}
is.close();
}
}
} catch (IOException e) {
throw new UnexpectedLiquibaseException(e);
}
if (packagesToScan.isEmpty()) {
.....
}
}
}
}
3.3 方法
在方法这个小节,我们主要介绍下findClassesImpl(Class requiredInterface) ,正是在该方法中完成了对于接口/服务实现类的扫描和加载,算是ServiceLocator功能的灵魂。
private List<Class> findClassesImpl(Class requiredInterface) throws Exception {
List<Class> classes = new ArrayList<>();
classResolver.addClassLoader(resourceAccessor.toClassLoader());
for (Class<?> clazz : classResolver.findImplementations(requiredInterface, packagesToScan.toArray(new String[packagesToScan.size()]))) {
if ((clazz.getAnnotation(LiquibaseService.class) != null) && clazz.getAnnotation(LiquibaseService.class)
.skip()) {
continue;
}
if (!Modifier.isAbstract(clazz.getModifiers()) && !Modifier.isInterface(clazz.getModifiers()) && !clazz.isAnonymousClass() &&!clazz.isSynthetic() && Modifier.isPublic(clazz.getModifiers())) {
try {
clazz.getConstructor();
LogService.getLog(getClass()).debug(LogType.LOG, clazz.getName() + " matches "+requiredInterface.getName());
classes.add(clazz);
} catch (NoSuchMethodException e) {
......
}
}
}
return classes;
}
4. 扩展知识 - 关于Service Locator 『服务定位模式』
服务定位模式(Service Locator Pattern)是一种软件开发中的设计模式,通过应用强大的抽象层,可对涉及尝试获取一个服务的过程进行封装。该模式使用一个称为"Service Locator"的中心注册表来处理请求并返回处理特定任务所需的必要信息。
相关参考链接: Service Locator 模式
5. Links
- Spring ServiceLocator 介绍及应用
|