java 反射:入门、使用、原理

java 反射

反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);

上面这样子进行类对象的初始化,我们可以理解为「正」。

而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。

这时候,我们使用 JDK 提供的 反射 API 进行反射调用:

Class clz = Class.forName("com.chenshuyi.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);		//获取指定的非私有方法
Constructor constructor = clz.getConstructor();				//获取非私有无参构造
Object object = constructor.newInstance();
method.invoke(object, 4);

上面两段正射和反射代码的执行结果,其实是完全一样的。但是其思路完全不一样,正射在没有运行时就已经确定了要运行的类(Apple),而反射则是在 运行时通过类名才得知要运行的类(com.chenshuyi.reflect.Apple)。

所以说什么是反射?

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整信息,打破权限壁垒、并调用对应的方法。

一个简单的例子

public class Apple {

	private int name;
    public int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常的调用
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        
        //使用反射调用
        Class clz = Class.forName("com.chenshuyi.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);	//获取指定的非私有方法
        Constructor appleConstructor = clz.getConstructor();			//获取非私有无参构造
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));		//反射执行无参方法
    }
}

从代码中可以看到我们使用反射调用了 setPrice 方法,并传递了 14 的值。之后使用反射调用了 getPrice 方法,输出其价格。上面的代码整个的输出结果是:

Apple Price:5
Apple Price:14

反射常用api:

1. 获取反射中的Class对象

想要使用反射的前提是获取目标类的 Class对象 ,每个类在加载的时候都会生成class对象

  • 使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

     Class clz = Class.forName("java.lang.String");		//调用静态方法
    
  • 使用 .class 方法: 这种方法只适合在编译前就知道操作的 Class。

     Class clz = String.class;
    
  • 使用类 对象的 getClass() 方法。

     String str = new String("Hello");
     Class clz = str.getClass();
    

2. 通过反射创建类对象

反射创建对象有两种方式,通过 Class对象的 newInstance()方法或者 Constructor对象的 newInstance()方法。

第一种:通过 Class 对象的 newInstance() 方法。 Class对象创建对象只能用默认无参构造

Class<Apple> clz = Apple.class;
Apple apple = clz.newInstance();		// 返回类型同class类型一致

第二种:通过 Constructor 对象的 newInstance() 方法,用指定的 构造器创建对象。

Class<Apple> clz = Apple.class;
Constructor<Apple> constructor = clz.getConstructor(String.class, int.class);
Apple apple = constructor.newInstance("红富士", 15);

3. 通过反射获取类属性、方法、构造器

Class<Apple> clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

getFields() 只能获取非私有属性,输出结果是:

price

而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:

Class<Apple> clz = Apple.class;
Field[] fields = clz.getDeclaredFields();		//获取包括私有属性在内的所有属性
for (Field field : fields) {
    System.out.println(field.getName());
}

输出结果是:

name
price

与获取类属性一样,当我们去获取 Method、Constructor时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。

反射源码解析

反射中的主要围绕着invoke方法,下面是 Method类的定义,Method类是不用泛型的,其中保存着类、方法、注解、参数信息。

public final class Method extends Executable {
    private Class<?>            clazz;
    private int                 slot;
    // This is guaranteed to be interned by the VM in the 1.4
    // reflection implementation
    private String              name;
    private Class<?>            returnType;
    private Class<?>[]          parameterTypes;
    private Class<?>[]          exceptionTypes;
    private int                 modifiers;
    // Generics and annotations support
    private transient String              signature;
    // generic info repository; lazily initialized
    private transient MethodRepository genericInfo;
    private byte[]              annotations;
    private byte[]              parameterAnnotations;
    private byte[]              annotationDefault;
    private volatile MethodAccessor methodAccessor;

下面我们来看看 JDK 的 invoke 方法到底做了些什么。

进入 Method 的 invoke 方法我们可以看到,一开始是进行了一些权限的检查,最后是调用了 MethodAccessor 类的 invoke 方法进行进一步处理,如下图红色方框所示。

在这里插入图片描述

那么 MethodAccessor 又是什么呢?

其实 MethodAccessor 是一个接口,定义了 invoke的具体操作,而它有三个具体的实现类:

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

第一个是抽象类,进一步封装接口、 干活的是第二个实现类,调用本地方法。第三个是第二个的静态代理。

  • sun.reflect. MethodAccessorImpl
  • sun.reflect. NativeMethodAccessorImpl
  • sun.reflect. DelegatingMethodAccessorImpl

而要看 method.invoke() 到底调用的是哪个 MethodAccessor 接口实现类的 invoke 方法,则需要看看 上面 acquireMethodAccessor() 方法中看看返回的 MethodAccessor 到底是上面三个哪一个。
在这里插入图片描述
从 acquireMethodAccessor() 方法我们可以看到,代码先判断是否存在对应的 MethodAccessor 对象,如果存在那么就复用之前的 MethodAccessor 对象,否则调用 反射工厂 ReflectionFactory 对象的 newMethodAccessor 方法生成一个 MethodAccessor 对象。
在这里插入图片描述
在反射工厂 ReflectionFactory 类的 newMethodAccessor 方法里,我们可以看到首先是生成了一个 NativeMethodAccessorImpl 对象,再这new一个 DelegatingMethodAccessorImpl 对 NativeMethodAccessorImpl 进行代理。

这里的实现是使用了代理模式,将 NativeMethodAccessorImpl 对象交给 DelegatingMethodAccessorImpl 对象代理。我们查看 DelegatingMethodAccessorImpl 类的构造方法可以知道,其实是将 NativeMethodAccessorImpl 对象赋值给 DelegatingMethodAccessorImpl 类的 delegate 属性。
在这里插入图片描述
所以说反射工厂 ReflectionFactory 类的 newMethodAccessor 方法最终返回 DelegatingMethodAccessorImpl 代理对象。所以我们在前面的 method.invoke() 里,最终执行的是 NativeMethodAccessorImpl 被代理类的 invoke 方法中。

method.invoke() --> DelegatingMethodAccessorImpl .invoke() --> NativeMethodAccessorImpl .invoke()
在这里插入图片描述
而在 NativeMethodAccessorImpl 的 invoke 方法里,其会判断调用次数是否超过阀值(numInvocations)。如果超过该阀值,那么就会生成另一个MethodAccessor 对象,并将原来 DelegatingMethodAccessorImpl 对象中的 delegate 属性指向最新的 MethodAccessor 对象。
在这里插入图片描述
最终我们可以看出 method.invoke() 方法最终是靠 NativeMethodAccessorImpl .invoke()执行的,是本地方法。

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;						//指向要执行的方法
    private DelegatingMethodAccessorImpl parent;		//其静态代理
    private int numInvocations;							//调用次数

    NativeMethodAccessorImpl(Method var1) {
        this.method = var1;
    }
    
	//超过该阀值,那么就会生成另一个MethodAccessor 对象
    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }

        return invoke0(this.method, var1, var2);
    }

    void setParent(DelegatingMethodAccessorImpl var1) {
        this.parent = var1;
    }

    private static native Object invoke0(Method var0, Object var1, Object[] var2);
}
"Inflation" mechanism. Loading bytecodes to implement Method.invoke() and Constructor.newInstance() 
currently costs 3-4x more than an invocation via native code for the first invocation (though subsequent
 invocations have been benchmarked to be over 20x faster).Unfortunately this cost increases startup time for certain 
applications that use reflection intensively (but only once per class) to bootstrap themselves.

Inflation 机制。初次加载字节码实现反射,使用 Method.invoke() 和 Constructor.newInstance() 加载花费的时间是使用原生 native代码加载花费时间的 3 - 4 倍。这使得那些频繁使用反射的应用需要花费更长的启动时间。

To avoid this penalty we reuse the existing JVM entry points for the first few invocations of Methods 
and Constructors and then switch to the bytecode-based implementations. Package-private to 
be accessible to NativeMethodAccessorImpl and NativeConstructorAccessorImpl.

为了避免这种痛苦的加载时间,我们在第一次加载的时候重用了 JVM 的入口,之后切换到字节码实现的实现。

就像注释里说的,实际的 MethodAccessor 实现有两个版本,一个是 Native 版本,一个是 Java 版本。

Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是 NativeMethodAccessorImpl 的实现,而当反射调用次数超过 15 次之后,则使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射。

讲到这里,我们了解了 Method 类的 invoke 方法的具体实现方式。知道了原来 invoke 方法内部有两种实现方式,一种是 native 原生的实现方式,一种是 Java 实现方式,这两种各有千秋。而为了最大化性能优势,JDK 源码使用了代理的设计模式去实现最大化性能。

同理: 使用java 反射创建对象例如 constructor.newInstance() 创建对象也是同样的道理,
有 NativeConstructorAcessorImpl.newInstance() 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值