0x00 反射

维基百科中,对于反射有这样一段的定义。

In computer science, reflection is the ability of a computer program to examine, introspect, and modify its own structure and behavior at runtime. 在计算机科学中,反射是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。

在Java中,反射库位于java.lang.reflect。反射可以在运行中分析类、查看对象等,在本文中将使用反射库分析指定的类。

0x01 分析类

在Java中,类通常都是继承自某个类,可能实现某些接口,拥有域、构造函数、方法等,并且它通常拥有修饰符如public。在本小节中我们先不分析类的域、构造函数以及方法,而是先分析该类继承自何处,实现了哪些接口。定义一个类通常写为Modifier class className extends superClassName implements interfaceName, ...,所以我们就按这个顺序来分析各个组成部分。在下文中,将使用reflectClass代表分析的类。

类的访问修饰符通常为public或不使用任何关键字,我们可以使用getModifiers()得知类的修饰符。使用getModifiers()得到的修饰符是一个int类型的数值,它将对应着它是由哪些修饰符构成的,如未使用修饰符其值将为0,而public的值为1,static为8。若是由多个修饰符共同组成,则获得的值为修饰符的值相加,如public static为9。虽然这个数值代表着它是由对应的修饰符构成的,但输出它并不是非常地直观,所以可以使用Modifiers.toString()将其输出为对应的关键字。

将类的名称转换为字符串可以直接使用toString()方法,或者可以使用getName()方法,两者的区别为Class类的toString()方法会在该类型名称前添加“class ”。得到类的父类可以使用Class类的getSuperClass()方法,它将得到一个Class类实例,也可以使用getName()将它转换为字符串。得到类实现的接口可以通过getInterfaces()getGenericInterfaces()方法获得,两者的区别为getInterfaces()将得到该类实现的所有接口,返回类型为Class[],而getGenericInterfaces()只得到由该类直接实现的接口,返回类型为Type[]

下面将通过方法printClass输出某个类的定义。

public void printClass(Class reflectClass) {
  // 得到类描述符
  String modifiers = Modifier.toString(reflectClass.getModifiers());
  // 若无类描述符便不输出
  if (modifiers.length() != 0) {
    System.out.print(modifiers + " ");
  }
  /*
   * 因类描述符只能是public或无描述符,所以也可以直接写为:
   * if (Modifier.isPublic(reflectClass.getModifiers())) {
   *   System.out.print("public ");
   * }
   */

  // 输出类名
  System.out.print("class " + reflectClass.getName());

  // 若父类为Object便不输出,否则输出extends SuperclassName
  Class superClass = reflectClass.getSuperclass();
  if (superClass != Object.class) {
    System.out.print(" extends " + superClass.getName());
  }

  // 若实现了接口便输出implements InterfaceName, ...
  Class[] interfaces = reflectClass.getInterfaces();
  if (interfaces.length != 0) {
    System.out.print(" implements ");
    int n = 0;
    for (Class i : interfaces) {
      if (n != 0) {
        // 若不是第一个实现的接口便打印一个,
        System.out.print(", ");
      }
      System.out.print(i.getTypeName());
      i++;
    }
  }

  System.out.println(" {");

  // 调用其它方法输出变量、构造函数、方法

  System.out.println("}");
}

0x02 分析类域

在Java中,域也就是类的成员变量,可以通过getDeclaredFields()方法得到类中的域,它返回一个Field类型的数组。除这个方法外,也可使用getFields()方法,它与getDeclaredFields()的区别为getField()只返回类中的公有域,而getDeclaredFields()返回全部的域。在定义域时,通常由访问修饰符、类型、域名构成。与类的访问修饰符相同,域访问修饰符也通过getModifiers()方法获得,并通过Modifier.toString(int)输出。除此以外,下文中的构造函数与方法的访问修饰符也将由同样的方法获得。

定义域时都会定义域的类型,比如是基础数据类型int、double等,或是引用类型。要得到该域的类型,可以使用getDeclaringClass()方法,它将返回一个Class类型的变量,可以使用getName()输出类名。最后,通过getName()方法得到域名称即可。

void printField() {
  Field[] fields = reflectClass.getDeclaredFields();

  for (Field field : fields) {
    System.out.print("\t");
    String modifiers = Modifier.toString(field.getModifiers());
    if (modifiers.length() != 0) {
      System.out.print(modifiers + " ");
    }
    Class fieldClass = field.getDeclaringClass();
    System.out.print(fieldClass.getName());
    System.out.print(" " + field.getName());
    System.out.println(";");
  }
}

0x03 分析类方法

类方法的定义也拥有修饰符也方法名,同样的,它们也是分别通过getModifiers()以及getName()获得。除此以外,方法的定义还包含返回类型以及参数。返回类型与上一节中域的数据类型一致,也是获得Class类型并利用getName()输出,区别是返回类型是由getReturnType()方法获得。

方法的参数个数是由方法编写者确定的,它可能是一个或者多个,又或是没有参数,所以获得的参数必定为一个数组。获得参数可以使用两个不同的方法,分别是getParameters()getParameterTypes()。二者的区别是前者将返回一个Parameter类型的数组,里面存放着参数的完整信息,包括修饰符、类型、参数名等;而后者返回的是Type类型的数组,只存放着参数的类型。若选择getParameters(),可以通过与上文同样的方法获得访问修饰符,并使用getType()获得参数类型,最后使用getName()输出。而使用getParameterTypes()则可以直接使用getName()输出。在本节与下一节构造函数分析中将分别使用两个方法。

void printMethod() {
  Method[] methods = reflectClass.getDeclaredMethods();

  for (Method method : methods) {
    System.out.print("\t");
    String modifiers = Modifier.toString(method.getModifiers());
    if (modifiers.length() != 0) {
      System.out.print(modifiers + " ");
    }

    Class returnClass = method.getReturnType();
    System.out.print(returnClass.getName() + " ");
    System.out.print(method.getName() + "(");

    Parameter[] parameters = method.getParameters();
    int i = 0;
    for (Parameter parameter : parameters) {
      if (i != 0) {
        // 若不是第一个参数则先输出一个,
        System.out.print(", ");
      }

      modifiers = Modifier.toString(parameter.getModifiers());
      if (modifiers.length() != 0) {
        System.out.print(modifiers + " ");
      }

      System.out.print(parameter.getType().getName() + " ");
      System.out.print(parameter.getName());
      i++;
    }

    System.out.println(");");
  }
}

0x04 分析类构造函数

构造函数的定义与类方法类似,区别是构造函数的名称一定为类名,且没有返回的类型。因此,我们在分析构造函数时,只需要将分析方法的代码中的Method改为Constructor,并删去获取返回类型的代码,其它基本相同。

void printConstructor() {
  Constructor[] constructors = reflectClass.getDeclaredConstructors();

  for (Constructor constructor : constructors) {
    System.out.print("\t");
    String modifiers = Modifier.toString(constructor.getModifiers());
    if (modifiers.length() != 0) {
      System.out.print(modifiers + " ");
    }

    System.out.print(constructor.getName() + "(");

    Type[] types = constructor.getParameterTypes();
    int i = 0;
    for (Type type : types) {
      if (i != 0) {
        System.out.print(", ");
      }
      System.out.print(type.getTypeName());
      i++;
    }

    System.out.println(");");
  }
}

0x05 使用及异常

在分析类时,可以在编程阶段直接指定要分析的对象,但这样在如果要分析其他类就要修改源代码,所以可以在程序运行过程中输入要分析的类,然后使用Class.forName(String)找到该类。若输入的类不存在,使用Class.forName(String)将会抛出一个ClassNotFoundException异常。

0x06 测试及结果

下面是通过类分析程序分析java.lang.Double得到的结果,供参考使用。

public abstract class java.lang.Number implements java.io.Serializable {
	private static final java.lang.Number serialVersionUID;

	public java.lang.Number();

	public byte byteValue();
	public short shortValue();
	public abstract int intValue();
	public abstract long longValue();
	public abstract float floatValue();
	public abstract double doubleValue();
}

0x07 参考资料

  1. Cay S. Horstmann and Gary Cornell. Core Java Volume I – Fundamentals, Ninth Edition.
  2. Bruce Eckel. Thinking in Java, Fourth Edition.
  3. Wikipedia - Reflection.
  4. Java Platform, Standard Edition 8. API Specification.