代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理

代理模式前传

前几天有个小朋友问到我代理模式,这个我拿手的嘛。废话不多说,直接开讲?这是不可能的。

一般呢,讲模式之前呢,礼貌上都要讲个前传。象我这种老了根本不怕没有故事祭奠的人,随手一个前传开始。

区区在下做为一名平时不怎么修边幅的小女子,突然之间接到电话,一个long long ago之前暗恋过的男神之一打来的。一直暗恋,从没明恋,他也从不知道曾被我暗恋过的男神说他出差到广州,老同学很多年没见了,问要不吃个便饭?那,就吃一个?怎么的我也算半个地主吧?

挂上电话之后,我浅浅的思考了一下,随随便便搞了一个购物清单。

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第1张图片

虽早已不恋,男神之一当年仅有的最突出的优点---帅也早已不复存在,存在的只有突出的大肚子。但,什么也不能阻止我此时默默地开个屏~~

先找帽子,和卖帽子的店家进行售前沟通、咨询、比较,再找外套,然后衬衣~~~基本上是这样子的

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第2张图片

就,心好累~~

简单一列就这样了,还不包括化妆品,护肤品,小饰品~~万一想穿裙子呢?要搞多几个造型对比选择呢?看着最后几个小……,世界已无爱~~确定要开屏吗?不认真地思考了一下,还是要的~~

然后看一眼,我买东西的店家其实也辛苦,它们要和我进行售前咨询,交易完成后还要保持与我的售后沟通服务。

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第3张图片

这样看,每个商家每天要面对大量的客户,也是累并快乐着呀~~

如果,我是说如果哈,如果此时我有一名造型师呢?情况就变成这样子了

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第4张图片

我只需要将我的需求告诉造型师,造型师帮我去找到各种商品。并保证我的售后沟通。

各商家呢?也再不用管售前咨询和售后沟通的问题,专注核心事业:搞产品。完美~~

至此,结论就来了:造型师,就是代理类。各商家就是被代理的类。我就是客户端的请求。

什么男神呀,都是不存在的,浮云。我爱代码,代码爱我,我为代码开个屏。

代理模式前传,懂?前几天问我代理模式的小朋友注意了哈,敲黑板了~~

没看懂的告诉我,看懂了的也可以告诉我。

先看懂前传,咱们后面接着静态代理~~

静态代理

好了,正传开始,此处已无男神~~区区在下小女子目下无尘,只有代码。

现在,我们开始假装要搞一个商城了。商城先得有商品吧,有商品就得把商品存到数据库里去吧。那我们就在此顺势建一个将商品持久化的类。(注:此处可能有小朋友会举手问什么是持久化,持久化你目前可以理解为就是把商品的数据保存到数据库里,以后想看,可以拿出来看。持久化了的东东,就不是只活在内存里,一关机就不见了。而是放到数据库里,保存到硬盘上了的。如果还有小朋友想问什么是内存,什么是硬盘,来,给钱我,我慢慢告诉你~~放心,我不得打小朋友的~~)。

将商品保存到数据库,增删改查是必须的。但太简单了,so easy(你确定?),暂时不理它~~(注:此处是胡说八道,实际情况是,查我们要单独处理,容后再说~~可能也许大概吧~~)

就此决定,先建三个方法:增删改:

第一代代码

商品持久化类 ProductDaoImpl

public class ProductDaoImpl {
  public void insert(){
    System.out.println("新增商品");
  }
  public void update(){
    System.out.println("修改商品");
  }
  public void delete(){
    System.out.println("删除商品");
  }
 }

客户端测试

public class ProxyTest {
  public static void main(String[] args) {
      ProductDaoImpl productDao=new ProductDaoImpl ();
      productDao.delete();
  }
 
}

正常运行,打印出"删除商品" 没有问题。

好了,这三个特别特别复杂的方法我们写好了,核心功能都实现了(是吧,是吧,都打印出来了的~~)。然后,对数据库操作,得有事务吧(不懂什么是事务的小朋友请举手,我们另外处理。),得写日志吧,还得先判断当前登录用户对这个商品是否有操作权限吧?(注:看明白了吧?至少确实不能开个事务来独占资源,所以不一起说~~)

于是,这三个特别复杂的方法瞬间又上一个档次的复杂了起来。

第二代代码

public class ProductDaoImpl {
  public void insert(){
    System.out.println("判断是否有操作权限");
    System.out.println("开始事务");
    System.out.println("新增商品");
    System.out.println("结束事务");
    System.out.println("写日志");
  }
  public void update(){
    System.out.println("判断是否有操作权限");
    System.out.println("开始事务");
    System.out.println("修改商品");
    System.out.println("结束事务");
    System.out.println("写日志");
  }
  public void delete(){
    System.out.println("判断是否有操作权限");
    System.out.println("开始事务");
    System.out.println("删除商品");
    System.out.println("结束事务");
    System.out.println("写日志");
  }
 
}

代码这么多,还大多是重复的,这不应该是人干的活,这应该是ChatGPT干的事儿哇~~纵观以上代码,每个方法中,核心业务只有一行~~边边角角,又必须存在的代码有4行之多~~所以,我们必须想个办法消灭它们,让我们核心突出。主角得有主角的待遇。

所以,我们另外建一个类,专注处理边边角角,可以吗?那是当然~~来,走起~~

新建一个代理类ProxyDao

  1. 首先,这个代理类有商品持久化类ProductDaoImpl的各同名方法。

public class ProxyDao {
  public void insert(){}
  public void update(){}
  public void delete(){}
 }
  1. 类里只需要一个成员变量productDao,该成员变量的类型就是ProductDaoImpl。

public class ProxyDao {
    private ProductDaoImpl productDao;
    public void insert(){}
    public void update(){}
    public void delete(){}
  }
  1. 要保证只要代理类ProxyDao的对象被创建,其属性productDao就必须是一个已构建好的ProductDaoImpl的对象,而不会是null。所以用带参构造方法给productDao赋初值。ProxyDao只有一个构造方法,是带一个参数的构造方法,参数就是ProductDaoImpl的对象。

public class ProxyDao {
    private  ProductDaoImpl productDao;
    public ProxyDao(ProductDaoImpl productDao){
        this.productDao=productDao;
      }
      public void insert(){}
      public void update(){}
      public void delete(){}
}
  1. 在代理类ProxyDao的各方法中,调用属性productDao的同名方法。

public class ProxyDao {
    private  ProductDaoImpl productDao;
    public ProxyDao(ProductDaoImpl productDao){
        this.productDao=productDao;
      }
      public void insert(){
        productDao.insert();
      }
      public void update(){
         productDao.update();
      }
      public void delete(){
         productDao.delete();
       }
}

代码写到这里,相信大家也看出来了,我们使用这个代理类ProxyDao的各个方法,与直接使用商品持久化类ProductDaoImpl的各个方法,产生的效果是一样的。那么,如果我们在代理类ProxyDao的各方法前后各加一些边边角角的内容呢?象这样?

public class ProxyDao {
  private ProductDaoImpl productDao;
  public ProxyDao(ProductDaoImpl productDao){
    this.productDao=productDao;
  }
  public void insert(){
    System.out.println("判断是否有操作权限");
    System.out.println("开始事务");
    productDao.insert();
    System.out.println("结束事务");
    System.out.println("写日志");
  }
  public void update(){
    System.out.println("判断是否有操作权限");
    System.out.println("开始事务");
    productDao.update();
    System.out.println("结束事务");
    System.out.println("写日志");
  }
  public void delete(){
    System.out.println("判断是否有操作权限");
    System.out.println("开始事务");
    productDao.delete();
    System.out.println("结束事务");
    System.out.println("写日志");
  }
 }

这样是不是商品持久化类ProductDaoImpl中的代码,只需要保留核心的那一行代码就可以了?

此时客户端测试,就不是直接调商品持久化类ProductDaoImpl对象的方法了,而是调代理类ProxyDao对象的方法。

public class ProxyTest {
  public static void main(String[] args) {
    ProductDaoImpl productDao=new ProductDaoImpl ();
          ProxyDao proxyDao=new ProxyDao(productDao);
          proxyDao.delete();
     
    }

再观察一下,我们还可以把代理类ProxyDao中重复的代码再抽一抽,该封装就封装一哈。

在客户端,productDao这个变量也只是做为参数传一传,就没用了。调方法根本不关它事,于是,感觉它可以不必拥有姓名?我们在new 代理类的时候,传一个商品持久化类ProductDaoImpl的匿名对象进去,不香吗?外面根本找不到,不用在客户端扰我视线,乱我思维。

于是最后代码成了这样

第三代代码

代理类ProxyDao

属性productDao是商品持久化类ProductDaoImpl的对象。并通过一个有参构造进行初始化。

ProxyDao类中有与ProductDaoImpl类相同的public方法,用于调用ProductDaoImpl类中的同名方法,处理核心业务。并在调用前后,进行事务、日志、判断操作权限等非核心的相关业务的处理。

注:此例中,将核心功能前后的业务处理都设计为相同,并分别封装在begin()和last()两个私有方法中。实际应用中,可根据具体情况进行处理。

public class ProxyDao {
  private ProductDaoImpl productDao;
  public ProxyDao(ProductDaoImpl productDao){
    this.productDao=productDao;
  }
  public void insert(){
    begin();
    productDao.insert();
    last();
  }
  public void update(){
    begin();
    productDao.update();
    last();
  }
  public void delete(){
    begin();
    productDao.delete();
    last();
  }
  private void begin(){
    System.out.println("判断是否有操作权限");
    System.out.println("开始事务");
  }
  private void last(){
    System.out.println("结束事务");
    System.out.println("写日志");
  }
 
}

被代理的类

商品持久化类ProductDaoImpl又恢复了原样,边边角角都交给别人去处理了,拒绝拼盘,从代理做起。

public class ProductDaoImpl {
  public void insert(){
    System.out.println("新增商品");
  }
  public void update(){
    System.out.println("修改商品");
  }
  public void delete(){
    System.out.println("删除商品");
  }
  }

测试类

此时客户端测试

public class ProxyTest {
  public static void main(String[] args) {
      ProxyDao proxyDao=new ProxyDao(new ProductDaoImpl());
      proxyDao.delete();
  }
 
}

运行结果:

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第5张图片

代理了?对不对?代理了哇~~别激动,这才刚开始呢~~

这个时候呢,我们就得思考了,商城哇,怎么也得有订单吧?订单呢?要不来个订单持久化先?

于是订单持久化来了

新增的类订单持久化类OrderDaoImpl

public class OrderDaoImpl {
  public void insert(){
    System.out.println("新增订单");
  }
  public void update(){
    System.out.println("修改订单");
  }
  public void delete(){
    System.out.println("删除订单");
  }
 
}

那订单持久化的边边角角怎么办呢?又搞一个代理类?格局小了吧~~

此时,理应接口闪亮登场了~~请跟着我念:面向接口编程~~

增加一个接口,并修改两个地方:

  1. 接口有增删改三个方法,与商品持久化类ProductDaoImpl的方法一致。

  1. 订单持久化类OrderDaoImpl和商品持久化类ProductDaoImpl都实现这个接口

  1. 代理类ProxyDao的成员变量改为接口,构造方法的形参也改为接口。

代码如下:

第四代代码

接口IGeneralDao

public interface IGeneralDao {
  void insert();
  void update();
  void delete();
 
}

代理类ProxyDao

public class ProxyDao {
  private IGeneralDao generalDao;
  public ProxyDao(IGeneralDao generalDao){
    this.generalDao=generalDao;
  }
  public void insert(){
    begin();
    generalDao.insert();
    last();
  }
  public void update(){
    begin();
    generalDao.update();
    last();
  }
  public void delete(){
    begin();
    generalDao.delete();
    last();
  }
  private void begin(){
    System.out.println("判断是否有操作权限");
    System.out.println("开始事务");
  }
  private void last(){
    System.out.println("结束事务");
    System.out.println("写日志");
  }
 
}

两个被代理的类

商品持久化类ProductDaoImpl

public class ProductDaoImpl implements IGeneralDao {
  public void insert(){
    System.out.println("新增商品");
  }
  public void update(){
    System.out.println("修改商品");
  }
  public void delete(){
    System.out.println("删除商品");
  }
 
}

订单持久化类OrderDaoImpl

public class OrderDaoImpl implements IGeneralDao {
  public void insert(){
    System.out.println("新增订单");
  }
  public void update(){
    System.out.println("修改订单");
  }
  public void delete(){
    System.out.println("删除订单");
  }
 
}

写到这里,是不是觉得想怎么代理就怎么代理了?轻松方便?

搞个测试?

测试类

public class ProxyTest {
  public static void main(String[] args) {

    ProxyDao proxyDao=new ProxyDao(new ProductDaoImpl());
    proxyDao.delete();
    System.out.println("-----------------------------");
          ProxyDao proxyDao2=new ProxyDao(new OrderDaoImpl());
          proxyDao2.insert();
   }
         
 }

运行结果:

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第6张图片

好象挺完美了?世界有我,我有世界~~

但是,你以为就这样结束了吗?告诉你,并没有~~~

那设计模式,不是开玩笑的,是老几辈优秀的科学家智慧的结晶,严谨得很的~~(注:是的,long long ago之前的程序员这活,只有科学家才干得了~~)

谁能思考出哪里不严谨吗?举手没奖。

那个,代理类ProxyDao的几个public方法,是不是要和被代理的类的方法一致呀?所以呢,ProxyDao是不是也可以和被代理的类实现同一个接口呢?这样就能保证两者的方法一致,不至于被写错了?请再跟着我念:面向接口编程~~

所以,最后一改,就是把ProxyDao类改成接口的实现类。

本次静态代理模式示例代码最终的完整呈现如下:

静态代理模式代码

接口

public interface IGeneralDao {
  void insert();
  void update();
  void delete();
 
}

代理类

public class ProxyDao implements IGeneralDao {
  private IGeneralDao generalDao;
  public ProxyDao(IGeneralDao generalDao){
    this.generalDao=generalDao;
  }
  public void insert(){
    begin();
    generalDao.insert();
    last();
  }
  public void update(){
    begin();
    generalDao.update();
    last();
  }
  public void delete(){
    begin();
    generalDao.delete();
    last();
  }
  private void begin(){
    System.out.println("判断是否有操作权限");
    System.out.println("开始事务");
  }
  private void last(){
    System.out.println("结束事务");
    System.out.println("写日志");
  }
 
}

两个被代理的类

商品持久化类

public class ProductDaoImpl implements IGeneralDao {

public void insert(){

System.out.println("新增商品");

}

public void update(){

System.out.println("修改商品");

}

public void delete(){

System.out.println("删除商品");

}

}

订单持久化类

public class OrderDaoImpl implements IGeneralDao {

public void insert(){

System.out.println("新增订单");

}

public void update(){

System.out.println("修改订单");

}

public void delete(){

System.out.println("删除订单");

}

}

测试类

public class ProxyTest {

public static void main(String[] args) {

IGeneralDao proxyDao=new ProxyDao(new ProductDaoImpl());

proxyDao.delete();

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

proxyDao=new ProxyDao(new OrderDaoImpl());

proxyDao.insert();

}

}

运行结果:

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第7张图片

好了,静态代理打完收工。都到这份上了,类图可以自己画否?

动态代理

前言

静态代理只能代理指定接口的实现类。即一个类只要实现了接口,就可以用一个实现了同一接口的代理类来代理它。那么问题来了,如果我有两个类,是分别实现了不同的接口,其内部方法各不相同,又都想有代理,且代理内容一样,怎么办呢?生成两个代理类?

比如,我们有一个订单管理类,一个商品管理类。订单管理类需要新增订单、更改订单状态、查询订单信息。商品管理类需要上架商品,下架商品,更新库存,查询商品信息。以上各业务每接受一次请求都需要写入日志。

我们学过静态代理模式,可以很快确定,写日志这件事,交给代理类去做,订单管理类和商品管理类专注核心功能即可。但,这就需要建两个代理类?这两个代理类干的边边角角的活还一样?明显不优雅了吧。

于是,动态代理他来了。

JDK提供了一种动态代理,只要被代理的类是实现了接口的类,就能被代理。注意:是只要实现了接口就可,不是要实现同一个接口才可。区别很大,思考一下就感觉到天地宽广了许多。

但有些类,它就是没有实现接口,又还是想要有代理类帮它处理边边角角怎么办呢?不慌,我们有CGLib动态代理。它不是java自带的,而是由第三方提供的优秀类库。CGLib动态代理的被代理类不需要实现接口,只要是能被继承类,都能被代理。(注:即被final修饰的类不能实现CGLib动态代理)

JDK动态代理

jdk动态代理,存在于java自带的核心内库,不需要引入jar包依赖什么的。

现在我们来回忆一下静态代理,代理类与被代理类实现了相同的接口,它们具有相同的方法进行代理和被代理。总结:我们需要代理类与被代理类的方法相同。

那么可否有一个方法,传出与被代理对象继承了同一个接口的代理对象。这样,我们就可以得到一个与被代理对象有同样的方法的代理对象了。

当然,方法得写在内中,那就建一个生产动态代理对象的类JdkDynamicProxy

  1. 被代理的对象还是设为类属性,通过构造方法传入。因为被代理类的类型不确定,设为Object类型。(注:别忘了所有实现了接口的类都可以做为被代理类,所以我们用顶级父类来接。)

  1. 方法getProxy()返回与被代理类实现了同一个接口的对象做为代理对象。同样,因为被代理类的类型不确定,这个方法的返回值也设为Object.

生产代理对象的类JdkDynamicProxy

public class JdkDynamicProxy {

//被代理对象

private Object obj;

//一个参数的构造方法,传入被代理对象

public JdkDynamicProxy(Object obj){

this.obj=obj;

}

/**

*

* @return 代理类对象。它与被代理对象实现同样的接口

*/

Object getProxy(){

return 返回一个与 obj实现了相同接口的对象,做为代理类;

}

来个客户端测试

public class JdkDynamicProxyTest {

public static void main(String[] args) {

JdkDynamicProxy jdkDynamicProxy=new JdkDynamicProxy(new ProductDaoImpl());

IGeneralDao proxy =(IGeneralDao) jdkDynamicProxy.getProxy();

proxy.delete();

}

}

好象架子搭起来了?

只是目前还有两个问题没解决

  1. 怎么造一个实现了被代理对象的接口的类,并生成一个代理对象出来。

  1. proxy.delete()调用的方法在哪里?怎么写代码?

先来解决第一个问题:怎么造一个实现了被代理对象的接口的类,并生成一个代理对象出来。

此时Proxy类闪亮登场。Proxy类在java.lang.reflect包中,使用它的静态方法newProxyInstance()就可以得到我们想要的代理对象。

Proxy.newProxyInstance方法

此方法需要传入三个参数,返回值是Object.很明显,返回的Object就是我们需要的代理对象。

参数​列表:

  1. 1. ClassLoader loader:被代理对象的类加载器,用于定义代理类

得到类加载器的代码实现:

ClassLoader classLoader=obj.getClass().getClassLoader();

  1. 2. Class[] interfaces:被代理对象实现的接口列表,代理类统统都要实现。(友情提醒,java类是可以实现多个接口的,所以这里是个数组)

得到接口列表代码实现:

Class[] interfaces=obj.getClass().getInterfaces();

事情进展到这里,我们已经得到了类加载器,可以造类了。也得到了接口列表,可以造一个把这列表里的接口统统实现了的类,没问题吧。类动态构造好了,类里的方法又怎么动态写呢?比如:delete()这个方法,怎么在代理对象里加上边边角角,在核心被代理类的delete()方法里走一圈,又回到代理对象里加边边角角呢?newProxyInstance方法的第三个参数帮你解决所有疑问。

  1. 3. InvocationHandler h:

InvocationHandler是一个接口,那我们就写一个这个接口的实现类,再new它的对象传进去试试先?

这个接口只有一个方法invoke()方法。我们需要一个实现了这个接口的对象做为参数传入,只要实现这一个方法就可以了。不管三七二十一,走一波看看效果。

InvocationHandler的实现类

注:暂时将这个类写成JdkDynamicProxy的内部类

/**

* InvocationHandler的实现类

*/

class InvocationHandlerImpl

implements InvocationHandler{

@Override

public Object invoke(

Object proxy,

Method method,

Object[] args)

throws Throwable {

//注意这句,测试效果看这里

System.out.println("成功了");

return null;

}

}

getProxy()方法

/**

* @param obj 被代理的对象

* @return 代理对象,它与被代理对象实现同样的接口

*/

public Object getProxy(){

//得到被代理对象的类加载器

ClassLoader classLoader=

obj.getClass().getClassLoader();

//得到被代理对象实现的接口列表

Class[] interfaces=

obj.getClass().getInterfaces();

Object o = Proxy.newProxyInstance(

classLoader,

interfaces,

new InvocationHandlerImpl()); //

return o;

}

客户端测试运行结果:

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第8张图片

显然有运行invoke方法

我们的俄罗斯套娃又进去一层,再来分析invoke()方法吧。

invoke方法

传入参数有三个

  1. Object proxy:代理对象

  1. Method method:要执行的方法(如delete)

  1. Object[] args: 要执行的方法的参数列表(此例中delete方法没有参数,则args为null)

返回值:

Object:执行的方法的返回值(此例中delete的返回值是void)

这个方法里要怎么干,好像也很明显了?反射,强大的无所不能的反射出现了。

我们有对象,有方法,还怕不能调用吗?不可能撒。于是我们把这个方法改成这样试一试呢

@Override

public Object invoke(Object proxy,

Method method,

Object[] args)

throws Throwable {

System.out.println("进来代理了");

Object result=method.invoke(obj);

System.out.println("我写日志了");

return result;

}

客户端测试,运行结果:

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第9张图片

代理了成功!

动态代理成功,但还没打完。

我们继续思考一下那个内部类,它的存在好象有些累赘?不想要它,觉得碍眼,不优雅。此时我们有两种解决方案。

1. 用JdkDynamicProxy来实现InvocationHandler接口,重写invoke方法。在调用newProxyInstance方法时传入this即可。

2. 在调用newProxyInstance方法时,第三个参数直接new一个匿名内部类对象,用这个匿名内部 类实现InvocationHandler接口,并重写invoke方法。

JDK动态代理代码:

生产代理对象的类JdkDynamicProxy(实现接口的方式)

顺手把边边角角也封装到begin 和last方法中

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

public class JdkDynamicProxy

implements InvocationHandler{

//被代理对象

private Object obj;

//一个参数的构造方法,传入被代理对象

public JdkDynamicProxy(Object obj){

this.obj=obj;

}

/**

*

* @return代理类对象。它与被代理对象实现同样的接口

*/

public Object getProxy(){

//得到被代理对象的类加载器

ClassLoader classLoader=

this.obj.getClass().getClassLoader();

//得到被代理对象实现的接口列表

Class[] interfaces=

this.obj.getClass().getInterfaces();

Object o = Proxy.newProxyInstance(

classLoader,

interfaces,

this);

return o;

}

@Override

public Object invoke(

Object proxy,

Method method,

Object[] args) throws Throwable {

begin();

Object result=method.invoke(obj);

last();

return result;

}

private void begin(){

System.out.println("进来代理了");

}

private void last(){

System.out.println("我写日志了");

}

}

生产代理对象的类JdkDynamicProxy(匿名内部类的方式)

注意这个方式中 调用Proxy.newProxyInstance()方法的第三个参数,是直接new的一个匿名类内部的对象,个人感觉有点乱,不是太推荐这种写法。

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

public class JdkDynamicProxy{

//被代理对象

private Object obj;

//一个参数的构造方法,传入被代理对象

public JdkDynamicProxy(Object obj){

this.obj=obj;

}

/**

*

* @return代理类对象。它与被代理对象实现同样的接口

*/

public Object getProxy(){

//得到被代理对象的类加载器

ClassLoader classLoader=

this.obj.getClass().getClassLoader();

//得到被代理对象实现的接口列表

Class[] interfaces=

this.obj.getClass().getInterfaces();

Object o = Proxy.newProxyInstance(

classLoader,

interfaces,

new InvocationHandler() {

@Override

public Object invoke(

Object proxy,

Method method,

Object[] args) throws Throwable {

begin();

Object result=method.invoke(obj);

last();

return result;

}

});

return o;

}

private void begin(){

System.out.println("进来代理了");

}

private void last(){

System.out.println("我写日志了");

}

}

客户端测试类

public class JdkDynamicProxyTest {

public static void main(String[] args) {

//将生成的代理类字节码文件写到磁盘上 路径在当前项目目录下 /com/sun/proxy目录下

System.getProperties().

put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

JdkDynamicProxy jdkDynamicProxy=new JdkDynamicProxy(new ProductDaoImpl());

IGeneralDao proxy =(IGeneralDao) jdkDynamicProxy.getProxy();

proxy.delete();

}

}

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 将生成的代理类字节码文件写到磁盘上 路径在当前项目目录下 /com/sun/proxy目录下

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第10张图片

JDK动态代理讲完

CGLib动态代理

今天讲另一种动态代理,CGLib动态代理。前面说过,JDK动态代理能代理实现了接口的类,那如果一些类没有实现任何接口,硬是需要动态代理一批批呢?这种情况,JAVA基本类库是不管的,但我们不慌。JAVA社区这么大,还能解决不了这问题?于是CGLib来了。

JDK动态代理的工作原理就是:根据接口,动态造一个实现类,实现一样的方法来达到代理的目的。

CGLib没有接口,那就反向思考,没有上一代我就造一个下一代。造个子类总能得相同的方法了吧。于是,造子类。动态造一个被代理类的子类做为代理类。

导包和依赖

这是一个第三方类库,所以我们要导包。

普通java项目导两个包

cglib-3.3.0.jar 和 asm-7.1.jar

记着别漏了asm-7.1.jar包,否则会报错

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第11张图片

maven项目呢,依赖如下:

cglib

cglib

3.3.0

准备工作完成,下面开始套娃讲解。

还是熟悉的配方,此时我们需要一个生产动态代理类,并造出代理对象的类。这个类得有一个属性,是被代理对象。

生产代理对象的类CGLibDynamicProxy类草稿

public class CGLibDynamicProxy {

//被代理对象

private Object obj;

//一个参数的构造方法,传入被代理对象

public CGLibDynamicProxy(Object obj){

this.obj=obj;

}

/**

*

* @return 代理对象。它与被代理对象实现同样的接口

*/

public Object getProxy(){

return proxy;

}

我们已知,getProxy方法需要造一个子类出来,再给这个子类造一个对象。那么,怎么造子类?

Enhancer类

这里会用到这个类的三个方法:

  1. 1. setSuperclass(Class) 字面意思:设置父类,形参是一个Class。很明显,把被代理类传进去就可以了。

  1. 2. setCallback(Callback) 形参是一个空接口Callback。这个什么用呢?下层套娃再说,先放这里。

  1. 3. create()。没有参数,返回一个Object。这个就是造代理类和对象的方法了。

现在,我们可以来稍微完善一下getProxy()方法了。

getProxy方法草稿

public Object getProxy(){

Enhancer enhancer=new Enhancer();

enhancer.setSuperclass(obj.getClass());

enhancer.setCallback(?);

Object proxy=enhancer.create();

return proxy;

}

事情进展到这里,根据已有经验,还缺处理代理的方法。MethodInterceptor接口亮相的时候到了。这个接口继承了Callback接口,且必须实现的方法就一个intercept()。

intercept方法

参数:

  1. Object 代理对象

  1. 2. Method 被代理对象要执行的方法

  1. 3. Object[] 被代理对象要执行的方法传入的参数列表

  1. 4. MethodProxy 生成的代理对象要执行的方法

返回值:

Object :执行的代理对象的方法的返回值

代码实现:

@Override

public Object intercept(Object o,

Method method,

Object[] objects,

MethodProxy methodProxy)

throws Throwable {

System.out.println("进来代理了");

Object result=method.invoke(obj,objects);

System.out.println("我写日志了");

return result;

}

此时就很明显了,我们需要一个实现了MethodInterceptor接口的类,在intercept方法中书写我们代理方法的代码。这个类自然也是实现了Callback接口的,于是可以做为Enhancer对象的setCallback方法参数。既然如此,我们不如就直接将CGLibDynamicProxy类做为MethodInterceptor接口的实现类。懒得再多写了嘛,而且还能保持优雅。

生产代理对象的类CGLibDynamicProxy类最终版

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLibDynamicProxy implements MethodInterceptor {

//被代理对象

private Object obj;

//一个参数的构造方法,传入被代理对象

public CGLibDynamicProxy(Object obj){

this.obj=obj;

}

/**

*

* @return 代理对象。它与被代理对象实现同样的接口

*/

public Object getProxy(){

Enhancer enhancer=new Enhancer();

enhancer.setSuperclass(obj.getClass());

enhancer.setCallback(this);

Object proxy=enhancer.create();

return proxy;

}

@Override

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

begin();

Object result=method.invoke(obj,objects);

last();

return result;

}

private void begin(){

System.out.println("进来代理了");

}

private void last(){

System.out.println("我写日志了");

}

}

客户端测试:

public class CGLibDynamicProxyTest {

public static void main(String[] args) {

CGLibDynamicProxy cgLibDynamicProxy=

new CGLibDynamicProxy(new ProductDaoImpl());

IGeneralDao proxy=

(IGeneralDao)cgLibDynamicProxy.getProxy();

proxy.insert();

}

}

运行结果:

代理模式(Java版)-静态代理、JDK动态代理和CGLib动态代理_第12张图片

整个代理模式,打完收工。

代理模式后传之海底捞

要不大家就以下场景写个代理?

约了朋友去海底捞吃饭,一去,发现人多得不得了,根本找不到位置。服务员小姐姐就特别热情的招呼我们,登记排队、搬凳子、拿水果、小吃、帮忙登记排队涂指甲什么的,搞得热闹得很。直到店里通知:有位置了(有资源),可以进来了。于是进店。

好不容易排队进去了,点菜之后继续等。服务员小哥哥又来忙了,端茶倒水、送热毛巾、上水果拼盘等等。终于厨房表示菜做好了(资源有了),于是上菜的小哥把菜上来了。

店里只提供两个资源:店中位置,菜品。

服务员就是代理,他们不断地询问有位没?有菜没?如果没有,他们就继承服务,直到我们需要的资源有了(店内座位或菜品),他们停止询问,我们获得资源。

你可能感兴趣的:(java,代理模式,设计模式)