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
OptionalloadBytecode(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 OptionalgetInputStream(String className) throws IOException;
接下来是定义了一个输入流和一个输出流,输入流被使用来加载一个字节码,输出流用来存储一个字节码,也就是二者分别承载load和save的输入输出流。
protected abstract OptionalgetInputStream(String className) throws IOException; protected abstract Optional getOutputStream(String className) throws IOException;
上述二者是用来为实现BytecodeStorage接口的两个load和save方法服务的。
在load方法里,使用了try catch结构来去尝试读取一个类的字节码,如果发生异常则不会报告上级,而是直接忽略这个错误,把错误写在日志上继续执行。
之所以这样做的原因,可能是因为这个字节码存储是一个非必要的功能,即使存储失败,在下一次用到这个类时,大不了重新从JVM里读取就可以,所以就直接忽略字节码存储和使用时的异常,但是得写到日志里报告这个错误。
public final OptionalloadBytecode(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 { OptionalmaybeOutputStream = 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 OptionalgetInputStream(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(4)DefiningClassLoaderMBeangetOutputStream(String className) throws IOException { return Optional.of(new FileOutputStream(storageDir.resolve(className + CLASS_FILE_EXTENSION).toFile())); }
这个也是一个接口,定义了对于创建出的ClassLoader进行计数或者 *** 作的方法,分别是计数所有、按类型Type计数、缓存中的ClassLoader计数:
public interface DefiningClassLoaderMBean { // jmx int getDefinedClassesCount(); Map(5)DefinningClassLoadergetDefinedClassesCountByType(); int getCachedClassesCount(); Map getCachedClassesCountByType(); }
这个类是所有类中最大的一个工具类,本来其就是定义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 @NotNullT 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博客
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)