JDK 1.3以后,Java提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在我们终于发现动态代理是实现AOP的绝好底层技术。
JDK的动态代理主要涉及到java.lang.reflect包中的两个类:
- Proxy
- InvocationHandler。
其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
一、JDK的动态代理
使用JDK创建代理有一个限制,即它只能为接口创建代理实例。下面写个小例子来验证一下。
1、被代理的接口和实现类
1 | package hello.proxy; |
输出:
1 | something |
这是一个很简单的逻辑,如果需要在这个业务逻辑上面增加其他的横切逻辑,并且不需要在业务逻辑中增加代码,你们就需要用到动态代理。
2、Proxy与InvocationHandler
改动代码如下
1 | import java.lang.reflect.InvocationHandler; |
输出:
1 | something |
可以发现,在不改动原来代码的基础上实现了横切逻辑的实现。涉及的两个关键的类就是Proxy与InvocationHandler。
通过Proxy.newProxyInstance()方法可以创建动态代理,这个方法需要三个参数:
- 类加载器:通过可以从已经被加载的对象中获取其类加载器并传递给它
- 希望该代理实现的接口列表(只能是接口,不能是类或抽象类)
- InvocationHandler接口的一个实现
动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以请求转发。
调用处理器就是InvocationHandler接口,该接口定义了一个 invoke(Object proxy, Method method, Object[] args)的方法:
- proxy是最终生成的代理实例,一般不会用到;
- method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用;
- args是通过被代理实例某一个方法的入参,在方法反射调用时使用。
二、CGLib
1、CGLib
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
1 | import java.lang.reflect.Method; |
输出:
1 | something |
有一点需要注意的就是CGLib需要额外的导入jar包,第一次导入cglib-2.1.3.jar后发现运行有异常,解决办法就是下载并导入cglib-nodep-2.1_3.jar然后删除cglib-2.1.3.jar,这样就可以正确运行了。
2、JDK与CGLib
JDK动态代理所创建的代理对象,在JDK 1.3下,性能强差人意。虽然在高版本的JDK中,动态代理对象的性能得到了很大的提高,但是有研究表明,CGLib所创建的动态代理对象的性能依旧比JDK的所创建的代理对象的性能高不少(大概10倍)。但CGLib在创建代理对象时所花费的时间却比JDK动态代理多(大概8倍),所以对于singleton的代理对象或者具有实例池的代理,因为无须频繁创建代理对象,所以比较适合用CGLib动态代理技术,反之适合用JDK动态代理技术。
值得一提的是,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final方法进行代理。