未分类

代理模式

代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了。只需要在代理类上增加就可以了。

使用代理的好处有很多,比如可以在原来的功能上增加功能,或者是控制访问,代理类帮忙去访问我访问不到的类等等。

其中,动态代理有着广泛的应用,也是动态代理让我们使用很多工具时能更简洁地编写代码,比如Mybatis不用写实现类只写接口就可以执行SQL,或者AOP,所以希望能动态代理学好来,以后更加深入理解框架的工作原理。

反射基础

动态代理的基础使反射,所以可以再简单巩固一下反射的内容。

在Java中,反射允许我们在运行时检查和操作类、接口、构造函数、方法和字段。Java中有一个名为Class的类,它在运行时保存关于对象和类的所有信息。Class的对象可以用于执行反射。创建Class对象有3种方法:

  1. Class.forName(“ClassName”)
  2. 类的实例对象.getClass()
  3. 直接用类名.class的形式

获得了Class对象后,就可以调用很多方法来获取类的信息了:

  • getName()
  • getModifiers()
  • getSuperclass()
  • getDeclareMethods()
  • getFiled()
  • getDeclaredField()
  • getDeclaredConstructors()
  • ……

很多的内容和方法都定义在 java.lang.reflect包中。我们学习这些方法后,发现通过代理,我们可以在运行时,得到类的相关信息,又可以在运行时去修改类的方法或是属性等等……

除此之外,还可以运行时创建对象。先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()方法来创建 Class 对象对应类的实例,这种方式可以选定构造方法创建实例。

静态代理

代理模式分为静态代理和动态代理。静态代理就是声明一个明确的代理类来访问对象,可以简单地用继承和重写方法来实现,不过就是会存在大量的冗余的代理类,而且代理类和目标类耦合度特别高,不易维护。在静态代理中的目标拓展类太多了,可以使用JDK动态代理,一个代理类可以被很多类拿来使用,增强功能。而且你修改了除代理类外的其他接口或者类,也不影响动态代理的功能。

JDK动态代理(接口代理)

JDK动态代理是通过实现InvocationHandler接口来实现的,也是利用反射机制在运行时动态的指定要代理目标类。 换句话说,动态代理是一种创建java对象的能力,让你不用特定创建某个需要被代理的类的代理类,就能创建代理类对象。

以下根据创建JDK动态代理类的步骤编写了一段代码,仅需要一个AnimalProxy代理类就可以拓展Dog和Cat的eat()功能,当然,她们的拓展内容是一样的。

  1. 创建Animal接口,定义目标类要完成的功能
  2. 创建目标类实现接口,Dog、Cat
  3. 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能:调用目标方法+增强功能
  4. 使用Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Animal {
   void eat();
}

class Dog implements Animal {
   @Override
   public void eat() {
       System.out.println("The dog is eating");
  }
}

class Cat implements Animal {
   @Override
   public void eat() {
       System.out.println("The cat is eating");
  }
}

// JDK 动态代理
class AnimalProxy implements InvocationHandler {
   private Object target; // 代理对象
   public Object getInstance(Object target) {
       this.target = target;
       // 获取代理对象
       return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       System.out.println("调用前");
       Object result = method.invoke(target, args); // invoke方法来执行某个对象的方法
       System.out.println("调用后");
       return result;
  }


   public static void main(String[] args) {
       //new一个JDK动态代理对象出来
       AnimalProxy proxy = new AnimalProxy();
       Animal dogProxy = (Animal) proxy.getInstance(new Dog());
       dogProxy.eat();
       Animal catProxy = (Animal) proxy.getInstance(new Cat());
       catProxy.eat();
  }
}

可以看看代码引入一块,一个最简单的动态代理类需要引入反射包 java.lang.reflect 里面的三个类 : InvocationHandler , Method, Proxy.

  • InvocationHandler 接口(调用处理器),表示你的代理要干什么。需要重写一个方法invoke(),是Method类中的一个方法,表示执行方法的调用,也就是 把原来静态代理中代理类要完成的功能,写在这个方法里。 (详细可以参考代码体悟)
  • Method类:表示方法的, 确切的说就是目标类中的方法。作用是通过Method可以执行某个目标类的方法。其中有一个invoke()方法。
  • Proxy类:核心的对象,创建代理对象。之前静态代理创建对象都是 new 类的构造方法, 现在我们是使用Proxy类的方法,代替new的使用。 其中,它有一个静态方法newProxyInstance()方法,作用是创建代理对象,等同于静态代理的new 代理类的构造方法的作用。
public Object invoke(Object proxy, Method method, Object[] args)
// 方法原型:
//         参数: Object proxy:jdk创建的代理对象,无需赋值。
//         Method method:目标类中的方法,jdk提供method对象的
//         Object[] args:目标类中方法的参数, jdk提供的。
//如果不传原有的method,只传两个参数就会陷入一个死循环,因为你看你后
//面还要在处理附加功能中执行原有bean的方法,以完成代理的职责。
   
method.invoke(目标对象,方法的参数)
//很多类都继承这个动态代理类,可能都有同名方法,所以就要根据传入的对象 //和参数来调用这个对象的方法
   
public static Object newProxyInstance(ClassLoader loader,  Class<?>[] interfaces, InvocationHandler h)
// 参数:
//   1. ClassLoader loader 类加载器,负责向内存中加载对象的。 使用反射获取对象的ClassLoader
//         如Dog类 , Dog.getCalss().getClassLoader(), 目标对象的类加载器(所以复习一下反射是很有必要的!!)
//   2. Class<?>[] interfaces: 接口,目标对象实现的接口,也是反射获取的。 如代码中就是获取了Animal接口
//   3. InvocationHandler h : 我们自己写的,刚好又在这个类里定义
//的方法,所以传个this,代理类要完成的功能。
//   返回值就是代理对象,所以需要自己转化一下

总结来说,JDK动态代理必须至少实现一个InvocationHandler 接口,因为JDK是根据这个接口“凭空”来生成类的,具体的功能增强的执行都在InvocationHandler的实现类里。而且只能是接口而不是继承一个类,因为动态代理对象默认已经继承了 Proxy 对象,而且 Java 不支持多继承,所以 JDK 动态代理要基于接口来实现。

基于ASM的CGLIB的动态代理

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成, 也是用来创建代理对象的。cglib的原理是继承, 他通过继承目标类,创建它的子类,在子类中重写父类中同名的方法, 实现功能的修改。因为cglib是继承,重写方法,所以要求目标类不能是final的, 方法也不能是final的。 cglib的要求目标类比较宽松, 只要能继承就可以了。cglib在很多的框架中使用,比如 mybatis ,spring框架中都有使用。如果发现没有实现接口该类而无法使用JDK代理,可以通过cglib代理实现。

这里有一篇十分好的示例博客

待补充……

Tagged

发表评论

邮箱地址不会被公开。 必填项已用*标注