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 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来迭代这种特殊集合了。

 

九:通过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 相关及示例_第1张图片

     

 

 

 

      

你可能感兴趣的:(Java Reflection 相关及示例)