ActiveJ框架学习(六)——Util其他散工具分析

ActiveJ框架学习(六)——Util其他散工具分析,第1张

ActiveJ框架学习(六)——Util其他散工具分析

2021SC@SDUSC

一、前文承接

在之前的博客中完成了对于ClassBuilder工具的分析,在Util工具这个包中,还有其他的散的工具类,这次就集中分析这些散的工具包,通过分析这些工具类,从其依赖的功能来认识expression的功能。

二、工具认识

从以上可以看到,util工具包中,除了之前分析过的ClassBuilder和Context以外,还有AbstractBytecodeStorage、BytecodeStorage以及DefiningClassLoader、DefiningClassLoader MBean、FileBytecodeStorage等包。

从其名称上来看,剩余的工具类主要是关于字节码(Bytecode)的相关工具类,除此之外还有关于之前分析的ClassBuilder中使用到的ClassLoader等类的定义,说明这些剩下的工具包主要还是关于字节和类的相关工具。

三、工具包分析

首先分析两个接口,毕竟接口定义功能,会有详细的功能描述而且没有具体实现代码,容易理解和看懂。

(1)BytecodeStorage

中文直译为字节码存储器,简单来讲,这个BytecodeStorage定义了一个持久性的字节码存储器,可以加载之前生成过的字节码,从而在一个应用启动时避免类生成的开销。

这个接口的主要功能:

一是load,即通过一个参数 ClassName 类名来得到代表这个类的字节数组

二是Save,即保存一个类的字节码到Storage里,需要传入 ClassName 和对应的 Bytecode

Optional loadBytecode(String className);
//得到字节数组,通过参数ClassName对应
void saveBytecode(String className, byte[] bytecode);
//保存字节数组,不仅要传类名,还要字节码

这个接口定义了load和save两种方法,因此后续的实现接口的类需要根据具体的需要实现这两个抽象方法。

(2)AbstractBytecodeStorage

分析完上一个BytecodeStorage接口后,接下来就分析实现了这个接口的AbstractBytecodeStorage类来看如何能够存储一个类的字节码

private final Logger logger = LoggerFactory.getLogger(getClass());

第一行代码是上一篇文章中提到过的,大公司通常在每一个类里都会使用这一行代码,来规范在程序输出日志中的格式。

private static final int DEFAULT_BUFFER_SIZE = 8192;

 第二行语句定义了一个int型的默认缓冲区大小,这里设置为8192,也就是2的13次方

protected abstract Optional getInputStream(String className) throws IOException;

接下来是定义了一个输入流和一个输出流,输入流被使用来加载一个字节码,输出流用来存储一个字节码,也就是二者分别承载load和save的输入输出流。

protected abstract Optional getInputStream(String className) throws IOException;
protected abstract Optional getOutputStream(String className) throws IOException;

上述二者是用来为实现BytecodeStorage接口的两个load和save方法服务的。

在load方法里,使用了try catch结构来去尝试读取一个类的字节码,如果发生异常则不会报告上级,而是直接忽略这个错误,把错误写在日志上继续执行。

之所以这样做的原因,可能是因为这个字节码存储是一个非必要的功能,即使存储失败,在下一次用到这个类时,大不了重新从JVM里读取就可以,所以就直接忽略字节码存储和使用时的异常,但是得写到日志里报告这个错误。

public final Optional loadBytecode(String className) {
	try {
		Optional maybeInputStream = getInputStream(className);
		if (!maybeInputStream.isPresent()) return Optional.empty();

		try (InputStream stream = maybeInputStream.get()) {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
			int size;
			while ((size = stream.read(buffer)) != -1) {
				baos.write(buffer, 0, size);
				
		    return Optional.of(baos.toByteArray());
			
	} catch (IOException e) {
		onLoadError(className, e);
		return Optional.empty();
	}
}

save方法相比于保存来讲比较简单,因为这个不像读取,无需逐一判断每一个字节的正确性而是直接保存即可,如果发生错误也是忽略,然后放到日志里即可

public final void saveBytecode(String className, byte[] bytecode) {
	try {
		Optional maybeOutputStream = getOutputStream(className);
		if (!maybeOutputStream.isPresent()) return;

		try (OutputStream outputStream = maybeOutputStream.get()) {
			outputStream.write(bytecode);
		}
	} catch (IOException e) {
		onSaveError(className, bytecode, e);
	}
}

处理try catch的异常很简单,就是直接在logger日志里报告即可:

protected void onLoadError(String className, IOException e) {
	logger.warn("Could not load bytecode for class: {}", className, e);
}
protected void onSaveError(String className, byte[] bytecode, IOException e) {
	logger.warn("Could not save bytecode for class: " + className, e);
}
(3)FileBytecodeStorage

AbstractBytecodeStorage里还有两个纯抽象方法,因此其显然也不能实例化,而FileBytecode Storage就是一个继承于AbstractBytecodeStorage抽象类的方法

第一行代码里明确说明FileBytecodeStorage是用来 *** 作".class"文件的类

private static final String CLASS_FILE_EXTENSION = ".class";

在构造函数里,需要使用一个String类型的 path 路径 来初始化这个FileBytecodeStorage类,注意这个构造函数是罕见的private类型的,也就是不允许用户来实例化

private FileBytecodeStorage(Path storageDir) {
	this.storageDir = storageDir;
}

为了解决实例化的问题,FileBytecodeStorage里定义了一个方法:

public static FileBytecodeStorage create(Path storageDir) {
	return new FileBytecodeStorage(storageDir);
}

这个方法是静态方法,通过create()方法来得到一个FileBytecodeStorage实例,这样就可以通过类名+方法名来得到调用这个方法

FileBytecodeStorage类继承了抽象类AbstractBytecodeStorage,因此需要重写getInputStream()和getOutputStream()方法

getInputStream()方法就是传入一个类名,然后加上之前定义的 CLASS_FILE_EXTENSION 变量组成一个完整的java文件路径,并由FileInputStream流来读取这个文件的字节码到内存中:

protected Optional getInputStream(String className) {
	try {
		FileInputStream fileInputStream = new FileInputStream(storageDir.resolve(className + CLASS_FILE_EXTENSION).toFile());
		return Optional.of(fileInputStream);
	} catch (FileNotFoundException ignored) {
		return Optional.empty();
	}
}

getOutputStream()方法直接返会一个写文件的对象,用来将内存中的字节码写入到特定的文件中:

protected Optional getOutputStream(String className) throws IOException {
	return Optional.of(new FileOutputStream(storageDir.resolve(className + CLASS_FILE_EXTENSION).toFile()));
}
(4)DefiningClassLoaderMBean

这个也是一个接口,定义了对于创建出的ClassLoader进行计数或者 *** 作的方法,分别是计数所有、按类型Type计数、缓存中的ClassLoader计数:

public interface DefiningClassLoaderMBean {
    // jmx
	int getDefinedClassesCount();

	Map getDefinedClassesCountByType();

	int getCachedClassesCount();

	Map getCachedClassesCountByType();
}
(5)DefinningClassLoader

这个类是所有类中最大的一个工具类,本来其就是定义ClassLoader的工具类,而ClassLoader是在Codegen中使用最多的对象之一,所以这个类会是最大的工具类

构造函数依旧是私有,这个工具包很多都不允许外部类实例化

private DefiningClassLoader() {
}

private DefiningClassLoader(ClassLoader parent) {
	super(parent);
}

ClassLoader,结合之前的其他工具类,就是在字节码(Bytecode)和类文件(Class)之间进行转换的工具,因此defineClass()方法就是接受一个 ClassName 和 Bytecode 两个参数,从而将这个类通过之前分析过的 FileOutputStream 来写入到对应路径中去:

public Class defineClass(String className, byte[] bytecode) {
	Class aClass = super.defineClass(className, bytecode, 0, bytecode.length);
	definedClasses.put(className, aClass);
	if (debugOutputDir != null) {
		try (FileOutputStream fos = new FileOutputStream(debugOutputDir.resolve(className + ".class").toFile())) {
			fos.write(bytecode);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	return aClass;
}

另一个方法ensureClass则是以安全的方法来执行defineClass,在这种方式中,采用了java里的锁机制(synchronized)来进行安全的生成类,防止多线程运作中创建了很多个相同的类

synchronized (getClassLoadingLock(className)) {
		if (bytecodeStorage != null) {
			byte[] bytecode = bytecodeStorage.loadBytecode(className).orElse(null);
			if (bytecode != null) {
				return (Class) defineClass(className, bytecode);
			}
		}

		GeneratedBytecode generatedBytecode = bytecodeBuilder.apply(this, className);
		Class aClass = generatedBytecode.defineClass(this);

		if (bytecodeStorage != null) {
			bytecodeStorage.saveBytecode(className, generatedBytecode.getBytecode());
		}

		return (Class) aClass;
	}
}

这个锁机制在之前面向对象里单例设计模式中学到过

在上诉写入类的过程中,首先判断bytecodeStorage里有没有这个类的字节码存储在里面,之前分析过,如果这个类的字节码存储在里面,则可以直接用而不用再去加载一下这个类,这样可以加快启动时间

如果bytecodeStorage里没有这个类的字节码,则将这个类的字节码读到一个内嵌的缓存区中,在defineClass运行结束后删除这个缓存区里的内容

接下来是CreatedInstance,这个方法接受一个传入的类,并且接受一个参数数组,作为构造函数的参数:

static @NotNull  T createInstance(Class aClass, Object[] arguments) {
	try {
		return aClass
			.getConstructor(Arrays.stream(arguments).map(Object::getClass).toArray(Class[]::new))
					.newInstance(arguments);
	} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
		throw new RuntimeException(e);
	}
}

最后是对接口DefiningClassLoaderMBean的实现,前文提到主要是按照类型获取对应的类,包括缓存类、所有类的计数等等,这里得到所有缓存中的类的方法是getCachedClass():

public @Nullable Class getCachedClass(@NotNull ClassKey key) {
	return Optional.ofNullable(cachedClasses.get(key)).map(AtomicReference::get).orElse(null);
}

这里的cacheClasses是一个ConcurrentHashMap数据结构,上一篇文章中提到过,其是一个带有锁的HashMap,能够保证并发执行的正确性。

(6)GeneratedBytecode

这个类是最后一个工具类,功能很简单,这个就是之前一直提到的字节码存储的地方,包括了之前分析提到的DefiningClass方法,这个可能就是存储临时的字节码的数据结构,其构造函数接受的参数和DefineClassLoader一样,都是 类名+字节码,功能就是DefindClass的功能。

public final Class defineClass(DefiningClassLoader classLoader) {
	try {
		Class aClass = classLoader.defineClass(className, bytecode);
		onDefinedClass(aClass);
		return aClass;
	} catch (Exception e) {
		onError(e);
		throw e;
	}
}
四、结语

本次主要是分析了Codegen里expression部分的工具包,理解了ActiveJ如何进行类写入和读取的 *** 作,不过ActiveJ只使用到了java文件的读写,对于静态资源的读写并没有提到,但是可以看到的是在底层的写入依然是通过FileOutputStream流来写入文件的。

为了加快速度,ActiveJ定义了StorageCache缓存这种数据结构,无论是类写入还是读取,其字节码都可以从这个缓存中读取到,从而无需再从本地文件读取到JVM中去加载一个类。

五、往期回顾

ActiveJ框架学习(五)——ClassBuilder类分析_m0_56367233的博客-CSDN博客

ActiveJ框架学习(四)——Context功能分析_m0_56367233的博客-CSDN博客

ActiveJ框架学习(三)——expressionTest类千行源码分析_m0_56367233的博客-CSDN博客

​​​​​​ActiveJ框架学习(二)——Codegen的初步认识_m0_56367233的博客-CSDN博客

ActiveJ框架学习(一)---起步_m0_56367233的博客-CSDN博客

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5158868.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-18
下一篇 2022-11-18

发表评论

登录后才能评论

评论列表(0条)

保存