首页 > 代码库 > Java Reflection 相关及示例
Java Reflection 相关及示例
Java Reflection 相关及示例
前言:
代码有点长、贴出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文件。
而动态加载则是在程序运行时、使用到的时候再根据实际的类型去加载类的实例。
六:三种获取Class方式的区别
1、 List.class、是在编译时就可以获取Foo类的类型。
2、 list.getClass()是在运行时根据Foo的一个具体的实例对象来获取其Class类型、比如他有可能是ArrayList类型的、也有可能是LinkedList、更有可能是TreeList类型的。这只有在运行时才能确定具体是哪一种类型。
3、 Class.forName(“packageName.ClassName”)不仅表示了类的类型、还代表了动态加载类。
七:实例前的说明
反射的操作都是在编译之后进行的、也就是说在运行时执行的。可以根据一个具体的Java类类型来创建此类的实例、前提是此类必须要有无参构造方法!
八:通过Java Reflection 绕过泛型
对于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<String> addElementsByMethodReflect(ArrayList<String> 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<String> arrayList = new ArrayList<String>(); 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来迭代这种特殊集合了。
九:通过Java Reflection 来重构一个类
首先说明:这样做的意义是通过他来知晓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
类结构图:
Java Reflection 相关及示例