ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

代理模式,静态代理与动态代理区别、各自实现,以及动态代理的源码分析

2021-09-23 13:32:38  阅读:93  来源: 互联网

标签:实现 代理 对象 源码 Proxy 动态 方法 public


一、什么是代理

代理就是我们指定一个工具类,这个类作为服务申明(一个接口)和服务具体实现(继承这个接口并实现方法)的第三者,它也实现这个服务接口,同时需要把这个服务具体实现放入其中,但是实现该服务的方法时,通过调用这个服务具体实现的方法,同时在该方法前后提供额外的增强服务来提高服务的力度,从而实现方法的增强。
在这里插入图片描述

二、静态代理

概念:

所谓静态代理就是上述的代理的实现由我们自己手动实现,在新增服务的时候需要在这个代理中心手动增加代理服务的实现者并在后面实例化代理调用它,下面是一个例子:

需求:现在需要两个服务,一个是需要购买衣服,一个是需要购买汽车。

  1. 根据单一职责原则我们需要先声明两个接口以表达两个任务:
//汽车销售服务声明
public interface CarGoodCenter {
    void saleCar(String carName);
}
//衣服销售服务声明
public interface ClothGoodCenter {
     void sale(String name);
}
  1. 根据服务需求,我们需要分别创建具体的实现类去实现对应的需求:
//实现卖车子服务
public class CarSaleCompany implements CarGoodCenter{
    @Override
    public void saleCar(String carName) {
        System.out.println("汽车公司出售了一台"+carName);
    }
}
//实现卖衣服
public class ClothSaleCompany implements ClothGoodCenter {
    @Override
    public void sale(String name) {
        System.out.println("衣服公司生产售出产品"+name);
    }
}
  1. 最后,我们需要一个代理,去帮我们实现买衣服、买车子的任务。
public class StaticProxy implements ClothGoodCenter,CarGoodCenter {
    //需要代理的公司
    public ClothSaleCompany clothSaleCompany;
    public CarSaleCompany carSaleCompany;

    public StaticProxy(ClothSaleCompany company) {
        this.clothSaleCompany = company;
    }

    public StaticProxy(CarSaleCompany carSaleCompany) {
        this.carSaleCompany = carSaleCompany;
    }

    @Override
    public void sale(String name) {
        System.out.println("衣服代理对象对方法做前置增强");
        clothSaleCompany.sale(name);
        System.out.println("衣服代理对象对方法做后置增强");
    }


    @Override
    public void saleCar(String carName) {
        System.out.println("汽车公司代理对象对方法做前置增强");
        carSaleCompany.saleCar(carName);
        System.out.println("汽车对代理对象对方法做后置增强");
    }
}

我们首先向代理注册了这两个服务的具体实现公司,然后实现了这两个服务声明的接口,而在接口方法的具体实现时,我们通过两个公司去分别实现方法,但是在实现方法的前后增加了额外的方法以达到增强对应方法的目的。

  1. 测试实现:
public class Test {
    public static void main(String[] args) {
        ClothSaleCompany clothSaleCompany = new ClothSaleCompany();
        CarSaleCompany carSaleCompany =  new CarSaleCompany();

        StaticProxy clothStaticProxy = new StaticProxy(clothSaleCompany);
        StaticProxy carStaticProxy = new StaticProxy(carSaleCompany);

        clothStaticProxy.sale("皮带");
        carStaticProxy.saleCar("凯迪拉克");
    }
}
  1. 结果:

在这里插入图片描述

静态代理的问题:

  • 由于代理的服务需要手动去引入具体实现了服务方法的对象,并且要实现服务的生命接口,此时当需要实现的服务大量增加时就会出现要实现大量接口并且引入大量服务方法实现对象,造成很大的冗余。
  • 当一个或一些服务的需求改变时,要改动的代码里太大,工作量大。

出于上述原因,我们引入动态代理实现服务。

动态代理

概念

动态代理的需求和静态代理一样,但是在代理的实现上不同,代理实现时不再手动的在代理类中引入需要实现方法的对象,并且不用手动的书写需要代理增强的方法。

核心原理

  • 原来在代理类中需要引入的实现了服务声明接口的具体的方法的对象如上面提到的CarSaleCompany和ClothSaleCompany不在手动引入,而是通过反射分别动态创建代理对象。
  • 对需要增强的服务方法也不需要手动编写在代理中,而是通过上面创建的动态代理对象调用它从Proxy类继承的InvocationHandler h对象中的invoke方法,通过反射的方式运行了增强的方法。这个invoke方法就是我们自己定义的继承了InvocationHandler 的动态代理类中实现的invoke方法(在获取动态代理类时把本代理传入以获取代理对象)。

例子:

  1. 根据单一职责原则我们需要先声明两个接口以表达两个任务:
//汽车销售服务声明
public interface CarGoodCenter {
    void saleCar(String carName);
}
//衣服销售服务声明
public interface ClothGoodCenter {
     void sale(String name);
}
  1. 根据服务需求,我们需要分别创建具体的实现类去实现对应的需求:
//实现卖车子服务
public class CarSaleCompany implements CarGoodCenter{
    @Override
    public void saleCar(String carName) {
        System.out.println("汽车公司出售了一台"+carName);
    }
}
//实现卖衣服
public class ClothSaleCompany implements ClothGoodCenter {
    @Override
    public void sale(String name) {
        System.out.println("衣服公司生产售出产品"+name);
    }
}
  1. 动态代理对象的声明
public class DynamicProxy implements InvocationHandler {
    //需要代理的公司
    Object company;

    public Object getCompany() {
        return company;
    }

    //设置需要代理的对象
    public void setCompany(Object company) {
        this.company = company;
    }

    //获得一个动态代理对象
    public Object getProxy(){
        return Proxy.newProxyInstance(company.getClass().getClassLoader(), company.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("动态代理对象的前置增强方法");
        Object set = method.invoke(company, args);//调用需要代理对象的方法
        System.out.println("动态代理对象的后置增强方法");
        return set;
    }
}

解析:可以看到,我们声明了一个Object类型的company,这个对象在静态代理中是由我们手动引入的,在动态代理中我们在具体场景通过set方法引入具体的实现类,从而避免修改代理类的代码,符合了开闭原则。我们在使用时通过getProxy方法获得一个动态代理对象,这个动态对象继承了Proxy类,即拥有了Proxy类中的InvocationHandler h字段,在实现时实际这个InvocationHandler h就是我们自己编写的代理类。当需要调用方法时这个代理对象会通过h.invoke()的方式调用我们自己定义的invoke方法,从而实现了服务方法的增强。

  1. 测试实现
public class Test {
    public static void main(String[] args) {

        ClothGoodCenter clothSaleCompany = new ClothSaleCompany();
        DynamicProxy dynamicProxy = new DynamicProxy();
        dynamicProxy.setCompany(clothSaleCompany); //设置需要代理的公司
        ClothGoodCenter proxy = (ClothGoodCenter) dynamicProxy.getProxy(); //获得衣服公司动态代理对象
        proxy.sale("裤子");

        CarGoodCenter carSaleCompany = new CarSaleCompany();
        dynamicProxy.setCompany(carSaleCompany); //设置需要代理的汽车公司
        CarGoodCenter carProxy = (CarGoodCenter) dynamicProxy.getProxy();//获得汽车公司动态代理对象
        carProxy.saleCar("凯迪拉克");


    }
}
  1. 结果
    在这里插入图片描述

源码分析

getProxy()

这里最关键的地方就是我们自己定义的继承了InvocationHandler的代理,其中 getProxy()方法通过调用Proxy的静态方法返回了一个动态代理对象,即

Proxy.newProxyInstance(company.getClass().getClassLoader(), company.getClass().getInterfaces(),
                this);

原型:
在这里插入图片描述
可以看到需要传入一个类加载器,这个 类加载器和我们传入的服务实现对象的类加载器是同一个;传入服务实现对象实现的接口,这两个东西用于创建动态带对象的类;而最后一个参数InvocationHandler用于给继承了Proxy类的动态代理对象中的InvocationHandler赋值。(由于Proxy类规定了通过它的静态方法生成的动态代理对象必须继承Proxy类,同时Proxy类中有一个InvocationHandler字段)
Proxy类中的InvocationHandler如下
在这里插入图片描述

newProxyInstance

Proxy.newProxyInstance这个方法中会根据传入的值生成对应的代理对象的类。如下:
在这里插入图片描述
其中getProxyClass0(loader,intfs)方法会根据传入的类加载器和接口生成对应的类。

进入,情况如下:
在这里插入图片描述
可以看到这个方法首先判断接口长度是否合法,然后会调用proxyClassCache.get(loader, interfaces)。
进入proxyClassCache.get(loader, interfaces)方法:
在这里插入图片描述
可以看到经过一系列的判断后调用核心的方法subKeyFactory.apply(key, parameter),随后进入下列方法:
在这里插入图片描述
可以看到经过一些列的对接口的判断和操作后有如下方法:
在这里插入图片描述
其中proxyClassNamePrefix = " P r o x y " , p r o x y P k g = n u l l 这 是 给 要 生 成 的 动 态 代 理 类 的 取 名 , 格 式 就 是 Proxy",proxyPkg=null这是给要生成的动态代理类的取名,格式就是 Proxy",proxyPkg=null这是给要生成的动态代理类的取名,格式就是Proxy+num的形式,如 P r o x y 0 、 Proxy0、 Proxy0、Proxy1
随后,下述方法生成了类的字节码并存在一个字节数组中:
在这里插入图片描述
最后,通过下述方法,生成了动态代理类
在这里插入图片描述
其中,defineClass0方法中的内容如下:
在这里插入图片描述
这是调用了系统的本地方法,生成对应的类

最后,在Proxy.newProxyInstance方法中,下述方法根据生成的动态代理对象的class,通过反射的方式生成了动态代理对象。
在这里插入图片描述
至此,动态代理对象就生成成功。
下面给出通过存储class的字节数组反编译出的类的情况:
在这里插入图片描述
其中存在我们自己定义的服务方法,并通过h.invoke调用
在这里插入图片描述
声明:文中代理原理图和反编译类图来源于享课堂Lison老师的课件

标签:实现,代理,对象,源码,Proxy,动态,方法,public
来源: https://blog.csdn.net/qq_41635401/article/details/120432506

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有