Java 反射机制

反射机制

AVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。即它允许我们在运行时发现和使用类的信息。

一:反射基础

反射就是把java类中的各种成分映射成一个个的Java对象。
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。

优点:

  • 能够运行时动态获取类的实例,提高灵活性;
  • 与动态编译结合

缺点:

  • 使用反射性能较低,需要解析字节码,将内存中的对象进行解析。
    解决方案:
    1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度;
    2、多次创建一个类的实例时,有缓存会快很多
    3、ReflectASM工具类,通过字节码生成的方式加快反射速度
  • 相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)

1、类加载机制流程:
在这里插入图片描述
2、类的初始化后
在这里插入图片描述

二:Class类

Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class ans enum)或接口(interface and annotation)(每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName(“类名”)等方法获取class对象)。数组同样也被映射为为class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 class 对象。

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    private static final int ANNOTATION= 0x00002000;
    private static final int ENUM      = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;

    private static native void registerNatives();
    static {
        registerNatives();
    }

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.   //私有构造器,只有JVM才能调用创建Class对象
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

到这我们也就可以得出以下几点信息:

  • Class类也是类的一种,与class关键字是不一样的。
  • 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
  • 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
  • Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
  • Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要

三:反射的使用

在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。
1、Class类对象的获取
在类加载的时候,jvm会创建一个class对象
class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种

  • 根据类名:类名.class
  • 根据对象:对象.getClass()
  • 根据全限定类名:Class.forName(全限定类名)
    @Test
    public void classTest() throws Exception {
        // 获取Class对象的三种方式
        logger.info("根据类名:  \t" + User.class);
        logger.info("根据对象:  \t" + new User().getClass());
        logger.info("根据全限定类名:\t" + Class.forName("com.test.User"));
        // 常用的方法
        logger.info("获取全限定类名:\t" + userClass.getName());
        logger.info("获取类名:\t" + userClass.getSimpleName());
        logger.info("实例化:\t" + userClass.newInstance());
    }

输出结果:-------------------------
根据类名:  	class com.test.User
根据对象:  	class com.test.User
根据全限定类名:	class com.test.User
获取全限定类名:	com.test.User
获取类名:	User
实例化:	User [name=init, age=0]

2、常用的 Class类的方法

  • forName():获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。
  • getName(): 取全限定的类名(包括包名),即类的完整名字。
  • isInterface() 判断Class对象是否是表示一个接口
  • getInterfaces() 返回Class对象数组,表示Class对象所引用的类所实现的所有接口
  • getSupercalss() 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
  • newInstance() 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器
  • getFields() 获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。
    类似的还有getMethods和getConstructors
  • getDeclaredFields
    获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors

3、getName、getCanonicalName与getSimpleName的区别

  • getSimpleName:只获取类名
  • getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName
  • getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了

四:Constructor类及其用法

Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法,获取Constructor对象是通过Class类中的方法获取的。
1、Class类与Constructor相关的主要方法如下

  • forName(String className) 返回与带有给定字符串名的类或接口相关联的 Class 对象。
  • getConstructor(Class<?>…
    parameterTypes) 返回指定参数类型、具有public访问权限的构造函数对象
  • getConstructors() 返回所有具有public访问权限的构造函数的Constructor对象数组
  • getDeclaredConstructor(Class<?>…
    parameterTypes) 返回指定参数类型、所有声明的(包括private)构造函数对象
  • getDeclaredConstructor() 返回所有声明的(包括private)构造函数对象
  • newInstance() 调用无参构造器创建此 Class 对象所表示的类的一个新实例。
public class ConstructionTest implements Serializable {
    public static void main(String[] args) throws Exception {

        Class<?> clazz = null;

        //获取Class对象的引用
        clazz = Class.forName("com.example.javabase.User");

        //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
        User user = (User) clazz.newInstance();
        user.setAge(20);
        user.setName("Jack");
        System.out.println(user);

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

        //获取带String参数的public构造函数
        Constructor cs1 =clazz.getConstructor(String.class);
        //创建User
        User user1= (User) cs1.newInstance("hiway");
        user1.setAge(22);
        System.out.println("user1:"+user1.toString());

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

        //取得指定带int和String参数构造函数,该方法是私有构造private
        Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
        //由于是private必须设置可访问
        cs2.setAccessible(true);
        //创建user对象
        User user2= (User) cs2.newInstance(25,"hiway2");
        System.out.println("user2:"+user2.toString());

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

        //获取所有构造包含private
        Constructor<?> cons[] = clazz.getDeclaredConstructors();
        // 查看每个构造方法需要的参数
        for (int i = 0; i < cons.length; i++) {
            //获取构造函数参数类型
            Class<?> clazzs[] = cons[i].getParameterTypes();
            System.out.println("构造函数["+i+"]:"+cons[i].toString() );
            System.out.print("参数类型["+i+"]:(");
            for (int j = 0; j < clazzs.length; j++) {
                if (j == clazzs.length - 1)
                    System.out.print(clazzs[j].getName());
                else
                    System.out.print(clazzs[j].getName() + ",");
            }
            System.out.println(")");
        }
    }
}


class User {
    private int age;
    private String name;
    public User() {
        super();
    }
    public User(String name) {
        super();
        this.name = name;
    }

    /**
     * 私有构造
     * @param age
     * @param name
     */
    private User(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

输出结果:

User{age=20, name='Jack'}
--------------------------------------------
user1:User{age=22, name='hiway'}
--------------------------------------------
user2:User{age=25, name='hiway2'}
--------------------------------------------
构造函数[0]:private com.example.javabase.User(int,java.lang.String)
参数类型[0]:(int,java.lang.String)
构造函数[1]:public com.example.javabase.User(java.lang.String)
参数类型[1]:(java.lang.String)
构造函数[2]:public com.example.javabase.User()
参数类型[2]:()

2、Constructor类本身一些常用方法如下

  • getDeclaringClass() 返回 Class 对象,该对象表示声明由此 Constructor
    对象表示的构造方法的类,其实就是返回真实类型(不包含参数)
  • getName() 以字符串形式返回此构造方法的名称
  • getParameterTypes() 按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型
  • newInstance(Object… initargs) 使用此 Constructor对象表示的构造函数来创建新实例

五:Field类及其用法

Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
1、Class类与Field对象相关方法如下

  • getField(String name) 获取指定name名称、具有public修饰的字段,包含继承字段
  • getField() 获取修饰符为public的字段,包含继承字段
  • getDeclaredField(String name) 获取指定name名称的(包含private修饰的)字段,不包括继承的字段
  • getDeclaredField() 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段

2、Field类还有其他常用的方法如下

  • set(Object obj, Object value) 将指定对象变量上此 Field 对象表示的字段设置为指定的新值
  • get(Object obj) 返回指定对象上此 Field 表示的字段的值
  • getType() 返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。
  • isEnumConstant() 如果此字段表示枚举类型的元素则返回 true;否则返回 false
  • toGenericString() 返回一个描述此 Field(包括其一般类型)的字符串
  • getName() 返回此 Field 对象表示的字段的名称
  • getDeclaringClass() 返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段
  • setAccessible(boolean flag) 将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性

六:Method类及其用法

Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。
1、Class类获取Method对象相关的方法

  • getMethod(String name, Class<?>… parameterTypes) 返回一个 Method
    对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
  • getMethods() 返回一个包含某些 Method 对象的数组,这些对象反映此 Class
    对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共方法。
  • getDeclaredMethod(String name, Class<?>… parameterTypes)
    返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法
  • getDeclaredMethod() 返回 Method 对象的一个数组,这些对象反映此 Class
    对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
    2、Method类还有其他常用的方法如下
  • invoke(Object obj, Object… args) 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法
  • getReturnType() 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型
  • getGenericReturnType() 返回表示由此 Method 对象所表示方法的正式返回类型的 Type
    对象,也是方法的返回类型
  • getParameterTypes() 按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method
    对象所表示的方法的形参类型。即返回方法的参数类型组成的数组
  • getGenericParameterTypes() 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method
    对象所表示的方法的形参类型的,也是返回方法的参数类型
  • getName() 以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称
  • isVarArgs() 判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false
  • toGenericString() 返回描述此 Method 的字符串,包括类型参数

七:反射机制执行的流程

public class HelloReflect {
    public static void main(String[] args) {
        try {
            // 1. 使用外部配置的实现,进行动态加载类
            TempFunctionTest test = (TempFunctionTest)Class.forName("com.tester.HelloReflect").newInstance();
            test.sayHello("call directly");
            // 2. 根据配置的函数名,进行方法调用(不需要通用的接口抽象)
            Object t2 = new TempFunctionTest();
            Method method = t2.getClass().getDeclaredMethod("sayHello", String.class);
            method.invoke(test, "method invoke");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e ) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    
    public void sayHello(String word) {
        System.out.println("hello," + word);
    }

}

1、使用外部配置的实现,进行动态加载类

第一步:首先调用了 java.lang.Class 的静态方法,获取类信息

 @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        // 先通过反射,获取调用进来的类信息,从而获取当前的 classLoader
        Class<?> caller = Reflection.getCallerClass();
        // 调用native方法进行获取class信息
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

主要是先获取 ClassLoader, 然后调用 native 方法,获取信息。

第二步: 调用 newInstance() 的实现方式

  // 首先肯定是 Class.newInstance
    @CallerSensitive
    public T newInstance()
        throws InstantiationException, IllegalAccessException
    {
        if (System.getSecurityManager() != null) {
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
        }

        // NOTE: the following code may not be strictly correct under
        // the current Java memory model.

        // Constructor lookup
        // newInstance() 其实相当于调用类的无参构造函数,所以,首先要找到其无参构造器
        if (cachedConstructor == null) {
            if (this == Class.class) {
                // 不允许调用 Class 的 newInstance() 方法
                throw new IllegalAccessException(
                    "Can not call newInstance() on the Class for java.lang.Class"
                );
            }
            try {
                // 获取无参构造器
                Class<?>[] empty = {};
                final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
                // Disable accessibility checks on the constructor
                // since we have to do the security check here anyway
                // (the stack depth is wrong for the Constructor's
                // security check to work)
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Void>() {
                        public Void run() {
                                c.setAccessible(true);
                                return null;
                            }
                        });
                cachedConstructor = c;
            } catch (NoSuchMethodException e) {
                throw (InstantiationException)
                    new InstantiationException(getName()).initCause(e);
            }
        }
        Constructor<T> tmpConstructor = cachedConstructor;
        // Security check (same as in java.lang.reflect.Constructor)
        int modifiers = tmpConstructor.getModifiers();
        if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            if (newInstanceCallerCache != caller) {
                Reflection.ensureMemberAccess(caller, this, null, modifiers);
                newInstanceCallerCache = caller;
            }
        }
        // Run constructor
        try {
            // 调用无参构造器
            return tmpConstructor.newInstance((Object[])null);
        } catch (InvocationTargetException e) {
            Unsafe.getUnsafe().throwException(e.getTargetException());
            // Not reached
            return null;
        }
    }

newInstance() 主要做了三件事:

  1. 权限检测,如果不通过直接抛出异常;
  2. 查找无参构造器,并将其缓存起来;
  3. 调用具体方法的无参构造方法,生成实例并返回;
  private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                        int which) throws NoSuchMethodException
    {
        // 获取所有构造器
        Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
        for (Constructor<T> constructor : constructors) {
            if (arrayContentsEq(parameterTypes,
                                constructor.getParameterTypes())) {
                return getReflectionFactory().copyConstructor(constructor);
            }
        }
        throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
    }
  

getConstructor0() 为获取匹配的构造方器;
分三步:

  • 先获取所有的constructors, 然后通过进行参数类型比较
  • 找到匹配后,通过 ReflectionFactory copy一份constructor返回
  • 否则抛出 NoSuchMethodException;

获取所有的构造器主要步骤

  • 先尝试从缓存中获取
  • 如果缓存没有,则从jvm中重新获取,并存入缓存,缓存使用软引用进行保存,保证内存可用

2. 根据配置的函数名,进行方法调用(不需要通用的接口抽象)
第一步,先获取 Method

 // java.lang.Class
    @CallerSensitive
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }
  • 获取所有方法列表(获取所有构造器的方法很相似,都是先从缓存中获取方法,如果没有,则从jvm中获取)
  • 根据方法名称和方法列表,选出符合要求的方法
  • 如果没有找到相应方法,抛出异常,否则返回对应方法

第二步,根据方法名和参数类型过滤指定方法返回(最优匹配或者精准匹配)

第三步,调用 method.invoke() 方法

八、反射调用流程小结

最后,用几句话总结反射的实现原理:

  • 反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化;

  • 每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上;

  • 反射也是考虑了线程安全的,放心使用;

  • 反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销;

  • 反射调用多次生成新代理Accessor, 而通过字节码生存的则考虑了卸载功能,所以会使用独立的类加载器;

  • 当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;

  • 调度反射方法,最终是由jvm执行invoke0()执行;

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值