2021SC@SDUSC
前文承接在第二篇博客中提到了expression接口的方法 load():
public interface expression {
Type load(Context ctx);
}
而通过上一篇博客对于expressionTest和其他expression类的分析,可以看到在每一个expression中,都以不同的方式实现了这个load()方法。而load()方法有一个很重要的参数就是Context类型,因此本篇博客来分析以下Context类的内容和功能。
Context结构概览context类位于Codegen模块中的util包中,即公共工具包中,因为会被很多类引用。
context类结构:第一行代码为ActiveJ的注释内容,描述 Context 的作用:
“Context包含了一个动态类有关的信息”。基于之前的官网例子,即是在运行过程中创建动态类的工具。通过Context的内容不同,可以在运行过程中创建新的类(相比于定义好的Class来讲)。
接下来是方法和属性,这里显示指明了构造函数,因此不能使用无参构造函数,也就是一个Context必须被创立时就初始化。
private final ClassLoader classLoader; private final ClassBuilder> classBuilder; private final GeneratorAdapter g; private final Type selfType; private final Method method; private SetaccessibleMethods; public Context(ClassLoader classLoader, ClassBuilder> builder, GeneratorAdapter g, Type selfType, Method method) { this.classLoader = classLoader; this.classBuilder = builder; this.g = g; this.selfType = selfType; this.method = method; }
定义一系列的get方法,返回Context的属性,这里使用到了很多classBuilder的功能。
public ClassLoader getClassLoader() {
return classLoader;
}
public ClassBuilder> getClassBuilder() {
return classBuilder;
}
public GeneratorAdapter getGeneratorAdapter() {
return g;
}
public Type getSelfType() {
return selfType;
}
// 以下都是使用 classBuilder 的功能
public Class> getSuperclass() {
return classBuilder.superclass;
}
public List> getInterfaces() {
return classBuilder.interfaces;
}
public Map> getFields() {
return classBuilder.fields;
}
public Map getMethods() {
return classBuilder.methods;
}
在这里有一个标准化的方法 toJavaType(),能将传入类的Type转化为java的Type,为之后的流输出做铺垫。(流是 java8 引入的一个新概念,下文会讲到)
public Class> toJavaType(Type type) {
if (type.equals(getSelfType()))
throw new IllegalArgumentException();
int sort = type.getSort();
if (sort == BOOLEAN)
return boolean.class;
if (sort == CHAR)
return char.class;
if (sort == BYTE)
return byte.class;
...
if (sort == DOUBLE)
return double.class;
return void.class;
if (sort == OBJECT) {
try {
return classLoader.loadClass(type.getClassName());
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(format("No class %s in class loader", type.getClassName()), e);
}
}
if (sort == ARRAY) {
Class> result;
if (type.equals(getType(Object[].class))) {
result = Object[].class;
} else {
String className = type.getDescriptor().replace('/', '.');
try {
result = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(format("No class %s in Class.forName", className), e);
}
}
return result;
}
throw new IllegalArgumentException(format("No Java type for %s", type.getClassName()));
}
这里ToJavaType()方法返回了Class类中的int.class和double.class等类似的java原本数据类型的引用。通过ctrl定位到一个使用这些方法的地方,比如算术运算符类里,可以看到:
if (op != SHL && op != SHR && op != USHR) {
Type resultType = getType(unifyArithmeticTypes(ctx.toJavaType(leftType), ctx.toJavaType(rightType)));
这里使用了ToJavaType()方法来进行操作,来将运算符两边的数据来进行转化,从而进行数据的运算。
接下来还有其他的方法,但考虑到之前分析expression过程中遇到的主要矛盾,这里就不再写其他方法的分析与调用方式,让我们直接跳转到最重要的 invoke() 方法这里。
Invoke()首先是最简单的invoke调用步骤,需要的参数分别为expression,methodName,expression-
List,这里的invoke()没有实现代码,而是调用了紧邻的invoke()方法(很像javascript风格,比较难看清楚)
public Type invoke(expression owner, String methodName, expression... arguments) {
return invoke(owner, methodName, asList(arguments));
}
在下一个invoke()里,将owner转为了Type类型,arguments通过load()方法转为了Type数组。同 时在invoke()方法里又使用了一个invoke(),return语句中的invoke()将跳转到下一个紧邻的重载invoke() 里,在这一步里仍没有具体的调用语句(即真正的invoke操作)
public Type invoke(expression owner, String methodName, Listarguments) { Type ownerType = owner.load(this); Type[] argumentTypes = new Type[arguments.size()]; for (int i = 0; i < arguments.size(); i++) { expression argument = arguments.get(i); argumentTypes[i] = argument.load(this); } return invoke(ownerType, methodName, argumentTypes); }
在第三个invoke()里,第一行语句使用到了 java8 新的Stream数据结构:
public Type invoke(Type ownerType, String methodName, Type... argumentTypes) {
Class>[] arguments = Stream.of(argumentTypes)
.map(this::toJavaType)
.toArray(Class[]::new);
}
第一行里将参数列表转换为Java Type,并且生成类型为Stream流。这里关于Stream流的操作,是为了将来的元素读取不再使用迭代的方式,而是使用foreach方式。关于Java8中新引入的Stream菜鸟教程描述如下:
使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象
官方网站则提到Stream是一种更快的元素操作,其与迭代的区别可以如下表示,并且任务越多优势越明显:
之后判断调用方法的所有者是否为本类。
如果为本类,则在自身里查找对应Method参数的方法并且传入arguments参数进行调用;
如果不是本类,则先做一个标准化JavaType转换,然后调用其非静态方法里的对应Method的方法(可能是因为被调用参数arguments并不一定是非静态变量)。无论是否为本类,如果在类的methods里搜索对应的方法名没有找到时都会抛出异常。
if (ownerType.equals(getSelfType())) {
foundMethod = findMethod(
getAccessibleMethods().stream(),
methodName,
arguments);
if (foundMethod == null) {
throw new IllegalArgumentException("Method not found: " + ownerType.getClassName()
+ '#' + methodName +Arrays.stream(arguments)
.map(Class::getName).collect(joining(",", "(",
")")));
}
g.invokeVirtual(ownerType, foundMethod);
}else {
Class> javaOwnerType = toJavaType(ownerType);
foundMethod = findMethod(
Arrays.stream(javaOwnerType.getMethods())
.filter(m -> !isStatic(m.getModifiers()))
.map(Method::getMethod),
methodName,
arguments);
if (foundMethod == null) {
throw new IllegalArgumentException("Method not found: " + ownerType.getClassName()
+ '#' + methodName +Arrays.stream(arguments).map(Class::getName)
.collect(joining(",", "(", ")")));
}
}
对于静态方法的调用,需要使用invokeStatic()方法来主动声明是调用的静态方法,但其调用过程与上文无异,不再额外分析。
在invoke()方法结束后,由于invoke()方法的返回值类型为Type,因此还需要调用一次foundMethod.getReturnType()来返回。
foundMethod.getReturnType()第一行代码将方法所属的类的所有可行方法,都保存在集合里(collection),这里也使用到了Stream流数据结构的操作,看来ActiveJ不愧是最新的技术框架,将各种新的java技术都包括了进来,以提供更快的用户体验。
methodSet.addAll(Arrays.stream(Object.class.getMethods()) .filter(m -> !isStatic(m.getModifiers())) .map(Method::getMethod) .collect(toSet()));
添加所有的元素后,进行一次集合的遍历。如果方法名称与methodname不同的,则跳过;如果方法名称就是传入的name,则将method对应方法需要的参数都转化为Stream流这种数据结构保存起来。
if (!name.equals(method.getName()))
continue;
Class>[] methodArguments = Stream.of(method.getArgumentTypes())
.map(this::toJavaType).toArray(Class[]::new);
if (!isAssignable(methodArguments, arguments)) {
continue;
}
只根据函数名进行匹配可能是不准确的,因为可能有重载的存在,需要额外判断:
if (!isAssignable(methodArguments, arguments))
这条语句判断参数类型是否匹配,此方法引用到了如下循环来逐一判断所有的参数类型是否匹配:
private static boolean isAssignable(Class>[] to, Class>[] from) {
if (to.length != from.length) return false;
return IntStream.range(0, from.length)
.allMatch(i -> to[i].isAssignableFrom(from[i]));
}
to[i].isAssignableFrom(from[i]) 方法来自于java的Class.java类,其通过类型转化来检查二者是否为相同的类或者是超类。从而检查合法性。
如果重载的参数依旧相同或者为父子类关系,则淘汰类范围大的参数对应的方法,即取子类参数对应的方法。遍历结束后,返回唯一的一个方法对应的类。
if (foundMethod == null) {
foundMethod = method;
foundMethodArguments = methodArguments;
} else {
if (isAssignable(foundMethodArguments, methodArguments)) {
foundMethod = method;
foundMethodArguments = methodArguments;
} else if (isAssignable(methodArguments, foundMethodArguments)) {
// do nothing
} else {
throw new IllegalArgumentException("Ambiguous method: " + method + " " +
Arrays.toString(arguments));
}
}
结语
Context的内容是完成一个动态类的创建所用的工具集合,包括了如何定义一个运行时的动态类的属性和方法,从而帮助用户快速的生成一个动态类。使用load()方法加载对应的Context,即可动态的生成类或是使用此类的功能。本次分析主要是集中在Context的invoke()方法里,因为当生成一个类时,主要就是使用其提供的方法来进行操作(面向对象思想),Context提供了多种Invoke()方法来帮助用户调用静态和非静态方法,从而完成一个类的功能。
到第四篇博客了,大致可以得到一个判断:ActiveJ在提高JVM速度层面,使用了大量的面向对象里的meta class,以及反射和内省操作,可惜,这些概念在上学期的面向对象第一次接触,没有深入的理解,在学习ActiveJ的过程中还需要学习这些内容。
最后,在Context类的集合数据进行迭代和生成操作中,使用了Stream流操作来完成。此方法可以提高JVM序列的速度,这也是接触到的第一个ActiveJ为什么快捷方便的原因。在以后的分析和学习中,还可以接触到更多的新技术,继续学习吧。
参考Java 8 Stream | 菜鸟教程 (runoob.com)
Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合_云深不知处-CSDN博客
往期回顾ActiveJ框架学习(三)——expressionTest类千行源码分析_m0_56367233的博客-CSDN博客
ActiveJ框架学习(二)——Codegen的初步认识_m0_56367233的博客-CSDN博客
ActiveJ框架学习(一)---起步_m0_56367233的博客-CSDN博客



