打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
静态代理与动态代理、CGLIB

代理:无论哪种代理,都存在代理对象和目标对象两个模型,所以目标对象就是我们要生成的代理对象代理的那个对象。

 

一、包装的动态代理

 

接口:

public interface Animal {

   void eat(String food);

   String type();

}

public interface Primate {

   void think();

}

实现类:

public class Monkey implements Animal,Primate{

   public void eat(String food) {

      System.out.println("the food is "+food+" !");

     

   }

   public String type() {

      String type="哺乳动物";

      System.out.println(type);

      return type;

   }

   public void think() {

      System.out.println("思考");

   }

}

 

包装类的代理:

public class AnimalWrapper implements Animal{

   private Animal animal;

   public AnimalWrapper(Animal animal){

      this.animal=animal;

   }

  

   public void eat(String food) {

      System.out.println("+++Wrapped Before!+++");

      animal.eat(food);

      System.out.println("+++Wrapped After!+++");

   }

 

   public String type() {

      System.out.println("+++Wrapped Before!+++");

      String type=animal.type();

      System.out.println("+++Wrapped After!+++");

      return type;

   }

 

}

 

可以看到,AnimalWrapper完成了对Animal所有子类的代理,在代理方法中,可以加入一些自己的额外逻辑,Spring的前置、后置、环绕方法通知,都可以通过这种方式有限的模拟出来。

缺点就是,当Animal接口新增了新的方法,包装类也必须新增方法,而且一个包装类实际上只能对应一个接口。

 

二、继承的代理

public class ExtendProxyTest extends Monkey{

 

   public static void main(String args[]){

   }

  

   public void eat(String food){

      System.out.println("+++warpped brfore+++");

      super.eat(food);

      System.out.println("+++warpped after+++");

     

   }

  

   public String type(){

      System.out.println("+++warpped brfore+++");

      String type=super.type();

      System.out.println("+++warpped after+++");

      return type;

   }

}

这种方式最简单,不过不能实现对Animal所有子类的代理,与包装的模式相比,大大缩小了代理范围。

 

三、动态代理

  

/**

 * 基于reflect.Proxyreflect.InvocationHandler两个类来完成的,使用java反射机制

 *1.

Onbject proxy=Proxy.newProxyInstance(定义代理对象的类加载器,

   要代理的目标对象的归属接口数组, 回调接口InvacationHandler);

 *

 *2. Onbject proxy=Proxy.newProxyInstance(定义代理对象的类加载器,

            要代理的目标对象的归属接口数组);

   proxy=proxClass.getConstructor(

new Class[]{InvocationHandler.class}).

newInstance(new 回调接口InvocationHandler);

          

   缺点:不能对类进行代理,只能对接口进行代理。因为生成的代理类$Proxy这个类继承了ProxyJava

   继承不允许出现多个父类*

 */

public class DynamicProxy {

 

   public static void main(String args[]) throws {

      /*

       * 使用第一种方法来创建代理对象

       */

      Object proxy=Proxy.newProxyInstance(

   Monkey.class.getClassLoader(),

   Monkey.class.getInterfaces(),

         new AnimalInvocationHandler(new Monkey()));

      /*

       * 代理对象调用的时候,会调用回调的invoke方法,Method.args是调用invoke的时候传入的

       */

      Animal animal=(Animal)proxy;

      animal.eat("橡胶");

      animal.type();

     
      /*

       第2中方式       */

   Class<?> proxClass=Proxy.getProxyClass(

Monkey.class.getClassLoader(),

 Monkey.class.getInterfaces());

 

   proxy=proxClass.getConstructor(new

         Class[]{InvocationHandler.class}).

newInstance(new AnimalInvocationHandler(

new Monkey()));

       

       animal=(Animal)proxy;

       animal.eat("香蕉");

       animal.type();

       

       Primate pre=(Primate)proxy;

       pre.think();

   }}

 

回调类:

 

/**

 * 回调类。

 *

 * InvocationHandler接口只有一个invoke需要实现,这个方法会在目标对象的方法调用的时候被激活

 * 你可以在这里控制目标对象的方法的调用,在调用前后插入一些其他从操作

 * 比如:鉴权、日志,事务管理等。

 *

 * 后两个参数,一个是调用的方法的Method对象,另一个是方法的参数,第一个参数,,就是我们使用Proxy

 * 的静态方法创建的动态代理的对象,也就是$Proxy实例,由于$ProxyJDK中不是静态存在的,所以不可以把

 * Object proxy强制转换成$Proxy类型,因为你根本就无法从Classpath中道途$Proxy。那么我们可以把proxy

 * 转换为目标接口对象吗?可以。因为$Proxy是实现了目标对象的,但是这样的实际意义不大,因为转换为

 * 目标对象的接口之后,你调用接口中的任何方法,都会导致invoke的调用陷入死循环而导致栈溢出。

 * 这是因为目标对象的大部分方法都被代理了,在invoke通过代理对象转换之后的接口,调用目标对象的方法

 * 依然走的是代理对象

 *

 * 所以第一个参数,一般情况下都用不到,除非想获得代理对象$Proxy的类描述信息,它的getClass方法不会陷入死循环.

 * 不过还是要注意,调用$proxy的从object上继承的方法,比如hashCode等也会导致陷入死循环,因为getClass()

 * 方法是final的,不可以被覆盖,所以也就不会被Proxy代理。当然,因为Proxy代理的是Monkey接口,不是Monkey本身,

 * 所以即使Monkey在实现AnimalPrimate接口的时候,把方法都变为final,也不会影响到proxy的动态代理。 *

 * *

 */

public class AnimalInvocationHandler implements InvocationHandler{

 

   private Object obj;

  

   public AnimalInvocationHandler(Animal animal){

      this.obj=animal;

   }

  

   /**

    * proxy是实际的代理对象

    */

   public Object invoke(Object proxy, Method method, Object[] args)

         throws Throwable {

      System.out.println("invoke method hefore");

      Object returnObj=method.invoke(obj, args);

      System.out.println("invoke method hefore");

     

      return returnObj;

   }

 

}

 

CGLIB

 

该类没有实现接口,并且有一个final方法

public class Monkey{

 

   public void eat(String food) {

      System.out.println("the food is "+food+" !");

     

   }

  

   public final String type() {

      String type="哺乳动物";

      System.out.println(type);

      return type;

   }

 

   public void think() {

      System.out.println("思考");

     

   }

 

}

 

/**

 * CGLIB进行动态代理的编程于Proxy没有多少不同,EnhancerCGLIB的入口,通过它

 * 创建代理对象,同时为代理对象分配一个Callbacl回调接口,用于执行回调。

 *

 * 常用的是MethodInterceptor,他继承Callback,用于执行方法拦截。另外还有一些内置的回调处理:

 * 1.FixedValue,为提高性能,FixedValue回调对枪支某一特别方法返回固定值是有用的

 * 2.NoOp,该回调把方法调用直接委派到这个方法在父类中的实现,相当于不处理

 * 3.LazyLoader,当时机的对象需要延迟加载时,可以使用LazyLoader回调。一旦实际对象被装载,

 * 它将被每一个调用代理对象的方法使用

 *

 * CGLIBHibernateSpring等很多开源框架内部使用,用于完成对类的动态代理,Spring中的很多XML配置属性的proxy-target-class

 * 默认都为false,含义就是默认不启用对目标类的动态代理,而是对接口动态代理。某些情况下,如果相对Struts2Action或者Spring

 * MVCController进行动态代理,会发现默认Spring会报告找不到$ProxyXXX方法,这是因为一般我们都不会给控制层写一个接口,

 * 而是直接在实现类中写请求方法,这样JDK自带的Proxy是找不到这些方法的,应为他们不在借口中,此时就要设置proxy-target-class="true"

 * 引入CGLIB/ASM等库。

 */

public class CglibMethodInterceptor implements MethodInterceptor{

 

   /**

    * 1.obj 代理对象

    * 2.被调用的方法

    * 3.方法的参数

    * 4.CGLIB提供的方法代理对象

    *

    * 一般使用最后一个参数,而不是第2个,java的反射,因为CGLIB试用ASM

    * 字节码操作,代理对象的执行效率比反射机制更高.而且,试用method会造成死循环

    */

   public Object intercept(Object obj, Method method, Object[] args,

         MethodProxy proxy) throws Throwable {

      System.out.println("=============================");

      Object o=proxy.invokeSuper(obj, args);

      //Object o1=method.invoke(obj, args);

     

      System.out.println("++++++++++++++++++++++++++++++");

      return o;

   }

 

   public static void main(String args[])

   {

      /**

       * create方法用来创建代理对象

       */

      Monkey mon=(Monkey)Enhancer.create(Monkey.class,

            new CglibMethodInterceptor());

      mon.think();

      /*

       * 发现type方法没有被代理,没有输出装饰的信息

       * 这是因为CGLIB代理的原理是试用ASM动态生成目标对象的子类,final方法不能

       * 被子类覆盖,所以也就不能被动态代理,这也是CGLIB的一个缺点

       */

      mon.type();

      mon.eat("xiangjiao");

     

      System.out.println("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");

     

      /*

       * 很多情况下,我们不是使用静态方法,而不是Enhancer的实例去完成动态代理对象的创建

       * 因为试用实例,可以获得更多的功能,比如是否使用缓存,生成策略等等.

       *

       * ,默认情况,子类会继承父类无参的构造方法进行实例化,如果想

       * 调用其他构造器,那么用.create(argumentTypes, arguments),参数分别是构造方法的

       * 参数类型、传递参数

       */

      Enhancer enhancer=new Enhancer();

      enhancer.setSuperclass(Monkey.class);

      /*

       * 可以使用setCallbacks(Callback[] callbacks)方法为代理对象设置一组回调器

       * 可以配合CallbackFilter为不同方法使用不同的回调器。CallbackFilteraccept方法

       * 返回的是回调器的索引值

       */

      enhancer.setCallbacks(new Callback[]{new CglibMethodInterceptor(),NoOp.INSTANCE});

      enhancer.setCallbackFilter(new CallbackFilter() {

        

         public int accept(Method method) {

            // 方法think使用回调数组中的第二个回调器

            if(method.getName().equals("think"))

                return 1;

            else return 0;

         }

      });

      Monkey monk=(Monkey)enhancer.create();

      monk.think();

      monk.type();

      monk.eat("xiangjiao");

     

   }

  

}

 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
面试题目(四)
面向对象设计
Java动态代理设计模式
设计模式学习心得(持续更新)
别再说 Spring AOP 默认用的是 JDK 动态代理
面向对象设计框架
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服