未分类

代理模式

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

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

其中,动态代理有着广泛的应用,也是动态代理让我们使用很多工具时能更简洁地编写代码,比如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代理实现。

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

Spring的AOP中的动态代理

AOP是一种被称为横切的技术,将那些影响多个类的公共行为封装到一个可重用的模块,并将其命名为 Aspect(切面)。所谓切面,就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来, 减少系统代码的重复,降低模块间的耦合度。和业务主要流程关系不大的功能拓展部分称为横切关注 点,经常发生在核心关注点的多出。AOP就是把核心关注点和横切关注点分离开。

spring AOP底层使用的就是动态代理来做到添加额外功能啦!它即使用到JDK动态代理,也使用到了CGLIB的动态代理,之所以用到两种动态代理,是因为JDK本身只提供基于接口的类,不支持类的代理,但是你不能保证你的类都是会继承接口的吧(虽然写项目的时候,大多业务类都是继承一个impl接口的嘤嘤嘤)。

来创建一个spring的工程,上业务方法:

public interface UserService {
   void addUser();
   void deleteUser();
   void updateUser();
   void getUser();
}

public class UserServiceImpl implements UserService {
   /**
    * 在业务方法中添加日志打印或者性能检测等......
    * 如果在每个业务方法中去添加:
    * 代码重复冗余,不易拓展维护
    * 不符合高内聚、低耦合的要求
    * 所以可以创建代理类,交给代理类为我们提供这些功能
    */
   @Override
   public void addUser() {
       System.out.println("add");
  }

   @Override
   public void deleteUser() {
       System.out.println("delete");
  }

   @Override
   public void updateUser() {
       System.out.println("update");
  }

   @Override
   public void getUser() {
       System.out.println("get");
  }
}

使用JDK代理

上代理类(重点!):

public class LogInvocationHandler implements InvocationHandler {
   //声明target对象
   //本案例的target就是UserServiceImpl类的对象
   private Object target;

   //创建构造函数,为target对象赋值
   public LogInvocationHandler(Object target) {
       this.target = target;
  }


   //代理类的业务方法
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       Object o = null;
       System.out.println("start");
       //实现业务增强
       method.invoke(target,args);
       System.out.println("end");
       return o;
  }

   //提供取得代理类对象的方法
   Object getProxy() {
       return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
  }
}

以下是测试:

  @Test
   void contextLoads() {
       LogInvocationHandler logInvocationHandler = new LogInvocationHandler(new UserServiceImpl());

       //代理类是运行时产生而非我们手动创建的
       UserService UserServiceProxy = (UserService) logInvocationHandler.getProxy();
       UserServiceProxy.addUser();
  }

使用CGLIB代理

假如我们的业务类不是实现接口的,就得使用CGLIB代理了,它需要添加一个依赖:

<dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib</artifactId>
     <version>3.3.0</version>
</dependency>

CGLIB主要是通过回调MethodInterceptor拦截器接口方法拦截,来实现自己的代理逻辑,类似于JDK中的InvocationHandler接口。

public class LogInterceptor implements MethodInterceptor{ 
   private Object target;
   //代理类的业务方法
   @Override
   public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {  
       System.out.println("Before:"+method);    
       Object object=proxy.invokeSuper(obj, arg);  
       System.out.println("After:"+method);  
       return object;  
  }  
   //返回代理类对象
   public Object getInstance(Object tatget) {
       this.target = target;
       //创建增强器
       Enhancer enhancer = new Enhancer();
       //使用增强其指定被代理的对象
       enhancer.setSuperClass(this.target.getClass())
;
   //设置回调方法
       enhancer.setCallback(this);
       
       //创建并返回代理对象
       return enhancer.create();
  }
   
}  

然后测试方法如下,也是可以得到相同的结果的:

@Test
   void test01() {
      LogInterceptor cglib = new LogInterceptor();

       //代理类是运行时产生而非我们手动创建的
       UserServiceImpl UserService = (UserServiceImpl) cglib.getInstance(new UserServiceImpl);
       UserService.addUser();
  }

cglib代理就适合代理没有实现任何接口的代理类,但是spring里用到最多的还是JDK的动态代理啦~

Tagged

发表评论

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