Java中的反射机制

2020-09-19  本文已影响0人  因你而在_caiyq

原创文章,转载请注明原文章地址,谢谢!

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

示例

一个普通的Apple类,正常创建对象的方式就是用new的方式,那么如果使用反射来创建对象呢?

public class Apple {
    private 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 is " + apple.getPrice());
    //反射调用
    Class<?> clazz = Class.forName("com.example.demo.reflect.Apple");
    Method setPriceMethod = clazz.getMethod("setPrice", int.class);
    Constructor<?> appleConstructor = clazz.getConstructor();
    Object appleObj = appleConstructor.newInstance();
    setPriceMethod.invoke(appleObj, 12);
    Method getPriceMethod = clazz.getMethod("getPrice");
    System.out.println("Apple price is " + getPriceMethod.invoke(appleObj));
}

上面的示例中,是通过获取类的构造器来创建对象,并调用对应的方法,上面的Class实例是程序运行时,通过类加载器,加载指定类的字节码文件到内存中,得到一个该类对应的Class实例。

总结一下获取类的Class对象以及创建类对象的几种方式。

//获取类的Class对象
//方式一:Class.forName()
Class<?> clazz = Class.forName("java.lang.String");
//方式二:.class
Class<String> clazz2 = String.class;
//方式三:类对象的getClass()
String str = new String();
Class<? extends String> clazz3 = str.getClass();

//创建类对象
//方式一:Class对象的newInstance()
Class<String> clz = String.class;
String str2 = clz.newInstance();
//方式二:Constructor的newInstance()
Class<String> clz2 = String.class;
Constructor<String> constructor = clz2.getConstructor();
String str3 = constructor.newInstance();

反射中的一些API使用

类名 用途
Class类 类的实体,在运行的Java应用程序中表示类和接口
Field类 类的成员变量(成员变量也称为类的属性)
Method类 类的方法
Constructor类 类的构造方法
Class类
方法 用途
asSubclass(Class<U> clazz) 把传递的类的对象转换成代表其子类的对象
Cast 把对象转换成代表类或是接口的对象
getClassLoader() 获取类加载器
getClasses() 返回一个数组,包含该类中所有公共类和接口的对象
getDeclaredClasses() 返回一个数组,包含该类中所有类和接口类的对象
forName(String className) 根据类名获取类的对象
getName() 获取类的完整路径名字
newInstance() 创建类的实例
getPackage() 获取类的包
getSimpleName() 获取类的简单名字
getSuperclass() 获取当前类继承的父类的名字
getInterfaces() 获取当前类实现的类或是接口
方法 用途
getField(String name) 获取某个公有的属性对象
getFields() 获取所有公有的属性对象
getDeclaredField(String name) 获取某个属性对象
getDeclaredFields() 获取所有属性对象
方法 用途
getAnnotation(Class<A> annotationClass) 返回该类中与参数类型匹配的公有注解对象
getAnnotations() 返回该类所有的公有注解对象
getDeclaredAnnotation(Class<A> annotationClass) 返回该类中与参数类型匹配的所有注解对象
getDeclaredAnnotations() 返回该类所有的注解对象
方法 用途
getConstructor(Class...<?> parameterTypes) 获取该类中与参数类型匹配的公有构造方法
getConstructors() 获取该类中所有公有的构造方法
getDeclaredConstrucyor(Class...<?> parameterTypes) 获取该类中与参数类型匹配的构造方法
getDeclaredConstrucyors() 获取该类所有构造方法
方法 用途
getMethod(String name, Class...<?> parameterTypes) 获取该类某个公有方法
getMethods() 获取所有公有方法
getDeclaredMethod(String name, Class...<?> parameterTypes) 获取该类某个方法
getDeclaredMethods() 获取该类所有方法
方法 用途
isAnnotation() 如果是注解类,返回true
isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果是指定类型注解类,返回true
isAnonymousClass() 如果是匿名类,返回true
isArray() 如果是数组类,返回true
isEnum() 如果是枚举类,返回true
isInstance(Object obj) 如果obj是该类的实例,返回true
isInterface() 如果是接口类,返回true
isLocalClass() 如果是局部类,返回true
isMemberClass() 如果是内部类,返回true
Field类
方法 用途
equals(Object obj) 属性与obj相等,返回true
get(Object obj) 获取obj中对应的属性值
set(Object obj, Object value) 设置obj中对应的属性值
Method类
方法 用途
invoke(Object obj, Object...args) 传递Object对象及参数调用该对象对应的方法
Constructor类

newInstance(Object... initargs)|根据传递的参数创建类的对象

反射的原理(源码)

反射的核心方法是invoke,所以从invoke方法入手,看下invoke的源码。

@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方法会首先检查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对象,这是一个native方法。

@CallerSensitive
public static native Class<?> getCallerClass();

获取了这个Class对象caller后用checkAccess方法做一次快速的权限校验,其实现为

volatile Object securityCheckCache;

void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers) throws IllegalAccessException {
    if (caller == clazz) {  // quick check
        return;             // ACCESS IS OK
    }
    Object cache = securityCheckCache;  // read volatile
    Class<?> targetClass = clazz;
    if (obj != null
            && Modifier.isProtected(modifiers)
            && ((targetClass = obj.getClass()) != clazz)) {
        // Must match a 2-list of { caller, targetClass }.
        if (cache instanceof Class[]) {
            Class<?>[] cache2 = (Class<?>[]) cache;
            if (cache2[1] == targetClass &&
                    cache2[0] == caller) {
                return;     // ACCESS IS OK
            }
            // (Test cache[1] first since range check for [1]
            // subsumes range check for [0].)
        }
    } else if (cache == caller) {
        // Non-protected case (or obj.class == this.clazz).
        return;             // ACCESS IS OK
    }

    // If no return, fall through to the slow path.
    slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
}

首先先执行一次快速校验,一旦调用方法的Class正确则权限检查通过。若未通过,则创建一个缓存,中间再进行一堆检查,比如检验是否为protected属性。如果上面的所有权限检查都未通过,那么将执行更详细的检查,其实现为

void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers, Class<?> targetClass) throws IllegalAccessException {
        Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);

        // Success: Update the cache.
        Object cache = ((targetClass == clazz)
                        ? caller
                        : new Class<?>[] { caller, targetClass });

        // Note:  The two cache elements are not volatile,
        // but they are effectively final.  The Java memory model
        // guarantees that the initializing stores for the cache
        // elements will occur before the volatile write.
        securityCheckCache = cache;         // write volatile
    }

大体意思就是,用Reflection.ensureMemberAccess方法继续检查权限,若检查通过就更新缓存,这样下一次同一个类调用同一个方法时就不用执行权限检查了,这是一种简单的缓存机制。由于JMM的happens-before规则能够保证缓存初始化能够在写缓存之前发生,因此两个cache不需要声明为volatile。

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

private volatile MethodAccessor methodAccessor;
// For sharing of MethodAccessors. This branching structure is
// currently only two levels deep (i.e., one root Method and
// potentially many Method objects pointing to it.)
//
// If this branching structure would ever contain cycles, deadlocks can
// occur in annotation code.
private Method              root;

那么MethodAccessor到底是个啥玩意呢?

public interface MethodAccessor {
    /** Matches specification in {@link java.lang.reflect.Method} */
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException;
}

可以看到MethodAccessor是一个接口,定义了invoke方法。分析其Usage可得它的具体实现类有

第一次调用一个Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没有创建;等第一次调用时才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()完成反射调用

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实例由reflectionFactory对象操控生成,它在AccessibleObject下的声明如下

static final ReflectionFactory reflectionFactory = 
        AccessController.doPrivileged(new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());

再研究一下sun.reflect.ReflectionFactory类的源码。实际的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开始的。研究ReflectionFactory.newMethodAccessor()生产MethodAccessor对象的逻辑,一开始(native版)会生产NativeMethodAccessorImpl和DelegatingMethodAccessorImpl两个对象。

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
        setDelegate(delegate);
    }

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        return delegate.invoke(obj, args);
    }

    void setDelegate(MethodAccessorImpl delegate) {
        this.delegate = delegate;
    }
}

在ReflectionFactory类的newMethodAccessor方法里,我们可以看到首先是生成了一个NativeMethodAccessorImpl对象,再这个对象作为参数调用DelegatingMethodAccessorImpl类的构造方法。这里的实现是使用了代理模式,将NativeMethodAccessorImpl对象交给DelegatingMethodAccessorImpl对象代理。查看DelegatingMethodAccessorImpl类的构造方法可以知道,其实是将NativeMethodAccessorImpl对象赋值给DelegatingMethodAccessorImpl类的delegate属性。所以说ReflectionFactory类的newMethodAccessor方法最终返回DelegatingMethodAccessorImpl类对象。所以在前面的ma.invoke()里,其将会进入DelegatingMethodAccessorImpl类的invoke方法中。它其实是一个中间层,方便在native版与Java版的MethodAccessor之间进行切换。然后下面就是native版MethodAccessor的Java方面的声明

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method method) {
        this.method = method;
    }

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        // We can't inflate methods belonging to vm-anonymous classes because
        // that kind of class can't be referred to by name, hence can't be
        // found from the generated bytecode.
        if (++numInvocations > ReflectionFactory.inflationThreshold()
                && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            parent.setDelegate(acc);
        }

        return invoke0(method, obj, args);
    }

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

    private static native Object invoke0(Method m, Object obj, Object[] args);
}

每次NativeMethodAccessorImpl.invoke()方法被调用时,程序调用计数器都会增加1,看看是否超过阈值;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。

Java版MethodAccessor的生成使用MethodAccessorGenerator实现。这里运用了asm动态生成字节码技术(sun.reflect.ClassFileAssembler),原理比较复杂。

总结

Inflation机制。初次加载字节码实现反射,使用Method.invoke()和Constructor.newInstance()加载花费的时间是使用原生代码加载花费时间的3 - 4倍,这使得那些频繁使用反射的应用需要花费更长的启动时间。为了避免这种痛苦的加载时间,在第一次加载的时候重用了JVM的入口,之后切换到字节码实现的实现。就像注释里说的,实际的MethodAccessor实现有两个版本,一个是Native版本,一个是Java版本。Native版本一开始启动快,但是随着运行时间边长,速度变慢。Java版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是NativeMethodAccessorImpl的实现,而当反射调用次数超过15次之后,则使用MethodAccessorGenerator生成的MethodAccessorImpl对象去实现反射。

扩展
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD})
public @interface CallerSensitive {
}

简而言之,用@CallerSensitive注解修饰的方法从一开始就知道具体调用它的对象,这样就不用再经过一系列的检查才能确定具体调用它的对象了。它实际上是调用sun.reflect.Reflection.getCallerClass方法。Reflection类位于调用栈中的0帧位置,sun.reflect.Reflection.getCallerClass()方法返回调用栈中从0帧开始的第x帧中的类实例。该方法提供的机制可用于确定调用者类,从而实现“感知调用者(Caller Sensitive)”的行为,即允许应用程序根据调用类或调用栈中的其它类来改变其自身的行为。

博客内容仅供自已学习以及学习过程的记录,如有侵权,请联系我删除,谢谢!

上一篇下一篇

猜你喜欢

热点阅读