java动态代理

一、作用:

     不改变源码的基础上,对已有的方法增强。(是AOP思想的实现技术)

二、动态代理的特点:

  1. 字节码随用随创建,随用随加载。
  2. 它与静态代理的差别在于此。因为静态代理是字节码一上来就创建好,并完成加载。
  3. 装饰者模式就是静态代理的一种体现。

三、分类:

1. 基于接口的动态代理(JDK动态代理):

要求: 被代理类最少实现一个接口
提供者: JDK官方
涉及的类: Proxy
创建代理对象的方法: newProxyInstance(ClassLoader, Class[],   InvocationHandler)
参数的含义:
     ClassLoader: 类加载器,和被代理对象使用相同的类加载器。代理对象.getClass().getClassLoader();
     Class[]: 字节码数组.被代理类实现的接口。(要求代理代理对象和被代理对象实现相同接口)。代理对象.getClass().getInterfaces();
     InvocationHandler: 是一个接口,就是用于我们提供增强代码的,我们一般都是写一个该接口的实现类实现类可以是匿名内部类,也可以不是。他的含义是:如何代理。具体的做法,由用户自己实现。
该参数使用了策略模式:使用要求 :
     数据已经存在;目的明确;达成目标的过程就是策略

IActor actor = new Actor();

IActor actor = (ICustomerDao)Proxy.newProxyInstance(actor .getClass().getClassLoader(),
        actor .getClass().getInterfaces(), new InvocationHandler() {
            //执行被代理对象的任何方法都会经过此方法;
            //该方法有拦截的功能
            //方法参数:
            //    Object o:代理对象的引用,不怎么用
            //    Method method:当前执行的方法
           //    Object[] objects:当前执行方法所需的参数
           //返回值:当前执行方法的返回值
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                return null;
            }
            });

InvocationHandler的作用:
     动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。如下图所示:
java动态代理_第1张图片
在这种模式之中: 代理Proxy 和代理类应该实现相同的功能,这一点相当重要。
在面向对象的编程之中,如果我们想要约定Proxy 和RealSubject可以实现相同的功能,有两种方式:
     1. 一个比较直观的方式,就是定义一个功能接口,然后让Proxy 和代理类来实现这个接口。
     2. 还有比较隐晦的方式,就是通过继承。因为如果Proxy 继承自代理类,这样Proxy则拥有了RealSubject的功能,
     Proxy还可以通过重写RealSubject中的方法,来实现多态。
其中JDK中提供的创建动态代理的机制,是以a 这种思路设计的,而cglib 则是以b思路设计的。
JDK的动态代理创建机制----通过接口
    1.获取 代理类上的所有接口列表;
    2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ;
    3. 根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码;
    4 . 将对应的字节码转换为对应的class 对象;
    5. 创建InvocationHandler 实例handler,用来处理Proxy所有方法调用;
    6. Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象
JDK通过 java.lang.reflect.Proxy包来支持动态代理,一般情况下,我们使用下面的newProxyInstance方法:

//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
IActor actorPro = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(),
        actor.getClass().getInterfaces(), new InvocationHandler() {
         @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                
                re = method.invoke(;)
                
                return null;
                
            }
            });

如果把最后的return 改为 return method(proxy, args);
invoke的对象不是actor ,而是proxy,根据上面的说明猜猜会发生什么?

     是的,会不停地循环调用。因为proxy是代理类的对象,当该对象方法被调用的时候,会触发InvocationHandler,而InvocationHandler里面又调用一次proxy里面的对象,所以会不停地循环调用。并且,proxy对应的方法是没有实现的。所以是会循环的不停报错
在newProxyInstance()发放中有这样几段

java动态代理_第2张图片
java动态代理_第3张图片
     其实大概就是把接口复制出来,通过这些接口和类加载器,拿到这个代理类cl。然后通过反射的技术复制拿到代理类的构造函数(这部分代码在Class类中的getConstructor0方法),最后通过这个构造函数new个一对象出来,同时用InvocationHandler绑定这个对象。

而对于InvocationHandler,我们需要实现下列的invoke方法:
     在调用代理对象中的每一个方法时,在代码内部,都是直接调用了InvocationHandler 的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法。
java动态代理_第4张图片
生成动态代理类的字节码并且保存到硬盘中:

System.out.println(proxy.getClass());
输出结果:class com.sun.proxy.$Proxy0

JDK提供了sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 底层方法来产生动态代理类的字节码:

 /* 
     * 将根据类信息 动态生成的二进制字节码保存到硬盘中, 
     * 默认的是clazz目录下 
         * params :clazz 需要生成动态代理类的类 
         * proxyName : 为动态生成的代理类的名称 
         */  
    public static void generateClassFile(Class clazz,String proxyName)  
    {  
        //根据类信息和提供的代理类名称,生成字节码  
                byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());   
        String paths = clazz.getResource(".").getPath();  
        System.out.println(paths);  
        FileOutputStream out = null;    
          
        try {  
            //保留到硬盘中,根目录+文件名  
            out = new FileOutputStream(paths+proxyName+".class");    
            out.write(classFile);    
            out.flush();    
        } catch (Exception e) {    
            e.printStackTrace();    
        } finally {    
            try {    
                out.close();    
            } catch (IOException e) {    
                e.printStackTrace();    
            }    
        }    
    }  

这样将在CustomerDaoImp.class 同级目录下产生Proxy.class文件

仔细观察可以看出生成的动态代理类有以下特点:
     1.继承自 java.lang.reflect.Proxy,实现了 代理类实现的接口;
     2.类中的所有方法都是final 的;
     3.所有的方法功能的实现都统一调用了InvocationHandler的invoke()方法。

生成代理对象的方法:
     在生成代理对象前执行这个方法:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

在class文件的同级目录下会生成com.sun.proxy.$Proxy0文件(.class)
java动态代理_第5张图片
生的代理类源文件:

package com.sun.proxy;
import com.aiun.core.IActor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements IActor {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void actionComplex(int var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final Integer actionSimple(int var1) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.aiun.core.IActor").getMethod("actionComplex", Integer.TYPE);
            m3 = Class.forName("com.aiun.core.IActor").getMethod("actionSimple", Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

System.out.println(dao.getClass().getName()); 输出的结果为:com.sun.proxy.$Proxy0

动态代理的缺点: JDK动态代理有一个限制,即他只能为接口创建代理实例。

2. 基于子类的动态代理:

     CGLIB采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术,拦截所有父类方法的调用并顺势织入横切逻辑。
   要求: 被代理类不能是最终类。被代理类不能被final修饰。因为被final修饰的类不能被继承。
   提供者: 第三方CGLIB
   涉及的类: Enhancer
   创建代理对象的方法: creat(Class, Callback);
   参数的含义:
     Class: 被代理对象的字节码
     Callback: 如何代理。他和InvocationHandler的作用是一样的。他也是一个接口。我们一般使用该接口的子接口MethodInterceptor.
在使用时创建该接口的匿名内部类。

Actor actor = new Actor();
Enhancer.create(actor.getClass(), new MethodInterceptor() {
    //和JDK动态代理的invoke方法作用是一样的
    //方法参数:
    //    methodProxy:当前执行方法的代理对象  
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        return null;
    }
});

采用CGLIB技术编一个可以为可以为任何类创建织入的代理对象的代理创建器:

public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class klass) {
        enhancer.setSuperclass(klass); //设置需要创建的子类
        enhancer.setCallback(this);
        return enhancer.create();  //通过字节码技术动态的创建子类实例
    }
    //拦截父类所有方法的调用
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        System.out.println("前置增强");
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("后置增强");
        return result;
    }
}

     用户可以通过getProxy(Class klass);方法为一个类创建动态代理对象,该代理对象通过扩展klass实现代理。intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy);是CGLIB定义的Interceptor接口方法,他拦截所有目标类方法的调用。obj表示目标了的实例;method为目标类方法的反射对象;args为方法的动态入参;proxy为代理类实例。

下面通过CglibProxy为Actor类创建代理对象,并测试代理对象的方法:

public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        //通过动态生成子类的方式创建代理类
        IActor actor = (IActor)proxy.getProxy(Actor.class);
        actor.actionSimple(12);
    }

通过CglibProxy为actor动态创建了一个织入性能的代理对象,并调用代理类的业务方法,输出结果:

前置增强
简单12
后置增强 

代理类的名字变为:

com.aiun.core.Actor$$EnhancerByCGLIB$$d1978608@108c4c35;
这个特殊的类就是CGLIB为actor动态创建的子类。

缺点: 不能对目标类中的final或private方法进行代理

代理知识小结

     研究表明:CGLIB所创建的动态代理对象的性能依旧比JDK所创建的动态代理对象的性能高不少(大概10倍)。但CGLIB在创建代理对象时所花费的时间却比JDK动态代理多(大概8倍)。对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLIB动态代理技术,反之则适合采用JDK动态代理技术。

动态代理的使用场景

     动态代理的好处我们从例子就能看出来,它比较灵活,可以在运行的时候才切入改变类的方法,而不需要预先定义它。
这种场景的使用是动态代理最佳的落地点,可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。

你可能感兴趣的:(Java)