想听听大家对于我这个想法的一些看法,喷也好,赞也罢,希望留下您宝贵的建议!
有共同想法并且想实现的请入群 2500261120
在使用autort插件时,首先要到autort服务器下载所有的jar包快照。里面存在了检索jar的相关信息。
在第一篇我们专门介绍过如何划分java类中出现的某一串字符,想了解的或者不知道我要干什么的请点这里 。其实说白了,也就是系统的整个类的组织及查询方式。Trie树可以在我们知道类全名的情况下尽快找到所需要的类。
当通过类全名找到存储某个类的Node时,此Node中有个map属性,key为类名,而值保存了这个类在版本迭代过程中的所有快照。类似于Git用快照来保存文件迭代的过程。不同的是,这里的快照是类的快照,而不是文件。类似如下形式:
一个类的不同快照会使用List列表来存储,因为版本的迭代发布本来就是有先后顺序的。快照的具体json结构如下:
{ serialVersion:快照支持的一系列版本,这些版本都是连续的 type:{ modifiers:修饰符 type:表示类型,如1代表类,2代表interface,3代表枚举,4为注解定义 name:内部类的写法为outer.innter extends:全类名的形式 implements:[] 多个类全名的形式 types:[] 存储多个type fields:1[ { modifiers:方法修饰符 declType:方法返回类型,也是全类名的形式 name:方法名称 } ... ] methods:[ { modifiers:方法修饰符 returnType:方法返回类型,也是全类名的形式 name:方法名称 throws:全类名的形式 arguments:[] 多个全类名的形式 } ... ] } }
首先介绍一下serialVersion属性。
举个例子,spring-core从开始发布版本至今共有90个版本。假如某个类A在1.2.8版本之前一直没有变化,那么这个serialVersion代表着之前的所有版本(共25个)。
假如在2.0进行API调整或者类重构时,这个类发生了变化,也就是快照中保存的某个或者多个属性发生了变化,比如某个方法的参数类型改变了,那么需要再生成一个快照结构,代表这次的变化。假如这次重构后经历了10次版本迭代仍然没有变化,那么这个结构中的serialVersion标识这10个版本。
如果下一次重构时,这个类变换了类路径,那么它将做为相应类路径下一个新建的类开始存在。而原来的快照只保存之前的35个版本快照。
下面重点来看一下type属性。
modifiers修饰符的定义:
例如某个类的修饰符有public和final,那么modifiers的值为 PUBLIC|FINAL
extends为字符串形式,存储了这个类可能继承的其它类。如果为空,默认为Object
implements类型为数组,存储值必须为全名,以方便通过Trie树迅速查找到这些接口相关的信息。可以为空。
types属性。这个属性中存储了多个type结构,这个type结构可能是类(包括抽象类)、接口、枚举类和注解定义类。
无论这个内部的结构有什么修饰符都需要存储相关的信息。和我们后面将要提到的属性与方法略有不同,属性与方法是通过不同的修饰符选择性存储。
举个例子:
public class Father01 { private class A{ public int a = 2; } public class B extends A{ } }
类A虽然是private,但是B继承了A。客户端的代码如下:
public class Son01 extends Father01{ public void test(){ B b = new B(); int x=b.a; // 使用了嵌套类A中声明的属性 } }
但是在块内Block定义的类就不需要保存了,因为封闭性较好,通常情况下无法被外部使用。接口,枚举类与注解定义类不能在块内定义。
剩下就是fields与methods了,目前private与默认访问控制符修饰的变量与方法不建立快照相关信息。
如果程序通过反射操作了三方包中的一些类属性,那么快照中需要存储所有的方法(包括构造方法)与变量,因为反射可以获取到类所有相关的信息。这会使快照的数据量变大并且快照数量变多,目前暂不考虑。
下面来说一说方法中的throws异常吧。
修改了某个公有方法抛出的异常。由原来的某个不受检查异常变为了受检查的异常,那么这一版本jar包就已经不满足我们程序的要求了,autort会通过检测将这一版本排除在可选择范围之外。
需要说明的是,并不是修改了方法抛出的异常就会为type新建快照,但是从某个检查异常修改为另外的检查异常,即使两者之间有继承关系,也一定会建立新的快照。不会对Error与RuntimeException的异常进行信息保存与处理,因为这些异常不会让现有的一些程序出错。
如下实例:
public void test() throws ReflectiveOperationException,NullPointerException{ ... } public void testException(){ try { test(); }catch (ReflectiveOperationException e) { e.printStackTrace(); } }
test方法新增加了一个非检查异常NullPointerException并且在testException方法中调用了这个方法,但是并不需要修改相关调用的代码。
其实我只说了需要存储哪些信息,并没有说为什么要选择性存储这些信息,我们可以思考一个问题:我们的程序使用了第三方jar包提供的相关功能,一搬情况下也就是下面几种:
(1)调用一个方法,使用new关键字创建对象时也看作调用它的构造方法,返回类型为构造方法所在的类类型。
(2)调用一个变量
(3)继承了三方的类或者实现了三方定义的接口
快照中需要存储足够的信息,当客户通过如上一种或多种方式使用了jar包功能时,可以通过扫描分析这些代码并结合快照中jar包类迭代的信息,快速找到当前代码合适的版本。
最终一个整体的存储结构大概如下图所示。
需要说明的是,一个jar包就需要有这样的一个结构来组织和存储相关的信息,主要是考虑到不同jar包会有类全名(包路径和类名)相等的情况,更重要的是会通过这样的结构来选择性加载到内存。