前言
动态代理是Java语言中非常经典的一种设计模式,也是所有设计模式中最难理解的一种,本文将通过一个简单的例子模拟JDK动态代理实现,让你明白动态代理设计模式的本质。
从字面意思来看,代理比较好理解,无非就是代为处理的意思。举个例子,大家到一定年龄还没结婚的话,肯定会被父母催婚,不得已就要去相亲,那么就要去中介所或者找媒婆,那么此处的媒婆就是一个代理。
是的,你没有看错,代理就是这么简单!
静态代理与动态代理的区别:
静态代理中的代理类是需要用户自己写的,这样代理类在程序运行前就已经存在了。
动态代理中的代理类是在程序运行中自动生成的。
动态代理有两种实现方式:
动态代理有两种实现方式:
JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
一、JDK动态代理
JDK动态代理是基于Java的反射机制实现的,主要涉及到java.lang.reflect包中的Proxy和InvocationHandler。
InvocationHandler是一个接口,通过实现这个接口定义一个横切的逻辑!然后通过反射机制调用目标类的方法,这样就能动态的把非业务逻辑和业务逻辑动态的拼接在一起!
- 官方解释:每一个代理实例都会有一个与之关联的处理器,当一个代理实例上的某个方法得到调用的时候,这个方法就会被编码并且会被派发到这个与之关联的处理器上的invoke方法就会得到调用。说白了,就是说,当我们的代理对象上的方法调用的时候,程序的执行流程就会立马让invoke方法得到调用。
proxy则利用InvocationHandler创建代理实例,来间接的调用代理的方法!
- 官方解释:动态代理类是这样的一个class,它是在程序运行的时候去创建一个class,这个class有一个特点,就是你必须要提供一组interface给它,这样做的目的在于创建的代理类就会去实现你提供的这些interface,那么这个代理类就宣告它实现了这些interface接口。这个动态代理其实就是一个Proxy,但是它不会替你做实际性的工作,在创建实例对象的时候,你必须要提供一个Handler,由这个Handler处理器来接替你做实际工作。
编写动态代理类的步骤
- 在创建动态代理类的时候,必须要实现InvocationHandler接口。
- 创建被代理的类及接口。
- 通过Proxy类的静态方法newInstance()方法创建一个代理及代理实例。
- 通过代理调用方法。
下面我们就按照上面举得媒婆的例子来给大家具体实现以下JDK动态代理
1.定义抽象角色
/** * @author WangZhiJun */ public interface Person { /** * 寻找真爱、相亲 */ void findLove(); }
2.定义真实角色
/**相亲对象:美女 * @author WangZhiJun */ public class Girl implements Person{ @Override public void findLove() { System.out.println("目标要求:"); System.out.println("高富帅"); System.out.println("有房有车的"); System.out.println("身高要求180cm以上,体重70kg"); } }
3.定义代理角色
/** 媒婆 * @author WangZhiJun */ public class MeiPo implements InvocationHandler { /** * 被代理对象的引用作为一个成员变量保存下来了 */ private Person target; /** 获取被代理人的个人资料 * @param target 被代理对象 * @return Object */ Object getInstance(Person target){ this.target = target; Class clazz = target.getClass(); System.out.println("被代理对象的class是:"+clazz); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是媒婆,按照你的要求"); System.out.println("开始进行海选..."); System.out.println("------------"); //调用的时候 method.invoke(this.target, args); System.out.println("------------"); System.out.println("选择结束,如果合适的话,就准备办事"); return null; } }
4.定义客户端类及创建动态代理类
/** 客户端 * @author WangZhiJun */ public class TestJDKProxy { public static void main(String[] args) { // 保存生成的代理类的字节码文件,可以得到$Proxy0的字节码文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Person obj = (Person)new MeiPo().getInstance(new Girl()); System.out.println("实际的被代理对象class是:" + obj.getClass()); obj.findLove(); } }
运行结果:
此时你会发现我们实际的代理对象已经被jdk动态的创建了出来,就是$Proxy0,不过后来又删除了,所以我们是没有感知的。
类调用关系:
这样就实现了一个JDK动态代理,但是注意JDK的反射是基于接口的!也就是你的service一定是有接口的不然是不行的!这时候就有个Cglib可以顶上了!
二、Cglib动态代理
Cglib采用了底层的字节码技术,为代理类创建了一个子类来代理它!
因为采用的是继承,所以不能对final修饰的类进行代理。
同样用Cglib的方式实现上述媒婆的例子
开始之前需要导入两个jar包:cglib-x.x.x.jar以及asm-x.x.x.jar
1.定义目标类
/**相亲对象:帅哥 * @author WangZhiJun */ class Boy { void findLove(){ System.out.println("目标要求:"); System.out.println("肤白貌美大长腿"); } }
2.定义代理类
/** 媒婆 * @author WangZhiJun */ public class MeiPo implements MethodInterceptor{ /**这里并没有持有被代理对象的引用 * @param clazz 要继承的类 * @return Object */ Object getInstance(Class clazz){ Enhancer enhancer = new Enhancer(); //这一步就是告诉cglib,生成的子类需要继承哪个类 enhancer.setSuperclass(clazz); //设置回调 enhancer.setCallback(this); //第一步、生成源代码 //第二步、编译成class文件 //第三步、加载到JVM中,并返回被代理对象 return enhancer.create(); } /**同样是做了字节码重组这样一件事情 * 对于使用API的用户来说,是无感知 * @param obj * @param method * @param args * @param proxy * @return * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("我是媒婆,按照你的要求"); System.out.println("开始进行海选..."); System.out.println("------------"); //这个obj的引用是由CGLib给我们new出来的 //cglib new出来以后的对象,是被代理对象的子类(继承了我们自己写的那个类) //OOP, 在new子类之前,实际上默认先调用了我们super()方法的, //new了子类的同时,必须先new出来父类,这就相当于是间接的持有了我们父类的引用 //子类重写了父类的所有的方法 //我们改变子类对象的某些属性,是可以间接的操作父类的属性的 proxy.invokeSuper(obj, args); System.out.println("------------"); System.out.println("选择结束,如果合适的话,就准备办事"); return null; } }
3.客户端
/** 客户端 * @author WangZhiJun */ public class TestCglibProxy { public static void main(String[] args) { //CGLib的动态代理是通过生成一个被代理对象的子类,然后重写父类的方法 //生成以后的对象,可以强制转换为被代理对象(也就是用自己写的类) //子类引用赋值给父类 Boy obj = (Boy)new MeiPo().getInstance(Boy.class); obj.findLove(); } }
运行结果:
三、cglib和jdk动态代理的区别
1.jdk动态代理实现了被代理对象的接口,cglib是继承了被代理的对象【通过字节码技术创建这个类的子类,实现动态代理】
2.都是在运行期间,jdk是直接写class字节码,cglib使用asm框架写字节码,实现更复杂
3.java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法时用InvokeHandler,cglib是通过继承要被代理类的机制
4.jdk动态代理要求
- 动态代理类实现InvocationHandler接口【必须】
- 被代理的类必须要实现一个接口
- 使用Proxy.newProxyInstance产生代理对象
cglib要求
- jdk动态代理类库中已有,而cglib必须加入第三方依赖(cglib、asm)
- jdk调用代理方法,通过反射机制,cglib通过fastclass机制直接调用方法,这方面cglib执行效率更高。
5.jdk调用代理方法,通过反射机制,cglib通过fastclass机制直接调用方法,这方面cglib执行效率更高。
简单介绍一下asm以及fastclass机制
fastclass机制
cglib执行代理方法效率快的原因:采用fastclass机制
简单来说就是:为代理类和被代理类各生产一个class,该class会为代理类或被代理类分配一个index(int),该index当做为一个入参,fastclass可以直接定位到要调用的方法直接进行调用,省去反射调用【反射效率较低】,即对一个类的方法建立索引,通过索引直接调用相应方法。ASM
位于字节码之上、直接操作字节码的框架,ASM是一个java字节码操控框架,可以以二进制的形式修改已有类,ASM可以直接生成二进制class文件,也可以在类被加载如java虚拟机之前改变类的行为,asm从类文件中读入信息,甚至可以根据用户要求生成新类
速率上的区别
- 对于创建实例:jdk更快,相比之下cglib创建实例的过程比较繁琐
- 对于方法执行效率:cglib更快,基于asm操作字节码技术和fastclass机制(通过索引调用方法),比基于反射的jdk动态代理来的快。
不过高版本的jdk有了很大的提升,在此不做研究。
Spring的代理选择
当bean实现接口时,spring使用jdk动态代理
当bean没实现接口,spring使用cglib
可以通过配置强制走cglib,<aop:aspectj-autoproxy proxy-target-class="true"/>,如果是基于注解的方式,比如说springboot项目的启动类,可以使用@EnableAspectJAutoProxy(proxyTargetClass=true)注解来强制使用cglib
在此动态代理就介绍完毕了,想看静态代理的同学可以移步【设计模式】——代理模式(静态)
当然了,这里只是介绍了动态代理是什么以及使用,但是我们还是不知道代理类底层是怎么帮我们实现动态生成代理类的,我们下文再细说!