深刻理解Java反射

题外话

最近公司建立了技术部的公众号用来鼓励你们进行分享,不少同窗比较纠结,以为找不到比较适合聊的topic。总的来讲大概两个缘由:一个是以为太基础讲出来比较 low 没有人会关注,另外一个是讲一些很牛的新技术又怕出错;然而每一项技术在本身的应用中都会有你本身独特的视角,也许这一点正是别人关心的。我我的认为分享一些咱们在编码中常常会碰到,而大多数人可能知其然而不知其因此然的话题是颇有意义的,今天我打算分享下咱们 Java 中一个常常用到的工具,反射和动态代理。java

当咱们在 IDE 中编写代码的时候,打一个点号,IDE会自动弹出对应的属性和方法名。当咱们在debug的时候,IDE会将方法运行时方法内局部变量和外部实例上属性的值都展现出来,spring中的IOC和AOP,以及一个RPC框架中,咱们反序列化,consumer的代理,以及provider的调用都会用到java的反射功能,有人说使用反射会慢,那么到底慢在哪里呢?面试

反射

反射使 Java 语言有了动态编译的功能,也就是在咱们编码的时候不须要知道对象的具体类型,可是在运行期能够经过Class.forName()获取一个类的class对象,在经过newInstance获取实例。spring

先看下java.lang.reflect包下的几个主要类的关系图,固然动态代理的工具类也在该包下。json

  • AnnotatedElement

做为顶级接口,这个接口提供了获取注解相关的功能,咱们在方法,类,属性,构造方法上均可以加注解,因此下面的Field,Method,Constructor都有实现这个接口,如下是咱们常常用的两个方法,jdk8之后接口里面能够经过default修饰方法实现了数组

Annotation[] getAnnotations(); //获取目标对象(方法和属性)上的全部注解
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
     Objects.requireNonNull(annotationClass);
     // Loop over all directly-present annotations looking for a matching one
     for (Annotation annotation : getDeclaredAnnotations()) {
         if (annotationClass.equals(annotation.annotationType())) {
             // More robust to do a dynamic cast at runtime instead
             // of compile-time only.
             return annotationClass.cast(annotation);
         }
     }
     return null;
 }
复制代码
  • GenericDeclaration

提供了获取泛型相关的功能,只有方法和构造方法上支持泛型,因此只有Method,Constructor实现了该接口缓存

  • Member

做为一个对象内部方法和属性的声明的抽象,包含了名称,修饰符,所在的类,其中修饰符包含了 static final public private volatile 等,经过一个整数表示,每个类型在二进制中占一个位.bash

public Class<?> getDeclaringClass();
public String getName();
public int getModifiers();


如下为Modifier类部分代码

public static final int PUBLIC           = 0x00000001;
public static final int PRIVATE          = 0x00000002;
public static final int PROTECTED        = 0x00000004;
public static final int STATIC           = 0x00000008;
public static final int FINAL            = 0x00000010;
public static final int SYNCHRONIZED     = 0x00000020;
public static final int VOLATILE         = 0x00000040;
public static final int TRANSIENT        = 0x00000080;
public static final int NATIVE           = 0x00000100;
public static final int INTERFACE        = 0x00000200;
public static final int ABSTRACT         = 0x00000400;
public static final int STRICT           = 0x00000800;
public static boolean isPublic(int mod) {
    return (mod & PUBLIC) != 0;
}
复制代码
  • AccessibleObject微信

    这是一个类,提供了权限管理的功能,例如是否容许在反射中在外部调用一个private方法,获取一个private属性的值,因此method,constructor,field都继承该类,下面这段代码展现了如何在反射中访问一个私有的成员变量,class对象的构造方法不容许对外。网络

private static void setAccessible0(AccessibleObject obj, boolean flag)
    throws SecurityException
{
    if (obj instanceof Constructor && flag == true) {
        Constructor<?> c = (Constructor<?>)obj;
        if (c.getDeclaringClass() == Class.class) {
            throw new SecurityException("Cannot make a java.lang.Class" +
                                        " constructor accessible");
        }
    }
    obj.override = flag;
}

boolean override;

public boolean isAccessible() {
    return override;
}
复制代码

如下为 Field里面经过field.get(原始对象)获取属性值得实现,先经过override作校验,若是没有重载该权限,则须要校验访问权限负载均衡

public Object get(Object obj)
    throws IllegalArgumentException, IllegalAccessException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    return getFieldAccessor(obj).get(obj);
}
复制代码

下面咱们看看如何经过反射修改Field里面属性的值

经过上面的代码,咱们能够看出jdk将Field属性的读取和写入委托给FieldAccessor,那么如何获取FieldAccessor呢

class UnsafeFieldAccessorFactory {
    UnsafeFieldAccessorFactory() {
    }

    static FieldAccessor newFieldAccessor(Field var0, boolean var1) {
        Class var2 = var0.getType();
        boolean var3 = Modifier.isStatic(var0.getModifiers());
        boolean var4 = Modifier.isFinal(var0.getModifiers());
        boolean var5 = Modifier.isVolatile(var0.getModifiers());
        boolean var6 = var4 || var5;
        boolean var7 = var4 && (var3 || !var1);

        if (var3) {
            UnsafeFieldAccessorImpl.unsafe.ensureClassInitialized(var0.getDeclaringClass());

            return (FieldAccessor) ((!var6)
            ? ((var2 == Boolean.TYPE)
            ? new UnsafeStaticBooleanFieldAccessorImpl(var0)
            : ((var2 == Byte.TYPE)
            ? new UnsafeStaticByteFieldAccessorImpl(var0)
            : ((var2 == Short.TYPE)
            ? new UnsafeStaticShortFieldAccessorImpl(var0)
            : ((var2 == Character.TYPE)
            ? new UnsafeStaticCharacterFieldAccessorImpl(var0)
            : ((var2 == Integer.TYPE)
            ? new UnsafeStaticIntegerFieldAccessorImpl(var0)
            : ((var2 == Long.TYPE)
            ? new UnsafeStaticLongFieldAccessorImpl(var0)
            : ((var2 == Float.TYPE)
            ? new UnsafeStaticFloatFieldAccessorImpl(var0)
            : ((var2 == Double.TYPE)
            ? new UnsafeStaticDoubleFieldAccessorImpl(var0)
            : new UnsafeStaticObjectFieldAccessorImpl(var0)))))))))
            : ((var2 == Boolean.TYPE)
            ? new UnsafeQualifiedStaticBooleanFieldAccessorImpl(var0, var7)
            : ((var2 == Byte.TYPE)
            ? new UnsafeQualifiedStaticByteFieldAccessorImpl(var0, var7)
            : ((var2 == Short.TYPE)
            ? new UnsafeQualifiedStaticShortFieldAccessorImpl(var0, var7)
            : ((var2 == Character.TYPE)
            ? new UnsafeQualifiedStaticCharacterFieldAccessorImpl(var0, var7)
            : ((var2 == Integer.TYPE)
            ? new UnsafeQualifiedStaticIntegerFieldAccessorImpl(var0, var7)
            : ((var2 == Long.TYPE)
            ? new UnsafeQualifiedStaticLongFieldAccessorImpl(var0, var7)
            : ((var2 == Float.TYPE)
            ? new UnsafeQualifiedStaticFloatFieldAccessorImpl(var0, var7)
            : ((var2 == Double.TYPE)
            ? new UnsafeQualifiedStaticDoubleFieldAccessorImpl(var0, var7)
            : new UnsafeQualifiedStaticObjectFieldAccessorImpl(var0, var7))))))))));
        } else {
            return (FieldAccessor) ((!var6)
            ? ((var2 == Boolean.TYPE)
            ? new UnsafeBooleanFieldAccessorImpl(var0)
            : ((var2 == Byte.TYPE) ? new UnsafeByteFieldAccessorImpl(var0)
                                   : ((var2 == Short.TYPE)
            ? new UnsafeShortFieldAccessorImpl(var0)
            : ((var2 == Character.TYPE)
            ? new UnsafeCharacterFieldAccessorImpl(var0)
            : ((var2 == Integer.TYPE)
            ? new UnsafeIntegerFieldAccessorImpl(var0)
            : ((var2 == Long.TYPE) ? new UnsafeLongFieldAccessorImpl(var0)
                                   : ((var2 == Float.TYPE)
            ? new UnsafeFloatFieldAccessorImpl(var0)
            : ((var2 == Double.TYPE) ? new UnsafeDoubleFieldAccessorImpl(var0)
                                     : new UnsafeObjectFieldAccessorImpl(var0)))))))))
            : ((var2 == Boolean.TYPE)
            ? new UnsafeQualifiedBooleanFieldAccessorImpl(var0, var7)
            : ((var2 == Byte.TYPE)
            ? new UnsafeQualifiedByteFieldAccessorImpl(var0, var7)
            : ((var2 == Short.TYPE)
            ? new UnsafeQualifiedShortFieldAccessorImpl(var0, var7)
            : ((var2 == Character.TYPE)
            ? new UnsafeQualifiedCharacterFieldAccessorImpl(var0, var7)
            : ((var2 == Integer.TYPE)
            ? new UnsafeQualifiedIntegerFieldAccessorImpl(var0, var7)
            : ((var2 == Long.TYPE)
            ? new UnsafeQualifiedLongFieldAccessorImpl(var0, var7)
            : ((var2 == Float.TYPE)
            ? new UnsafeQualifiedFloatFieldAccessorImpl(var0, var7)
            : ((var2 == Double.TYPE)
            ? new UnsafeQualifiedDoubleFieldAccessorImpl(var0, var7)
            : new UnsafeQualifiedObjectFieldAccessorImpl(var0, var7))))))))));
        }
    }
}

复制代码

以上代码能够发现,经过工厂模式根据field属性类型以及是否静态来获取,为何会有这样的划分呢?

首先,jdk是经过 UNSAFE 类对堆内存中对象的属性进行直接的读取和写入,要读取和写入首先须要肯定属性所在的位置,也就是相对对象起始位置的偏移量,而静态属性是针对类的不是每一个对象实例一份,因此静态属性的偏移量须要单独获取。

其实经过该偏移量咱们能够大体推断出一个实例内每一个属性在堆内存的相对位置,以及分别占用多大的空间,有了位置信息,咱们还须要这个字段的类型,以方便执行器知道读几个字节的数据,而且如何进行解析,目前提供了8大基础类型(char vs Charector)和数组和普通引用类型。

Java虚拟机为了保证每一个对象所占的空间都是8个字节倍数,有时候为了不两个volatile字段存放在同一个缓存行,因此有时候会再某些字段上作空位填充。

如下为UnSafe类的部分代码

public final class Unsafe {
    private static final Unsafe theUnsafe;
    private Unsafe() {
    }

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

    public native int getInt(Object var1, long var2);
    public native void putInt(Object var1, long var2, int var4);
    public native Object getObject(Object var1, long var2);
    public native void putObject(Object var1, long var2, Object var4);
    public native boolean getBoolean(Object var1, long var2);
    public native void putBoolean(Object var1, long var2, boolean var4);
    public native byte getByte(Object var1, long var2);

    public native long objectFieldOffset(Field var1);
@Deprecated
public int fieldOffset(Field var1) {
    return Modifier.isStatic(var1.getModifiers())?(int)this.staticFieldOffset(var1):(int)this.objectFieldOffset(var1);
}
复制代码

而后咱们在来看看经过反射来调用方法

一样,jdk 经过MethodAccessor来进行method的调用,Java虚拟机提供了两种模式来支持method的调用 一个是NativeMethodAccessorImpl 一个是经过 ASM 字节码直接动态生成一个类在invoke方法内部调用目标方法,因为是动态生成因此jdk中没有其源码,但jdk提供了DelegatingMethodAccessorImpl委派模式以方便在运行过程当中能够动态切换字节码模式和native模式,咱们能够看下生成MethodAccessor的代码。

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

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

    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);
}
复制代码

能够看到 JDK 内部经过 numInvocations 判断若是该反射调用次数超过ReflectionFactory.inflationThreshold()则用字节码实现。若是小于该值则采用native实现,native的调用比字节码方式慢不少, 动态实现和本地实现相比执行效率要快20倍,由于动态实现无需通过JAVA,C++再到JAVA的转换,以前在jdk6之前有个工具ReflectAsm就是采用这种方式提高执行效率,不过在jdk8之后,也提供了字节码方式,因为许多反射只须要执行一次,然而动态方式生成字节码十分耗时,因此jdk提供了一个阈值默认15,当某个反射的调用次数小于15的话就走本地实现,大于15则走动态模式,而这个阈值能够在jdk启动参数里面作配置。

反射为何慢

通过以上优化,其实反射的效率并不慢,在某些状况下可能达到和直接调用基本相同的效率,可是在首次执行或者没有缓存的状况下仍是会有性能上的开销,主要在如下方面

  1. Class.forName();会调用本地方法,咱们用到的method和field都会在此时加载进来,虽然会进行缓存,可是本地方法免不了有JAVA到C+=在到JAVA得转换开销。
  2. class.getMethod(),会遍历该class全部的公用方法,若是没匹配到还会遍历父类的全部方法,而且getMethods()方法会返回结果的一份拷贝,因此该操做不只消耗CPU还消耗堆内存,在热点代码中应该尽可能避免,或者进行缓存。
  3. invoke参数是一个object数组,而object数组不支持java基础类型,而自动装箱也是很耗时的。

反射的运用

  • spring doc

spring加载bean的流程基本都用到了反射机制

  1. 获取类的实例 经过构造方法getInstance(静态变量初始化,属性赋值,构造方法);
  2. 若是实现了BeanNameAware接口,则用反射注入bean赋值给属性;
  3. 若是实现了BeanFactoryAware接口,则设置 beanFactory;
  4. 若是实现了ApplicationContextAware,则设置ApplicationContext;
  5. 调用BeanPostProcesser的预先初始化方法;
  6. 若是实现了InitializingBean,调用AfterPropertySet方法;
  7. 调用定制的 init-method()方法 对应的直接 @PostConstruct;
  8. 调用BeanPostProcesser的后置初始化完毕的方法。
  • 序列化

fastjson能够参考ObjectDeserializer的几个实现 JavaBeanDeserializer和ASMJavaBeanDeserializer。

动态代理

jdk提供了一个工具类来动态生成一个代理,容许在执行某一个方法时进行额外的处理。

Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

class HWInvocationHandler implements InvocationHandler{
        //目标对象
        private Object target;
        public HWInvocationHandler(Object target){
            this.target = target;
        }
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("------插入前置通知代码-------------");
            //执行相应的目标方法
            Object rs = method.invoke(target,args);
            System.out.println("------插入后置处理代码-------------");
            return rs;
        }
    }
复制代码

咱们分析下这个方法的实现,首先生成的代理对象,须要实现参数里面声明的全部接口。接口的实现应给委托给InvocationHandler进行处理,invocationHandler里面能够根据method声明判断是否须要作加强,因此所生成的代理类里面必须可以获取到InvocationHandler,在咱们没法知道代理类的具体类型的时候,咱们能够经过反射从构造方法里将InvocationHandler传给代理类的实例。 因此 总的来讲生成代理对象须要两步

  1. 获取代理类的class对象
  2. 经过class对象获取构造方法,经过反射生成代理类的实例,并将InvocationHandler传人
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();

    /*
     * Look up or generate the designated proxy class.
     * 生成代理类
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {

        //获取代理类的构造方法
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //获取代理类的实例,而且将invocationhandler传人
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
       ...
    }
}
复制代码

下面咱们在看下 getProxyClass0 如何获取代理类的class对象,这里idk经过WeakCache来缓存已经生成的class对象,由于生成该class经过字节码生成仍是很耗时,同时为了解决以前因为动态代理生成太多class对象致使内存不足,因此这里经过弱引用WeakReference来缓存所生成的代理对象class,当发生GC的时候若是该class对象没有其余的强引用将会被直接回收。 生成代理类的class在ProxyGenerator的generateProxyClass方法内实现,该方法返回一个byte[]数组,最后经过一个本地方法加载到虚拟机,因此能够看出生成该对象仍是很是耗时的。

//生成字节码数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);
try {
//加载进虚拟机
    return defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
    /*
     * A ClassFormatError here means that (barring bugs in the
     * proxy class generation code) there was some other
     * invalid aspect of the arguments supplied to the proxy
     * class creation (such as virtual machine limitations
     * exceeded).
     */
    throw new IllegalArgumentException(e.toString());
}

private byte[] generateClassFile() {
    this.addProxyMethod(hashCodeMethod, Object.class);
    this.addProxyMethod(equalsMethod, Object.class);
    this.addProxyMethod(toStringMethod, Object.class);
    Class[] var1 = this.interfaces;
    int var2 = var1.length;

    int var3;
    Class var4;
    for(var3 = 0; var3 < var2; ++var3) {
        var4 = var1[var3];
        Method[] var5 = var4.getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method var8 = var5[var7];
            this.addProxyMethod(var8, var4);
        }
    }

    this.methods.add(this.generateConstructor());
...
 }
 //生成一个带invocationhandler参数的构造方法
private ProxyGenerator.MethodInfo generateConstructor() throws IOException {
    ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
    DataOutputStream var2 = new DataOutputStream(var1.code);
    this.code_aload(0, var2);
    this.code_aload(1, var2);
    var2.writeByte(183);
    var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
    var2.writeByte(177);
    var1.maxStack = 10;
    var1.maxLocals = 2;
    var1.declaredExceptions = new short[0];
    return var1;
}
复制代码

上面的流程能够简单概括为

  1. 增长hashcode,equals,toString方法;
  2. 增长全部接口中声明的未实现方法;
  3. 增长一个方法参数为;java/lang/reflect/InvocationHandler的构造方法
  4. 其余静态初始化数据。

动态代理的应用

  1. spring-aop

    spring aop默认基于jdk动态代理来实现,咱们来看下下面这个经典的面试问题

    一个类里面,两个方法A和方法B,方法B上有加注解作事物加强,那么A调用this.B为何没有事物效果?

    由于spring-aop默认基于jdk的动态代理实现,最终执行是经过生成的代理对象的,而代理对象执行A方法和B方法实际上是调用的InvocationHandler里面的加强后的方法,其中B方法是通过InvocationHandler作加强在方法先后增长了事物开启和提交的代码,而真正执行代码是经过methodB.invoke(原始对象) 而A方法的实现内部虽然包含了this.B方法 但实际上是调用了methodA.invoke(原始对象),而这一句代码至关于调用的是原始对象的methodA方法,而这里面的this.B()方法实际上是调用的原始对象的B方法,没有进行过事物加强,而若是是经过cglib作字节码加强,生成这个类的子类,这种调用this.B方法是有事物效果的。

  1. rpc consumer

    有过RMI开发经验的人可能会很熟悉,为何在对外export rmi服务的时候会分别在client和server生成两个stub文件,其中client的文件其实就是用动态代理生成了一个代理类 这个代理类,实现了所要对外提供服务的全部接口,每一个方法的实现其实就是将接口信息,方法声明,参数,返回值信息经过网络发给服务端,而服务端收到请求后经过找到对应的实现而后用反射method.invoke进行调用,而后将结果返回给客户端

    其实其余的RPC框架的实现方式大体和这个相似,只是客户端的代理类,可能不只要将方法声明经过网络传输给服务提供方,也能够作一下服务路由,负载均衡,以及传输一些额外的attachment数据给provider

做者简介

小强,铜板街资金端后台开发工程师,2015年6月加入铜板街。目前负责铜板街资金端清结算相关的开发。

更多精彩内容,请扫码关注 “铜板街科技” 微信公众号。
相关文章
相关标签/搜索