Java反射机制和动态代理详解

反射

概念

反射机制:

  • 指的是可以于运行时加载、探知、使用编译期间完全未知的类。
  • 程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;
Class c = Class.forName("com.abc.test.User");
  • 加载完类之后,在堆内存中,就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象地称之为:反射。

Class.forName 使用的类加载器是调用者的类加载器。

Class类

一个类被加载之后,JVM 会创建一个对应该类的 Class 对象,类的整个结构信息会放在相应的 Class 对象中。

如果多次执行 forName 等加载类的方法,类只会被加载一次;一个类只会形成一个 Class对象,无论执行多少次加载类的方法,获得的 Class 都是一样的。

使用

/*
 * 获取Class对象的三种方式
 */
Class clazz1 = Class.forName("className");
Class clazz2 = obj.getClass();//obj为某一个类的实例,getClass()获取这个实例
Class clazz3 = Proson.class//Person为可以加载到的类名

/*
 * 使用
 */
Object obj1=c.newInstance();//创建一个c的实例
Constructor constructor = clazz1.getConstructor(String.class, Integer.class);//通过class对象获取一个参数类型对应的构造器
Object obj2 = constructor.newInstance("name",22);//通过构造器对象获取实例对象
Method method1 = obj2.getMethod("foo",String.class);//通过class对象获取一个方法对象
method1.invoke(obj2,"name");//通过方法对象调用

用途

  • 动态加载类、动态获取类的信息(属性、方法、构造器)
  • 动态构造对象
  • 动态构造类和对象的任意方法、构造器
  • 动态调用和处理属性
  • 获取泛型信息
  • 处理注解

性能

反射带来灵活性的同时,也有降低程序执行效率的弊端。

很多本来可以在编译阶段优化和验证的东西必须要等到运行时才能处理。

setAccessible 方法不仅可以标记某些私有的属性方法为可访问的属性方法,并且可以提高程序的执行效率。

实际上是启用和禁用访问安全检查的开关。如果做检查就会降低效率;关闭检查就可以提高效率。

反射调用方法比直接调用要慢大约 30 倍,如果跳过安全检查的话比直接调用要慢大约 7 倍

开启和不开启安全检查对于反射而言可能会差 4 倍的执行效率。

为什么慢?
1. 验证等防御代码过于繁琐,这一步本来在 link 阶段,现在却在计算时进行验证
2. 产生很多临时对象,造成 GC 与计算时间消耗
3. 由于缺少上下文,丢失了很多运行时的优化,比如 JIT(它可以看作 JVM 的重要评测标准之一)

当然,现代 JVM 也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实现 JIT 优化,所以反射不一定慢。

动态代理

代理的定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用

静态代理:代理类是在编译时就实现好的。也就是说 Java 编译完成后代理类是一个实际的class 文件。代理类和被代理类都实现业务接口,代理类通过组合关系使用被代理对象。

动态代理:代理类是在运行时生成的。也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时动态生成的类字节码,并加载到 JVM 中。

代理的目的:(1) 通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;(2) 通过代理对象对原有的业务增强。

JDK动态代理

概述和使用

静态代理的缺陷:业务的拓展,实现接口的增多。开放-封闭原则:对外拓展开放,对修改关闭。

JDK 动态代理是由 Java 内部的反射机制+动态生成字节码 来实现的,cglib 动态代理底层则是借助 asm 来实现的。总的来说,反射机制在生成类的过程中比较高效,而 asm 在生成类之后的相关执行过程中比较高效(可以通过将 asm 生成的类进行缓存,这样解决 asm 生成类过程低效问题)。还有一点必须注意:JDK 动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,JDK 动态代理不能应用。由此可以看出,JDK 动态代理有一定的局限性,cglib 这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

前者必须基于接口,后者不需要接口,是基于继承的,但是不能代理 final 类和 final 方法;JDK 采用反射机制调用委托类的方法,CGLIB 采用类似索引的方式直接调用委托类方法;前者效率略低于后者效率,CGLIB 效率略高(不是一定的)

Proxy 类(代理类)的设计用到代理模式的设计思想,Proxy 类对象实现了代理目标的所有接口,并代替目标对象进行实际的操作。代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截。所以,Proxy 应该包括一个方法拦截器,来指示当拦截到方法调用时作何种处理。InvocationHandler 就是拦截器的接口。

要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这个方法有三个参数:
+ 一个类加载器
+ 一个Class对象数组,每个元素都是需要实现的接口
+ 一个调用处理器(InvocationHandler)

public class RentInvocationHandler implements InvocationHandler {
    private Object object;//被代理的对象,实际的方法执行者

    public  RentInvocationHandler(Object object) {
        this.object = object;
    }

    //通过Proxy获取动态代理的对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this)
    }


    /**
     * @param proxy:代理类
     * @param method:代理类调用处理程序的方法对象
     * @param args:方法的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("使用动态代理做的一些增强的操作......");
        //第一个参数表示真实对象
        //第二个参数表示参数
        Object result = method.invoke(object, args);
        return result;
    }
}

使用的时候就直接传要代理的对象构造代理对象就行了。

原理

Proxy.newProxyInstance:

public static Object newProxyInstance(ClassLoader loader,
                                        Class<?>[] interfaces,
                                        InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

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

    /*
        * Invoke its constructor with the designated invocation handler.
        */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        // 获取代理对象的构造方法(constructorParams就是InvocationHandler的Class)
        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;
                }
            });
        }
        // 生成代理类的实例并把 InvocationHandlerImpl 的实例传给它的构造方法
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

可以看出getProxyClass0应该就是生成代理类字节码的关键。

private static Class<?> getProxyClass0(ClassLoader loader,
                                        Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);

    expungeStaleEntries();

    Object cacheKey = CacheKey.valueOf(key, refQueue);

    // lazily install the 2nd level valuesMap for the particular cacheKey
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,
                                valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }

    // create subKey and retrieve the possible Supplier<V> stored by that
    // subKey from valuesMap
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;

    while (true) {
        if (supplier != null) {
            // supplier might be a Factory or a CacheValue<V> instance
            V value = supplier.get();
            if (value != null) {
                return value;
            }
        }
        // else no supplier in cache
        // or a supplier that returned null (could be a cleared CacheValue
        // or a Factory that wasn't successful in installing the CacheValue)

        // lazily construct a Factory
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }

        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
                // successfully installed Factory
                supplier = factory;
            }
            // else retry with winning supplier
        } else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                // successfully replaced
                // cleared CacheEntry / unsuccessful Factory
                // with our Factory
                supplier = factory;
            } else {
                // retry with current supplier
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

最终跟随源码会找到ProxyGenerator.generateClassFile方法,实际上就是根据class文件规则改写字节码流。

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);
        }
    }

    Iterator var11 = this.proxyMethods.values().iterator();

    List var12;
    while(var11.hasNext()) {
        var12 = (List)var11.next();
        checkReturnTypes(var12);
    }

    Iterator var15;
    try {
        this.methods.add(this.generateConstructor());
        var11 = this.proxyMethods.values().iterator();

        while(var11.hasNext()) {
            var12 = (List)var11.next();
            var15 = var12.iterator();

            while(var15.hasNext()) {
                ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                this.methods.add(var16.generateMethod());
            }
        }

        this.methods.add(this.generateStaticInitializer());
    } catch (IOException var10) {
        throw new InternalError("unexpected I/O Exception", var10);
    }

    if (this.methods.size() > 65535) {
        throw new IllegalArgumentException("method limit exceeded");
    } else if (this.fields.size() > 65535) {
        throw new IllegalArgumentException("field limit exceeded");
    } else {
        this.cp.getClass(dotToSlash(this.className));
        this.cp.getClass("java/lang/reflect/Proxy");
        var1 = this.interfaces;
        var2 = var1.length;

        for(var3 = 0; var3 < var2; ++var3) {
            var4 = var1[var3];
            this.cp.getClass(dotToSlash(var4.getName()));
        }

        this.cp.setReadOnly();
        ByteArrayOutputStream var13 = new ByteArrayOutputStream();
        DataOutputStream var14 = new DataOutputStream(var13);

        try {
            var14.writeInt(-889275714);
            var14.writeShort(0);
            var14.writeShort(49);
            this.cp.write(var14);
            var14.writeShort(this.accessFlags);
            var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
            var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
            var14.writeShort(this.interfaces.length);
            Class[] var17 = this.interfaces;
            int var18 = var17.length;

            for(int var19 = 0; var19 < var18; ++var19) {
                Class var22 = var17[var19];
                var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
            }

            var14.writeShort(this.fields.size());
            var15 = this.fields.iterator();

            while(var15.hasNext()) {
                ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                var20.write(var14);
            }

            var14.writeShort(this.methods.size());
            var15 = this.methods.iterator();

            while(var15.hasNext()) {
                ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                var21.write(var14);
            }

            var14.writeShort(0);
            return var13.toByteArray();
        } catch (IOException var9) {
            throw new InternalError("unexpected I/O Exception", var9);
        }
    }
}

CGLIB动态代理

CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。

CGLIB 的核心类:
+ net.sf.cglib.proxy.Enhancer – 主要的增强类
+ net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是 Callback 接口的子接口,需要用户实现
+ net.sf.cglib.proxy.MethodProxy – JDK 的 java.lang.reflect.Method 类的代理类,可以方便的实现对源对象方法的调用,如使用:

Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。

net.sf.cglib.proxy.MethodInterceptor 接口是最通用的回调(callback)类型,它经常被基于代理的 AOP 用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法 public Object intercept( Object object, java.lang.reflect.Method method,Object[] args, MethodProxy proxy) throws Throwable;

第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用 java.lang.reflect.Method 对象的一般反射调用,或者使用net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy 通常被首选使用,因为它更快。

public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy
    methodProxy) throws Throwable {
        System.out.println("++++++before " + methodProxy.getSuperName() +
        "++++++");
        System.out.println(method.getName());
        Object o1 = methodProxy.invokeSuper(o, args);
        System.out.println("++++++before " + methodProxy.getSuperName() +
        "++++++");
        return o1;
    }
}
public class Main {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(cglibProxy);
        UserService o = (UserService)enhancer.create();
        o.getName(1);
        o.getAge(1);
    }
}

我们通过 CGLIB 的 Enhancer 来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用 create()方法得到代理对象,对这个对象所有非 final 方法的调用都会转发给MethodInterceptor.intercept()方法,在 intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;通过调用 MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是 HelloConcrete 的具体方法。CGLIG 中MethodInterceptor 的作用跟 JDK 代理中的 InvocationHandler 很类似,都是方法调用的中转站。

注意:对于从 Object 中继承的方法,CGLIB 代理也会进行代理,如 hashCode()、equals()、toString()等,但是 getClass()、wait()等方法不会,因为它是 final 方法,CGLIB 无法代理。既然是继承就不得不考虑 final 的问题。我们知道 final 类型不能有子类,所以 CGLIB 不能代理 final 类型。

final 方法是不能重载的,所以也不能通过 CGLIB 代理,遇到这种情况不会抛异常,而是会跳过 final 方法只代理其他方法。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/java%e5%8f%8d%e5%b0%84%e6%9c%ba%e5%88%b6%e5%92%8c%e5%8a%a8%e6%80%81%e4%bb%a3%e7%90%86%e8%af%a6%e8%a7%a3/

(0)
彭晨涛彭晨涛管理者
上一篇 2020年2月15日
下一篇 2020年2月16日

相关推荐

发表回复

登录后才能评论