Java常用设计模式-代理模式

490 阅读8分钟

代理模式是一种设计模式,其意义在于生成一个占位,来代替真实的对象,从而控制对真实对象的访问。 其实在现实生活中,代理很容易理解。假设这样一个场景:你的公司是一家服装公司,客户来定制服装,肯定不是直接找公司的裁缝谈的,而是去找商务谈,这时客户就会认为商务就代表公司。

image.png

可见这是一个间接的过程,那么商务(代理对象)的意义何在?商务可以进行一些额外逻辑例如谈判、价格等等,也就是说,代理的作用是:在委托对象访问之前或者之后加入对应逻辑,或者根据其它条件判断是否要访问委托对象,这个场景很显然商务控制了客户对裁缝的访问。

那么我们的代码示例也将以此为场景,一一讲解各个代理模式。

在讲解之前,我们先定义衣服实体类衣服制作接口裁缝类(实现衣服制作接口) 这几个基本类:

衣服实体类:

package com.example.javaproxy.model;

import lombok.Getter;
import lombok.Setter;

/**
 * 衣服类
 */
@Getter
@Setter
public class Clothing {

   /**
    * 衣服名
    */
   private String name;

   /**
    * 尺码(S,M,L,XL,XXL)
    */
   private String size;

}

衣服制作接口:

package com.example.javaproxy.service;

import com.example.javaproxy.model.Clothing;

/**
 * 衣服制作接口
 */
public interface ClothesMaking {

   /**
    * 衣服制作
    * @param name 衣服名
    * @param size 衣服大小
    * @return 衣服实例
    */
   Clothing makeClothing(String name, String size);

}

裁缝类(实现衣服接口,为委托对象):

package com.example.javaproxy.service.impl;

import com.example.javaproxy.model.Clothing;
import com.example.javaproxy.service.ClothesMaking;

/**
 * 裁缝(委托类)
 */
public class Tailor implements ClothesMaking {

   @Override
   public Clothing makeClothing(String name, String size) {
      Clothing clothing = new Clothing();
      clothing.setName(name);
      clothing.setSize(size);
      return clothing;
   }

}

1,静态代理

静态代理其实很简单,相当于直接创建代理人类,间接地访问裁缝类。

image.png

我们直接上代码,代理人(类):

package com.example.javaproxy.proxy.staticproxy;

import com.example.javaproxy.model.Clothing;
import com.example.javaproxy.service.ClothesMaking;
import com.example.javaproxy.service.impl.Tailor;

/**
 * 裁缝代理人(代理类,静态代理)
 */
public class TailorStaticAgent implements ClothesMaking {

   /**
    * 代理人的裁缝实例(委托对象)
    */
   private ClothesMaking tailor = new Tailor();

   /**
    * 向代理人定制衣服
    *
    * @param name   衣服名字
    * @param size 客户身高
    * @return 衣服
    */
   @Override
   public Clothing makeClothing(String name, String size) {
      System.out.println("[进入静态代理逻辑]");
      System.out.println("裁缝代理人已接到需求!");
      return tailor.makeClothing(name, size);
   }

}

然后在主方法测试:

ClothesMaking agent = new TailorStaticAgent();
Clothing customClothing = agent.makeClothing("衬衫", "XL");
System.out.println("得到衣服名:" + customClothing.getName() + ";得到衣服尺码:" + customClothing.getSize());

结果:

image.png

可见静态代理非常容易理解,就是新建一个代理类,利用这个代理类(裁缝的代理人) 去访问 委托类(裁缝),客户只需要找代理人即可间接地访问裁缝。

可见静态代理中,代理类和委托类需要实现同一个接口,且静态代理是一种编译后再代理的模式,不太利于扩展。

2,动态代理

上述例子可见,静态代理的代理类编译后也变为class文件,而动态代理是在运行时动态生成代理对象的,在运行时动态生成类字节码,并加载到JVM中。

(1) JDK动态代理

利用JDK自带的API实现在内存中创建动态的代理对象,我们无需向上面静态代理一样建立代理类,只需要建立一个代理逻辑类,这个逻辑类需要使用InvocationHandler接口,在逻辑类里面写下代理方法逻辑,并将代理对象和委托对象建立联系即可,然后这个代理逻辑类就可以生成动态代理对象。

image.png

这里贴上代理逻辑类的代码:

package com.example.javaproxy.proxy.jdkproxy;

import com.example.javaproxy.service.ClothesMaking;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK动态代理逻辑实现
 */
public class JDKProxyBind implements InvocationHandler {

   /**
    * 代理人的裁缝代理实例(委托对象)
    */
   private ClothesMaking tailor;

   /**
    * 将委托对象和代理对象建立关系
    *
    * @param origin 委托对象
    * @return 代理对象
    */
   public Object bind(ClothesMaking origin) {
      this.tailor = origin;
      return Proxy.newProxyInstance(tailor.getClass().getClassLoader(), tailor.getClass().getInterfaces(), this);
   }

   /**
    * 代理逻辑方法
    *
    * @param proxy  代理对象
    * @param method 当前调度方法
    * @param args   当前方法参数
    * @return 代理结果
    * @throws Throwable 异常
    */
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("[进入JDK动态代理逻辑]");
      System.out.println("裁缝代理人已接到需求!");
      // 调用裁缝(委托类)的方法
      Object result = method.invoke(tailor, args);
      System.out.println("衣服定制完成!");
      return result;
   }

}

然后去主方法测试:

// 首先创建JDK代理逻辑实现类的实例,用于将代理对象和委托对象联系起来
JDKProxyBind jdkProxyBind = new JDKProxyBind();
// 新建裁缝实例,绑定关系
ClothesMaking tailor = new Tailor();
ClothesMaking agent = (ClothesMaking) jdkProxyBind.bind(tailor);
// 这个时候agent对象已经是一个代理对象了,它会进入代理的逻辑方法invoke中去
Clothing customClothing = agent.makeClothing("衬衫", "XL");
System.out.println("得到衣服名:" + customClothing.getName() + ";得到衣服尺码:" + customClothing.getSize());

结果:

image.png

可见JDK动态代理,并没有手动建立代理类,只是写了代理人的代理逻辑部分。其主要分为两大步:

  1. 建立代理对象和委托对象的关系:在上述我们建立了代理逻辑类,其中定义了bind方法,方法中首先用类的属性tailor保存了委托对象,然后通过Proxy类的newProxyInstance方法,建立代理对象。这个方法有三个参数:
    • 第一个是类加载器,使用委托类的加载器
    • 第二个是要把生成的动态代理对象挂载至哪个接口之下,这里很显然就是要挂在委托类的接口下
    • 第三个是定义实现方法逻辑的代理类,this表示当前对象。代理逻辑类必须实现InvocationHandler接口的invoke方法,这个方法就是代理逻辑方法的实现方法。 我们就将代理逻辑放在本类
  2. 实现代理方法:在代理逻辑类的invoke方法中实现代理逻辑,该方法三个参数意义如下:
    • proxy 代理对象,就是bind方法生成的对象
    • method 当前调度的方法
    • args 调度方法的参数

我们使用了代理方法调度方法之后,就会进入到invoke方法中去。

实现了代理逻辑类,在主方法中使用时,可见只需要使用代理逻辑类将裁缝(委托类)绑定关系,就可以直接生成代理对象,执行代理逻辑。

(2) CGLib动态代理

我们发现,无论是静态代理还是JDK动态代理,都需要提供接口。若在不能提供接口的环境中,我们可以使用CGLib代理。它只需要提供一个委托类就能实现动态生成代理对象实现代理。

首先我们需要在Maven中引入CGLib的依赖:

<!-- CGLib代理 -->
<dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>3.3.0</version>
</dependency>

然后也是创建一个代理逻辑类,这个和JDK动态代理有点类似。

image.png

还是贴上代理逻辑类的代码:

package com.example.javaproxy.proxy.cglibproxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CGLib动态代理逻辑实现
 */
public class CGLibProxyBind implements MethodInterceptor {

   /**
    * 生成CGLib代理对象
    *
    * @param cls 真实对象的类
    * @return 代理对象
    */
   public Object getProxy(Class cls) {
      // CGLib增强类型对象
      Enhancer enhancer = new Enhancer();
      // 设定委托类为父类
      enhancer.setSuperclass(cls);
      // 设定代理逻辑类为本类
      enhancer.setCallback(this);
      // 生成并返回代理对象
      return enhancer.create();
   }

   /**
    * 代理逻辑方法
    *
    * @param proxy       代理对象
    * @param method      方法
    * @param args        方法参数
    * @param methodProxy 方法代理
    * @return 代理逻辑返回
    * @throws Throwable 异常
    */
   @Override
   public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      System.out.println("[进入CGLib动态代理逻辑]");
      System.out.println("裁缝代理人已接到需求!");
      // 调用裁缝(委托类)的方法
      Object result = methodProxy.invokeSuper(proxy, args);
      System.out.println("衣服定制完成!");
      return result;
   }

}

然后在主方法测试:

// 首先创建CGLib代理逻辑类实例,把代理对象和委托对象联系起来
CGLibProxyBind cgLibProxyBind = new CGLibProxyBind();
// 生成裁缝的代理对象
Tailor agent = (Tailor) cgLibProxyBind.getProxy(Tailor.class);
// 这个时候agent已经是一个代理对象了,尽管它是Tailor类
Clothing customClothing = agent.makeClothing("衬衫", "XL");
System.out.println("得到衣服名:" + customClothing.getName() + ";得到衣服尺码:" + customClothing.getSize());

结果:

image.png

可见这里用了CGLib的加强者Enhancer,通过设定超类为委托类,并设定本类为代理逻辑类(代理逻辑类需要实现MethodInterceptor接口),来生成动态代理对象。

可见在使用上面,CGLib动态代理和JDK动态代理还是很相似的。

不过这里需要提醒一下,使用JDK9及其以上版本执行CGLib动态代理会抛出异常java.lang.ExceptionInInitializerError,因为在JDK高版本中,--illegal-access选项默认为deny,这会导致深度反射失败。

可以使用JDK8版本来实现CGLib代理。

若要使用JDK9及其以上版本,那么就修改运行JVM参数--illegal-access的值为warn(不过这样还是会弹出警告)。

命令行运行加上参数即可:

java --illegal-access=warn -jar xxx.jar

使用IDEA的话,在运行配置中设定运行参数:

image.png

image.png

image.png

image.png

最后应用、确定即可!

3,总结

代理模式就是一种间接访问的模式,在很多地方其实都有着应用。

静态代理很简单,不过需要手动定义代理类,不利于业务扩展。

动态代理只需要实现代理方法逻辑即可,在运行过程中就会动态地生成代理对象,常用的就是JDK动态代理和CGLib动态代理。

而JDK动态代理和CGLib动态代理最大的区别就是,前者需要依赖接口而后者不需要。JDK动态代理的实质是通过反射代理方法,而CGLib是通过生成类字节码实现代理。

示例程序仓库地址