代码有点长、贴出github地址:https://github.com/andyChenHuaYing/scattered-items/tree/master/items-java-reflection
测试目标类:TargetClass、自定义的辅助类比较多、在这里不贴了。篇幅有限、并且测试也简单、因此测试类也没有提及。
Java Reflection是针对Class也就是我们平常说的类而言的、用于操作Java中的Class、在Java中万事万物皆对象(需要注意的是原始类型和静态类、静态方法不是面向对象的、它是属于类的)、那么“类”也是Java中的对象、是“Class类”类型的对象。
Java Reflection相关的是四个final类型的类Class、Method、Field、Constructor。以及java中所有类的父类Object。其中Class的构造方法是私有的、也就意味着不允许我们外部调用Class类构造方法来构造Class实例、Class类提供了对于Java中类类型的操作、比如根据类类型获取以及操作属性、方法(包括私有方法、私有类型、至于这种行为的应不应该暂不讨论、但是既然现在存在、自然有存在的道理)、ElementType.TYPE,ElementTYPE.METHOD, ElementTYPE.FIELD级别的Annotation信息等。
Java类中的属性也是对象——Field、同样普通方法也是——Method。构造方法——Constructor、Field、Method、Constructor指定类类型中属性对象、普通方法对象和构造方法对象的表示类型、用于操作指定类类型中的属性和方法。
在一个类编译完成之后,下一步就需要开始使用类,如果要使用一个类,肯定离不开JVM。在程序执行中JVM通过装载,链接,初始化这3个步骤完成。
装载:类的装载是通过类加载器完成的,加载器将.class文件的二进制文件装入JVM的方法区,并且在堆区创建描述这个类的java.lang.Class对象。用来封装数据。 但是同一个类只会被类装载器装载一次。
链接:链接就是把二进制数据组装为可以运行的状态。链接分为校验,准备,解析这3个阶段。校验一般用来确认此二进制文件是否适合当前的JVM(版本),准备就是为静态成员分配内存空间,并设置默认值。解析指的是转换常量池中的代码作为直接引用的过程,直到所有的符号引用都可以被运行程序使用(建立完整的对应关系)。
初始化:完成之后,类型也就完成了初始化,初始化之后类的对象就可以正常使用了,直到一个对象不再使用之后,将被垃圾回收。释放空间。
当没有任何引用指向Class对象时就会被卸载,结束类的生命周期。
假设我们现在有一个类Foo、那么他的类类型可以通过下面这三种方式来获取:
1、 Classclazz1 = Foo.class;
2、 Classclazz2 = foo.getClass();
3、 Classclazz3 = Class.forName(“packageName.Foo”);
前面中知道、每个类只会被装载一次、也就是说每个类不管是通过哪种方式获取的类类型、都是一样的:
clazz1 == clazz2 == clazz3;
这两个名词我们都不陌生、但是通过反射我们更能认清他们的区别。还是再重复一遍两者的概念:
1、 静态加载:表示编译时(javac)加载类。
2、 动态加载:表示运行时(java)加载类。
与之对应的、我们可以结合RuntimeException和非RuntimeException来理解。非RuntimeException就在编译时必须处理的异常、比如常见的:ClassNotFoundException、ClassCastException、IOException等等、这些在我们敲代码的时候就必须要指明是抛出还是使用try、catch来捕获。否则的话就是编译失败、而RuntimeException则不需要我们在编译的时候来处理、而是在运行时如果不满足条件就会抛出异常、比如常见的:NullPointException、IndexOutofBoundException等等。
Java中所有使用new关键字构造的类实例都是在编译期加载的、也就意味着当编译我们写好的Java类时、所有与此类相关的并且使用new关键字实例化的都必须存在。一并编译成class文件。
而动态加载则是在程序运行时、使用到的时候再根据实际的类型去加载类的实例。
1、 List.class、是在编译时就可以获取Foo类的类型。
2、 list.getClass()是在运行时根据Foo的一个具体的实例对象来获取其Class类型、比如他有可能是ArrayList类型的、也有可能是LinkedList、更有可能是TreeList类型的。这只有在运行时才能确定具体是哪一种类型。
3、 Class.forName(“packageName.ClassName”)不仅表示了类的类型、还代表了动态加载类。
反射的操作都是在编译之后进行的、也就是说在运行时执行的。可以根据一个具体的Java类类型来创建此类的实例、前提是此类必须要有无参构造方法!
对于Generic(泛型)我们都不陌生、尤其是在使用集合的时候、指定泛型可以避免我们添加错误的类型进去、导致意想不到的结果、Generic是在编译时规定我们只能添加同一种类型数据。但是通过反射我们却可以绕过泛型的校验、通过编译。
代码:
package org.alien.reflection.generic;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
/**
* Demonstrate java reflect could bypass the Collection Generic.
* Happy day, happy life.
*
* @author andy
* @version 1.0-SNAPSHOT
* Created date: 2014-12-22 20:46
*/
@SuppressWarnings("unchecked")
public class CollectionGenericEssence {
/**
* Use java reflect to bypass the Collection Generic.
* @param arrayList
* ArrayList of String.
* @param value
* An object which will be added in ArrayList of String.
*/
public ArrayList addElementsByMethodReflect(ArrayList arrayList, Object value) {
/*
* Illegal value type:
* arrayList.add(value);
*/
Class arrayListClass = arrayList.getClass();
try {
Method method = arrayListClass.getMethod("add", Object.class);
method.invoke(arrayList, value);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return arrayList;
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList arrayList = new ArrayList();
Class clazz = arrayList.getClass();
Method method = clazz.getMethod("add", Object.class);
method.invoke(arrayList, 20);
System.out.println(arrayList.size());
}
@Override
public String toString() {
return "CollectionGenericEssence{}";
}
}
总结:Java中泛型是防止输入错误类型的一种措施、通过上面代码也可以说明编译之后集合的泛型就已经没有作用了。也就是去泛型化的、只在编译时期有用。当然、我们也就不能使用foreach来迭代这种特殊集合了。
首先说明:这样做的意义是通过他来知晓JavaReflection相关类的使用、这些是基础、同时也不是全部、侧重点不同。有了下面的这些过滤、以后用到这些之外的也不外乎寻找的结果不同、过程都是相同的。对于Field、Method的执行会在后面有。同时下面重现的类也不包括方法体、暂时只包括本类的属性、方法。以下分成几点来说明、并且这些都在代码中有注释、且所有的方法都可以单独测试、测试类的代码就不贴了、后面会有TargetClass内容、里面使用到的自定义Annotation也不再贴、都放在github上、前后会贴出地址。具体过程:
1. 获取完成包名、
2. 关于import、当类中使用的类都是全名时、就不需要import(当然方法体中除外)
3. 获取类级别注释、
4. 获取类的父类、
5. 获取类的所有接口、
6. 获取类修饰符、
7. 获取类名、
8. 获取类中所有属性、
9. 分别获取属性的所有Annotation、
10. 获取所有构造方法、
11. 获取所有构造方法的Annotation、
12. 获取所有方法、
13. 分别获取方法的Annotation、
14. 分别获取所有方法的修饰符、
15. 分别获取所有方法的返回值、
16. 分别获取所有方法的所有参数以及类型、
17. 分别获取所有方法声明的异常信息。
18. 综上组合成最终结果。
具体代码:
package org.alien.reflection.api;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
/**
* Happy day, happy life.
*
* @author andy
* @version 1.0-SNAPSHOT
* Created date: 2014-12-22 21:48
*/
public class ShowClassDetailInfo {
private static final String LINE_BREAK = "\r\n";
private static final String SPACE = " ";
private static final String SEMICOLON = ";";
public static String showClassFullInfo(Class clazz) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("package ").append(showClassPackage(clazz)).append(LINE_BREAK);
String classModifier = Modifier.toString(clazz.getModifiers());
stringBuilder.append(classModifier).append(SPACE).append(clazz.getSimpleName());
if (hasSuperClass(clazz)) {
stringBuilder.append(SPACE).append("extend ").append(clazz.getSimpleName());
}
if (hasInterface(clazz)) {
stringBuilder.append(SPACE).append("implement ").append(showClassImplInterfaces(clazz));
}
stringBuilder.append(" {").append(LINE_BREAK);
stringBuilder.append(showDeclaredField(clazz))
.append(LINE_BREAK)
.append(showConstructs(clazz))
.append(LINE_BREAK)
.append(showDeclaredMethod(clazz))
.append(LINE_BREAK)
.append("}");
return stringBuilder.toString();
}
/**
* Show target class's package name.
* @return
* Target class package name.
*/
public static String showClassPackage(Class clazz) {
return clazz.getPackage().getName();
}
/**
* Validate target class has super class or not.
* @param clazz
* Target class.
* @return
* If target class has super class, return true, otherwise false.
*/
public static boolean hasSuperClass(Class clazz) {
Class superclass = clazz.getSuperclass();
return superclass != null;
}
/**
* Show target class's super class.
* @param clazz
* Target class.
* @return
* The name of super class.
*/
public static String showSuperClass(Class clazz) {
if (hasSuperClass(clazz)) {
return clazz.getSuperclass().getSimpleName();
}
return "Object";
}
/**
* Validate target class has interface or not.
* @param clazz
* Target class.
* @return
* If target class has one or more interface, return true, otherwise false.
*/
public static boolean hasInterface(Class clazz) {
return clazz.getInterfaces().length > 0;
}
/**
* Show target class's interfaces.
* @param clazz
* Target class
* @return
* Interfaces info.
*/
public static String showClassImplInterfaces(Class clazz) {
StringBuffer stringBuffer = new StringBuffer();
Class[] classes = clazz.getInterfaces();
if (hasInterface(clazz)) {
for (Class face : classes) {
stringBuffer.append(face.getSimpleName()).append(", ");
}
stringBuffer = fixStringBuffer(stringBuffer);
}
return stringBuffer.toString();
}
/**
* Show target class's constructors
* @param clazz
* Target class
* @return
* Constructors info.
*/
public static String showConstructs(Class clazz) {
StringBuffer stringBuffer = new StringBuffer();
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
Annotation[] annotations = constructor.getDeclaredAnnotations();
stringBuffer = constructorAnnotation(stringBuffer, annotations);
String modifierType = Modifier.toString(constructor.getModifiers());
stringBuffer.append(modifierType).append(SPACE).append(clazz.getSimpleName()).append("(");
Class[] types = constructor.getParameterTypes();
if (types.length > 0) {
for (Class type : types) {
String parameterTypeName = type.getName();
String parameterTypeReferenceName = type.getClass().getSimpleName().toLowerCase();
stringBuffer = injectMethodParametersContent(stringBuffer,
parameterTypeName, parameterTypeReferenceName);
}
stringBuffer = fixStringBuffer(stringBuffer);
}
stringBuffer.append(")").append("{...}").append(LINE_BREAK).append(LINE_BREAK);
}
return stringBuffer.toString();
}
/**
* Show all fields value declared by target class instance.
* @param object
* Target class instance.
* @return
* The object array of fields value.
* @throws IllegalAccessException
* Execution failed.
*/
public static Object[] showAllDirectInstanceFieldsValue(Object object) throws IllegalAccessException {
Field[] fields = object.getClass().getDeclaredFields();
Object[] objects = new Object[fields.length];
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
/*
change the access privilege so that we could obtain or change the private Field ,
Constructor(except Class) or private method action.
*/
field.setAccessible(true);
Object obj = fields[i].get(object);
objects[i] = obj;
System.out.println(fields[i].get(object));
System.out.println(Modifier.toString(fields[i].getModifiers()));
}
return objects;
}
/**
* Show class's declared field.
* @param clazz
* Target class.
* @return
* Declared field info.
*/
public static String showDeclaredField(Class clazz) {
StringBuffer stringBuffer = new StringBuffer();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Annotation[] annotations = field.getDeclaredAnnotations();
stringBuffer = constructorAnnotation(stringBuffer, annotations);
String fieldModifier = Modifier.toString(field.getModifiers());
String fieldType = field.getType().getSimpleName();
stringBuffer.append(fieldModifier).append(SPACE).append(fieldType).append(SPACE);
stringBuffer.append(field.getName()).append(SEMICOLON).append(LINE_BREAK).append(LINE_BREAK);
}
return stringBuffer.toString();
}
/**
* Show class's declared method.
* @param clazz
* Target Class type.
*/
public static String showDeclaredMethod(Class clazz) {
Method[] methods = clazz.getDeclaredMethods();
return showMethodsDetail(methods);
}
/**
* Show methods' detail information
* @param methods
* Target class's method.
* @return
* All methods print info .
*/
private static String showMethodsDetail(Method[] methods) {
StringBuffer stringBuffer = new StringBuffer();
for (Method method : methods) {
//Construct Annotation
Annotation[] annotations = method.getDeclaredAnnotations();
stringBuffer = constructorAnnotation(stringBuffer, annotations);
//Construct method modifier type
String modifierType = Modifier.toString(method.getModifiers());
stringBuffer.append(modifierType).append(SPACE);
// String methodInfo = method.toString();
// if (methodInfo.startsWith("public")) {
// stringBuffer.append("public ");
// }
// if (methodInfo.startsWith("protected")) {
// stringBuffer.append("protected ");
// }
// if (methodInfo.startsWith("private")) {
// stringBuffer.append("private ");
// }
//Construct method name
Class> returnType = method.getReturnType();
String methodName = method.getName();
stringBuffer.append(returnType).append(SPACE).append(methodName).append("(");
//Construction method parameters
Class[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length > 0) {
for (Class parameterTypeClass : parameterTypes) {
String parameterTypeName = parameterTypeClass.getName();
String parameterTypeReferenceName = parameterTypeClass.getSimpleName().toLowerCase();
stringBuffer = injectMethodParametersContent(stringBuffer,
parameterTypeName, parameterTypeReferenceName);
}
stringBuffer = fixStringBuffer(stringBuffer);
}
stringBuffer.append(")");
//Construct throws Exceptions
Class>[] exceptionTypes = method.getExceptionTypes();
if (exceptionTypes.length > 0) {
stringBuffer.append("throws ");
for (Class exceptionType : exceptionTypes) {
String exceptionName = exceptionType.getSimpleName();
stringBuffer.append(exceptionName).append(", ");
}
stringBuffer = fixStringBuffer(stringBuffer);
}
//Construct method body, of course is invisible.
stringBuffer.append("{...}").append(LINE_BREAK).append(LINE_BREAK);
}
return stringBuffer.toString();
}
/**
* Construct target's annotation expression.
* @param stringBuffer
* Result container.
* @param annotations
* Target's all annotations.
* @return
* Final expression.
*/
private static StringBuffer constructorAnnotation(StringBuffer stringBuffer, Annotation[] annotations) {
if (annotations.length > 0) {
for (Annotation annotation : annotations) {
String annotationName = annotation.annotationType().getSimpleName();
stringBuffer.append("@").append(annotationName).append("\r\n");
}
}
return stringBuffer;
}
/**
* Cut the last "," in stringBuffer.
* @param stringBuffer
* raw str.
* @return
* Fixed str.
*/
private static StringBuffer fixStringBuffer(StringBuffer stringBuffer) {
return stringBuffer.delete(stringBuffer.lastIndexOf(","), stringBuffer.length());
}
/**
* Construct method parameters list.
* @param stringBuffer
* Parameters list container.
* @param parameterTypeName
* Parameter type class name.
* @param parameterTypeReferenceName
* Parameter dummy reference name.
* @return
* Method parameters list info.
*/
private static StringBuffer injectMethodParametersContent(StringBuffer stringBuffer, String parameterTypeName, String parameterTypeReferenceName) {
return stringBuffer.append(parameterTypeName).append(SPACE).append(parameterTypeReferenceName).append(", ");
}
}
1、方法的执行只有下面一个方法、可以自己动手试一下:
Method.invok(TargetClassInstance, Object …parameters);
2、 属性的执行方法类似、具体可以看一下Field的API:
Object = field.getValue(TargetClassInstancetarget);
重点说明私有方法、属性的执行赋值。
Java中对类、方法、属性使用修饰符Public、protected、 默认、 private 来修饰限定一个类、属性、方法的访问权限。而JavaReflection中不但有对类类型以及他的组成部分的操作方式、还提供了修改类类型中的属性、方法的访问权限的权利。AccessibleObject、他是Field、Method、Constructor、ReflectPermission的父类、其中有一个方法:setAccessible()、可接收一个boolean类型参数、来指定是否开放当前Field、Method、Constructor的私有访问的权利。注意(前面提到过Class的构造方法是私有的、是不是可以通过这种方式来获取Class类私有构造方法的权利?答案是否定的、因为Class并不是AccessibleObject的子类!)。
代码片段:
public static Object[] showAllDirectInstanceFieldsValue(Object object) throws IllegalAccessException {
Field[] fields = object.getClass().getDeclaredFields();
Object[] objects = new Object[fields.length];
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
/*
change the access privilege so that we could obtain or change the private Field ,
Constructor(except Class) or private method action.
*/
field.setAccessible(true);
Object obj = fields[i].get(object);
objects[i] = obj;
System.out.println(fields[i].get(object));
System.out.println(Modifier.toString(fields[i].getModifiers()));
}
return objects;
}
这里没有类加载器的信息、类加载器牵涉到较多的JVM内容、暂不表。最后放上类结构图以及本项目在github上的地址:
https://github.com/andyChenHuaYing/scattered-items/tree/master/items-java-reflection
类结构图: