IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【源码解读】liquibase之ServiceLocator -> 正文阅读

[Java知识库]【源码解读】liquibase之ServiceLocator

服务定位模式(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 {
        	// 抽取接口实现类,进行实例化注册到内存中
        	// 以工厂模式对外提供相应实例。
        	// liquibase大量采用了这类 ServiceLocator + Factory模式的方式。
            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类中的字段。

// 关键字段一, 用作资源读取. 
//	观察该字段的类型名称,以及其继承链可猜测, liquibase中的资源读取逻辑基本就是该接口的各类实现了.
private ResourceAccessor resourceAccessor;
// 换存池, 用于缓存加载过的实现类
private Map<Class, List<Class>> classesBySuperclass;
// 决定了ServiceLocator进行扫描实现类时候的package路径
private List<String> packagesToScan;
// 关键字段二 从指定的package检索出class
//	扫描指定package, 获取相应的实现类
//	配合PackageScanFilter接口扩展, 实现更为精细的检索
private PackageScanClassResolver classResolver;

以上分析可以得出:

  1. 有两个关键字段resourceAccessorclassResolver。前者负责抽象资源加载,后者负责封装扫描指定package,获取相关服务的实现类的逻辑。

3.2 静态构造函数

对于常年从事业务代码CRUD开发的同学来说,可能静态构造函数都已经很陌生了。

ServiceLocator类中的静态构造函数完成了一件很重要的事情:自身的实例化,实现了单体模式。(当然这个里面还有一些诸如OSGI的判断逻辑等)

既然静态构造函数中完成了自身实例化过程,那自身的构造函数势必需要探查下。

ServiceLocator存在多个构造函数,但无一例外都是完成了两件事:

  1. 关键字段classResolver的赋值。默认值为DefaultPackageScanClassResolver实例。
  2. 关键字段resourceAccessor的赋值。默认值为ClassLoaderResourceAccessor实例。

这里需要专门介绍是的对于字段resourceAccessor的赋值 —— setResourceAccessor(ResourceAccessor resourceAccessor) 方法的调用:

  1. setResourceAccessor在每个构造函数中都会被调用,确保其被调用。
  2. liquibase在该方法中完成了对于字段packagesToScan的赋值
//ServiceLocator.setResourceAccessor()
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<>();
		// 优先用户配置的系统属性. 注意这里是二选一, 且优先级更高, 所以如果你确信需要启用这项配置, 别忘记把liquibase的默认扫描package集合也加上
		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 {
				// liquibase中扩展机制之一: 扫描classpath下所有的 META-INF/MANIFEST.MF 文件
				// 	笔者较为欣赏这类扩展机制, 相较于配置文件中的扩展配置, MANIFEST.MF文件的修改必须借助专门的maven插件, 且需要重新打包, 这让整个扩展过程相对来说更为可控
				manifests = resourceAccessor.getResourcesAsStream("META-INF/MANIFEST.MF");
				if (manifests != null) {
					for (InputStream is : manifests) {
						Manifest manifest = new Manifest(is);
						// 配置项为 Liquibase-Package 所对应的值
						//	关于这个键值对, 可以在liquibase-core-xxx.jar 中的META-INF/MANIFEST.MF 文件找到范例, 
						//	默认liquibase也正是采用这种形式进行自身配置的, 这也非常好地契合了"优秀的框架也应该自身当作扩展来集成"原则
						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()) {
				// 添加一系列默认的package
				..... 
			}
		}
	}
}

3.3 方法

在方法这个小节,我们主要介绍下findClassesImpl(Class requiredInterface),正是在该方法中完成了对于接口/服务实现类的扫描和加载,算是ServiceLocator功能的灵魂。

private List<Class> findClassesImpl(Class requiredInterface) throws Exception {

	List<Class> classes = new ArrayList<>();

	classResolver.addClassLoader(resourceAccessor.toClassLoader());
	// 将查找逻辑下放给classResolver来完成
	for (Class<?> clazz : classResolver.findImplementations(requiredInterface, packagesToScan.toArray(new String[packagesToScan.size()]))) {
		// 提供更为精细的查找扩展机制 @LiquibaseService注解
		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

  1. Spring ServiceLocator 介绍及应用
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-16 11:36:57  更:2021-08-16 11:38:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 0:36:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码