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方法,无法进行代理

原文地址:https://www.cnblogs.com/zhaosq/p/9876779.html