深入理解java反射

  • 前言
  • 一、反射机制相关的类?
  • 1.1获得类相关的方法
  • 1.2获得类中属性相关的方法
  • 1.3获得类中构造器相关的方法
  • 1.4获得类中构造器相关的方法
  • 1.5Field类、Method类、Constructor类方法
  • 二、反射的基本运用
  • 2.1获得 Class 对象
  • 2.2创建实例
  • 2.3获取方法
  • 2.4调用方法
  • 3.浅析invoke过程
  • 3.1invoke()方法实现源码
  • 3.1.1权限检查
  • 3.1.2调用MethodAccessor的invoke方法
  • 注意


前言

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


一、反射机制相关的类?

反射机制的相关类
与Java反射相关的类如下:

类名

用途

Class类

代表类的实体,在运行的Java应用程序中表示类和接口

Field类

代表类的成员变量(成员变量也称为类的属性)

Method类

代表类的方法

Constructor类

代表类的构造方法

1.1获得类相关的方法

方法

用途

forName(String className)

根据类名返回类的对象

newInstance()

创建类的实例

1.2获得类中属性相关的方法

方法

用途

getField(String name)

获得某个公有的属性对象

getFields()

获得所有公有的属性对象

getDeclaredField(String name)

获得某个属性对象

getFields()

获得所有公有的属性对象

1.3获得类中构造器相关的方法

java Teigha 解析dwg java深入解析_java

1.4获得类中构造器相关的方法

java Teigha 解析dwg java深入解析_Java_02

1.5Field类、Method类、Constructor类方法

java Teigha 解析dwg java深入解析_Java_03

二、反射的基本运用

2.1获得 Class 对象

三种方法:
(1) 使用 Class 类的 forName 静态方法:
(2)直接获取某一个对象的 class;
(3)调用某个对象的 getClass() 方法;

2.2创建实例

通过反射来生成对象主要有两种方式。
使用Class对象的newInstance()方法来创建Class对象对应类的实例。

Class<?> clazz = String.class;
Object str = clazz.newInstance();

先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。

//获取String所对应的Class对象
Class<?> clazz = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = clazz.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("java");
System.out.println(obj);

2.3获取方法

方法

用途

getDeclaredMethods

方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

getMethods

方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。

getMethod

方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。

2.4调用方法

当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。invoke 方法的原型为:

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException

3.浅析invoke过程

3.1invoke()方法实现源码

@CallerSensitive
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;             // read volatile
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

根据invoke方法的实现,将其分为以下几步:

3.1.1权限检查

首先检查AccessibleObject的override属性的值。AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。

override的值默认是false,表示需要权限调用规则,调用方法时需要检查权限;我们也可以用setAccessible方法设置为true,若override的值为true,表示忽略权限规则,调用方法时无需检查权限(也就是说可以调用任意的private方法,违反了封装)。

如果override属性为默认值false,则进行进一步的权限检查:
用Reflection.quickCheckMemberAccess(clazz, modifiers)方法检查方法是否为public,如果是的话跳出本步;如果不是public方法,那么用Reflection.getCallerClass()方法获取调用这个方法的Class对象,获取了这个Class对象caller后用checkAccess方法做一次快速的权限校验,一旦调用方法的Class正确则权限检查通过。
若未通过,则创建一个缓存,中间再进行一堆检查(比如检验是否为protected属性)。
如果上面的所有权限检查都未通过,那么将执行更详细的检查,大体意思就是,用Reflection.ensureMemberAccess方法继续检查权限,若检查通过就更新缓存,这样下一次同一个类调用同一个方法时就不用执行权限检查了,这是一种简单的缓存机制。由于JMM的happens-before规则能够保证缓存初始化能够在写缓存之前发生,因此两个cache不需要声明为volatile。
到这里,前期的权限检查工作就结束了。如果没有通过检查则会抛出异常,如果通过了检查则会到下一步。

3.1.2调用MethodAccessor的invoke方法

Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。首先要了解Method对象的基本构成,每个Java方法有且只有一个Method对象作为root,它相当于根对象,对用户不可见。当我们创建Method对象时,我们代码中获得的Method对象都相当于它的副本(或引用)。root对象持有一个MethodAccessor对象,所以所有获取到的Method对象都共享这一个MethodAccessor对象,因此必须保证它在内存中的可见性。
第一次调用一个Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没有创建;等第一次调用时才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()完成反射调用

// NOTE that there is no synchronization used here. It is correct
// (though not efficient) to generate more than one MethodAccessor
// for a given Method. However, avoiding synchronization will
// probably make the implementation more scalable.
private MethodAccessor acquireMethodAccessor() {
    // First check to see if one has been created yet, and take it
    // if so
    MethodAccessor tmp = null;
    if (root != null) tmp = root.getMethodAccessor();
    if (tmp != null) {
        methodAccessor = tmp;
    } else {
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    }
    return tmp

实际的MethodAccessor实现有两个版本,一个是Java版本,一个是native版本,两者各有特点。初次启动时Method.invoke()和Constructor.newInstance()方法采用native方法要比Java方法快3-4倍,而启动后native方法又要消耗额外的性能而慢于Java方法。也就是说,Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。

为了尽可能地减少性能损耗,HotSpot JDK采用“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版本。 这项优化是从JDK 1.4开始的。每次NativeMethodAccessorImpl.invoke()方法被调用时,程序调用计数器都会增加1,看看是否超过阈值;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。

注意

反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

https://www.sczyh30.com/posts/Java/java-reflection-1/#%E4%B8%80%E3%80%81%E5%9B%9E%E9%A1%BE%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF%E5%8F%8D%E5%B0%84%EF%BC%9F

https://www.jianshu.com/p/9be58ee20dee

https://www.sczyh30.com/posts/Java/java-reflection-2/#%E5%BC%95%E5%85%A5