JDK和Cglib动态代理
一:动态代理的引入
Spring中的两大核心之一的 AOP是基于 动态代理实现的,简单来说就是面向切面编程.Spring AOP的实现对于接口来说就是使用的JDK的动态代理来实现的,而对于类的代理使用CGLIB来实现。
二动态代理的概念
代理类在程序运行时创建的代理方式被成为 动态代理.也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。代理模式最大的特点就是代理类和实际业务类实现同一个接口(或继承同一父类),代理对象持有一个实际对象的引用,外部调用时操作的是代理对象,而在代理对象的内部实现中又会去调用实际对象的操作,Java动态代理其实内部也是通过Java反射机制来实现的,即已知的一个对象,然后在运行时动态调用其方法,这样在调用前后作一些相应的处理。
三:JDK动态代理的原理
JDK的动态代理,就是在程序运行的过程中,根据被代理的接口来动态生成代理类的class文件,并加载运行的过程。代理的目的是调用目标方法时可以转而执行InvocationHandler拦截器的invoke方法以及类的反射,实际上spring aop也是在这里做文章。这也是典型的代理模式 。
四:JDK动态代理的实现
1>首先有一个实现了InvocationHandler接口的类
package com.svse.daili.jdk_cglib;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*JDK的动态代理对象
*前提:被代理的类实现了接口才可以进行代理
*原理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用invokeHandler的invoke()方法来处理
*InvocationHandler其实底层为一个拦截器
*/
public class JDKProxy implements InvocationHandler
{
private Object targetObject;//需要代理的目标对象
public JDKProxy(Object targetObject){
this.targetObject=targetObject;
}
public Object proxyObject()
{
// 三个参数 (被加载的类、代理的接口、代理的目标对象)
// newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), this);//返回代理对象
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
checkPopedom();
Object rs=null;
rs=method.invoke(targetObject, args);
return rs;
}
private void checkPopedom(){
System.out.println("...模拟检测权限 checkPopedom()");
}
}
2>定义一个接口
package com.svse.daili.jdk_cglib;
public interface UserManager
{
public void addUser(String id,String password);
public void delUser(String id);
}
3>定义一个实现该接口的类
package com.svse.daili.jdk_cglib;
public class UserManagerImpl implements UserManager
{
@Override
public void addUser(String id, String password)
{
System.out.println("...调用了UserManagerImpl的addUser()方法");
}
@Override
public void delUser(String id)
{
System.out.println("...调用了UserManagerImpl的delUser()方法");
}
}
4>测试类
package com.svse.daili.jdk_cglib;
/**
* 测试代理对象
* @author Administrator
*
*/
public class JDKProxyTest
{
public static void main(String[] args)
{
JDKProxy jdkProxy=new JDKProxy(new UserManagerImpl());
UserManager proxyManager=(UserManager) jdkProxy.proxyObject();//返回代理对象
System.out.println("-----------------JDK动态代理----------------");
proxyManager.addUser("", "");
proxyManager.delUser("");
}
}
5>测试结果
6>疑问?为什么jdk动态代理必须基于接口 原因如下:
1)、生成的代理类继承了Proxy,由于java是单继承,所以只能实现接口,通过接口实现
2)、从代理模式的设计来说,充分利用了java的多态特性,也符合基于接口编码的规范
当然,jdk在生成代理的参数中也说明了,需要传入对应接口。
五:Cglib动态代理的原理
利用字节码处理框架ASM开源包,主要是对指定的类生成一个子类,覆盖其中的所有方法,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。该类或方法不能声明为final。
六:Cglib动态代理的实现
1>创建一个类并实现MethodInterceptor接口
package com.svse.daili.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* cglib动态代理 (需要引入asm.jar 和cglib.jar)
* @author Administrator
*
*/
public class CglibProxy implements MethodInterceptor
{
private Object targetObject;
public CglibProxy(Object obj){
this.targetObject=obj;
}
public Object createProxyObject(){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(targetObject.getClass());
enhancer.setCallback(this);
Object proxyObj=enhancer.create();
return proxyObj;//返回代理对象
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable
{
if("sayHello".equals(method.getName())){
checkPopedom();
}
Object obj=null;
obj=method.invoke(targetObject, args);
return obj;
}
private void checkPopedom(){
System.out.println("...模拟检测权限 checkPopedom()");
}
}
2>创建一个代理类
package com.svse.daili.cglib;
public class StudentManager {
public void sayHello(){
System.out.println("...调用了StudentManager类的sayHello()方法");
}
}
3>测试类
package com.svse.daili.cglib;
public class CglixProxyTest
{
//过程中遇到的问题:如果cglib.jar 版本为3.0以上,可能会报类找不到,原因跟asm.jar版本冲突
public static void main(String[] args) {
CglibProxy cglibProxy=new CglibProxy(new StudentManager());
StudentManager studentProxy=(StudentManager) cglibProxy.createProxyObject();
System.out.println("-----------------Cglix动态代理----------------");
studentProxy.sayHello();
}
}
4>测试结果
七:jdk代理与cglib代理的区别
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对普通类
(2)CGLIB是针对普通类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类
(3)JDK代理是不需要以来第三方的库,只需要JDK环境就可以进行代理,CGLib 必须依赖于CGLib的类库(asm和cglib)
(4)JDK代理类必须实现InvocationHandler接口 ,通过Proxy.newProxyInstance产生代理对象;而CGLIB是实现MethodInterceptor接口,通过cglibProxy.createProxyObject()产生代理对象
(5)在AOP中,如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,也可以强制使用CGLIB实现AOP;如果目标对象没有实现了接口,必须采用CGLIB库进行代理,spring会自动在JDK动态代理和CGLIB之间转换
八:二者优缺点分析
(1) 使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
(2)CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。因为是覆盖其中的所有方法,所以目标类和方法不能声明为final类型。
(3)从执行效率上看,Cglib动态代理效率较高。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
(4) CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib是采用动态创建子类的方法,对于final方法,无法进行代理