第五章 5.1 代理模式

分类:01_设计模式

标签:

5.1 代理模式

5.1.1 代理模式介绍

在软件开发中,由于一些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为"代理"的第三者来实现间接访问.该方案对应的设计模式被称为代理模式.

代理模式(Proxy Design Pattern ) 原始定义是:让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许将请求提交给对象前后进行一些处理。

5.1.2 代理模式原理

代理(Proxy)模式分为三种角色:

66.jpg

5.1.3 静态代理实现

这种代理方式需要代理对象和目标对象实现一样的接口。

举例:保存用户功能的静态代理实现


//接口类: IUserDao
public interface IUserDao {

   void save();
}
//目标对象:UserDaoImpl
public class UserDaoImpl implements IUserDao {
   @Override
   public void save() {
       System.out.println("保存数据");
   }
}

//静态代理对象:UserDaoProxy 需要实现IUserDao接口
public class UserDaoProxy implements IUserDao {

   private IUserDao target;

   public UserDaoProxy(IUserDao target) {
       this.target = target;
   }

   @Override
   public void save() {
       System.out.println("开启事务"); //扩展额外功能
       target.save();
       System.out.println("提交事务");
   }
}

//测试类
public class TestProxy {

   @Test
   public void testStaticProxy(){

       //目标对象
       UserDaoImpl userDao = new UserDaoImpl();
       //代理对象
       UserDaoProxy proxy = new UserDaoProxy(userDao);
       proxy.save();
   }
}

5.1.4 JDK动态代理

5.1.4.1 JDK动态代理实现

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能.动态代理又被称为JDK代理或接口代理.

静态代理与动态代理的区别:

  1. 静态代理在编译时就已经实现了,编译完成后代理类是一个实际的class文件

  2. 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中.

JDK中生成代理对象主要涉及的类有

举例:保存用户功能的静态代理实现


/**
* 代理工厂-动态生成代理对象
* @author spikeCong
* @date 2022/9/22
**/
public class ProxyFactory {

   private Object target; //维护一个目标对象

   public ProxyFactory(Object target) {
       this.target = target;
   }

   //为目标对象生成代理对象
   public Object getProxyInstance(){

       //使用Proxy获取代理对象
       return Proxy.newProxyInstance(
               target.getClass().getClassLoader(), //目标类使用的类加载器
               target.getClass().getInterfaces(), //目标对象实现的接口类型
               new InvocationHandler(){ //事件处理器

                   /**
                    * invoke方法参数说明
                    * @param proxy 代理对象
                    * @param method 对应于在代理对象上调用的接口方法Method实例
                    * @param args 代理对象调用接口方法时传递的实际参数
                    * @return: java.lang.Object 返回目标对象方法的返回值,没有返回值就返回null
                    */
                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       System.out.println("开启事务");

                       //执行目标对象方法
                       method.invoke(target, args);
                       System.out.println("提交事务");
                       return null;
                   }
               }
       );
   }

}


//测试
public static void main(String[] args) {
   IUserDao target = new UserDaoImpl();
   System.out.println(target.getClass());//目标对象信息

   IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
   System.out.println(proxy.getClass()); //输出代理对象信息
   proxy.save(); //执行代理方法
}

5.1.4.2 类是如何动态生成的

Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流

  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:

69.jpg

所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用

5.1.4.3 代理类的调用过程

我们通过借用阿里巴巴的一款线上监控诊断产品 Arthas(阿尔萨斯) ,对动态生成的代理类代码进行查看

67.jpg68.jpg

代理类代码如下:


package com.sun.proxy;

import com.mashibing.proxy.example01.IUserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0
extends Proxy
implements IUserDao {
   private static Method m1;
   private static Method m3;
   private static Method m2;
   private static Method m0;

   public $Proxy0(InvocationHandler invocationHandler) {
       super(invocationHandler);
   }

   static {
       try {
           m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
           m3 = Class.forName("com.mashibing.proxy.example01.IUserDao").getMethod("save", new Class[0]);
           m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
           m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
           return;
       }
       catch (NoSuchMethodException noSuchMethodException) {
           throw new NoSuchMethodError(noSuchMethodException.getMessage());
       }
       catch (ClassNotFoundException classNotFoundException) {
           throw new NoClassDefFoundError(classNotFoundException.getMessage());
       }
   }

   public final boolean equals(Object object) {
       try {
           return (Boolean)this.h.invoke(this, m1, new Object[]{object});
       }
       catch (Error | RuntimeException throwable) {
           throw throwable;
       }
       catch (Throwable throwable) {
           throw new UndeclaredThrowableException(throwable);
       }
   }

   public final String toString() {
       try {
           return (String)this.h.invoke(this, m2, null);
       }
       catch (Error | RuntimeException throwable) {
           throw throwable;
       }
       catch (Throwable throwable) {
           throw new UndeclaredThrowableException(throwable);
       }
   }

   public final int hashCode() {
       try {
           return (Integer)this.h.invoke(this, m0, null);
       }
       catch (Error | RuntimeException throwable) {
           throw throwable;
       }
       catch (Throwable throwable) {
           throw new UndeclaredThrowableException(throwable);
       }
   }

   public final void save() {
       try {
           this.h.invoke(this, m3, null);
           return;
       }
       catch (Error | RuntimeException throwable) {
           throw throwable;
       }
       catch (Throwable throwable) {
           throw new UndeclaredThrowableException(throwable);
       }
   }
}

简化后的代码


package com.sun.proxy;

import com.mashibing.proxy.example01.IUserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0
extends Proxy
implements IUserDao {
   private static Method m3;

   public $Proxy0(InvocationHandler invocationHandler) {
       super(invocationHandler);
   }

   static {
       try {
        m3 = Class.forName("com.mashibing.proxy.example01.IUserDao").getMethod("save", new Class[0]);
         return;
       }
   }

   public final void save() {
       try {
           this.h.invoke(this, m3, null);
           return;
       }
   }
}

5.1.5 cglib动态代理

5.1.5.1 cglib动态代理实现

cglib (Code Generation Library ) 是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。cglib 为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

72.jpg

使用cglib 需要引入cglib 的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib 。


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

示例代码

目标类


public class UserServiceImpl {

   public List<User> findUserList(){

       return Collections.singletonList(new User("tom",18));
   }
}


public class User {

   private String name;

   private int age;

   .....
}

cglib代理类,需要实现MethodInterceptor接口,并指定代理目标类target


public class UserLogProxy implements MethodInterceptor {

   private Object target;

   public UserLogProxy(Object target) {
       this.target = target;
   }

   public Object getLogProxy(){

       //增强器类,用来创建动态代理类
       Enhancer en = new Enhancer();

       //设置代理类的父类字节码对象
       en.setSuperclass(target.getClass());

       //设置回调: 对于代理类上所有的方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦截
       en.setCallback(this);

       //创建动态代理对象并返回
       return en.create();
   }

   /**
    * 实现回调方法
    * @param o     代理对象
    * @param method  目标对象中的方法的Method实例
    * @param args      实际参数
    * @param methodProxy  代理对象中的方法的method实例
    * @return: java.lang.Object
    */
   @Override
   public Object intercept(Object o, Method method, Object[] args,
                           MethodProxy methodProxy) throws Throwable {

       Calendar calendar = Calendar.getInstance();
       SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

       System.out.println(formatter.format(calendar.getTime()) + " [" +method.getName() + "] 查询用户信息...]");

       Object result = methodProxy.invokeSuper(o, args);
       return result;
   }
}

public class Client {

   public static void main(String[] args) {

       //目标对象
       UserServiceImpl userService = new UserServiceImpl();
       System.out.println(userService.getClass());

       //代理对象
       UserServiceImpl proxy = (UserServiceImpl) new UserLogProxy(userService).getLogProxy();
       System.out.println(proxy.getClass());

       List<User> userList = proxy.findUserList();
       System.out.println("用户信息: "+userList);
   }
}

5.1.5.2 cglib代理流程

74.jpg

5.1.6 代理模式总结

5.1.6.1 三种代理模式实现方式的对比

5.1.6.2 代理模式优缺点

优点:

缺点:

5.1.6.2 代理模式使用场景


修改内容