日常生活中代理

掌握的程度

  • 基于反射机制
  • 什么是动态代理
    • 使用jdk的反射机制,创建对象的能力,创建的代理类的对象,而不用你创建类文件,不用写java文件
    • 动态: 在程序执行时候,调用jdk提供的方法才能创建代理类的对象
  • 知道动态代理能做什么?

什么是动态代理

比如有一家美国的大学,可以对全世界进行招生。(代理,留学中介)

留学中介(代理): 帮助这家美国学校进行招生。中介是学校的代理,中介是代替学校完成招生的功能

代理特点:

  • 中介和代理他们要做的事情是一致的:招生
  • 中介是学校的代理,学校是目标
  • 家长 —-> 中介(学校介绍,办理入学手续) ——> 美国学校
  • 中介是代理,不能白干活,需要收取费用
  • 代理不让你访问到目标。

为什么要找中介?

  • 中介是专业的,方便
  • 家长现在不能自己去找学校,家长没有能力访问学校。或者美国学校不能接受个人来访

买东西都是商家卖,商家是某个商品的代理,你个人买东西,肯定不会让你接触到厂家的。

在开发中也会有这样的情况

  • 你有a类,本来是调用c类的方法,完成某个功能,但是c不让a调用
  • a —- 不能调用c类的方法
  • 在 a 和 c 直接创建一个 b 代理,c让b访问
  • a —> 访问b —-> 访问c

实际例子

  • 登录注册有验证码,验证码是手机短信
  • 你没有短信发送的能力,中国移动和中国联通可以发短信
  • 移动和联通有子公司或者关联公司,他们面向社会提供短信的发送功能
  • 张三项目要发送短信 —-> 子公司或者关联公司 —-> 中国移动,联通

    代理模式的作用

  • 功能增强: 在你的原有的功能上,增加了额外的功能,新增加的功能,叫做功能增强

  • 控制访问:代理类不让你访问目标,例如商家不让用户访问厂家

    静态代理

    特性:

  • 代理类是自己手工实现的,自己要创建一个java类,表示代理类

  • 同时,你所要的代理的目标类的确定的
  • 实现简单,理解容易

模拟一个用户购买u盘的行为

  • 用户是客户端类
  • 商家: 代理,代理某个拼盘的u盘
  • 厂家: 目标类
  • 三者的关系 : 用户—-> 商家 —-> 厂家
    • 商家和厂家都是卖u盘的,他们完成的功能一致

实现的步骤

  • 创建一个接口定义卖u盘的方法,表示厂家和商家做的事情
  • 创建厂家类,实现 1步骤的接口
  • 床架商家,就是代理,也是现实1步骤中的接口
  • 创建客户端类,调用商家 的方法卖一个u盘

    静态代理接口和目标类的创建

    创建接口

  1. package com.itvip666.proxy.service;
  2. /**
  3. * Created by gysui on 2020/10/11
  4. * 表示功能,厂家和商家都要完成的功能
  5. */
  6. public interface UsbSell {
  7. /**
  8. * 卖u盘的方法
  9. * @param amount 一次购买的数量
  10. * @return 表示一个u盘的价格
  11. */
  12. float sell(int amount);
  13. }

创建金士顿厂家

/**
 * Created by gysui on 2020/10/11
 * 目标类,金士顿厂家,不接受用户的单独购买
 */
public class UsbKingFactory implements UsbSell {
    /**
     * 卖u盘的方法
     *
     * @param amount 一次购买的数量
     * @return 表示一个u盘的价格
     */
    @Override
    public float sell(int amount) {
        // 1个128G U盘是85元
        // 后期可以根据 aumount 实现不同的价格,例如10000个,单价是80,50000个是75
        return 85.0f
                ;
    }
}

创建淘宝代理

package com.itvip666.proxy.service.shangjia;

import com.itvip666.proxy.service.UsbSell;
import com.itvip666.proxy.service.factory.UsbKingFactory;

/**
 * Created by gysui on 2020/10/11
 * 淘宝是一个商家,代理金士顿u盘的销售
 */
public class TaoBao implements UsbSell {
    // 声明 商家代理的厂家具体是谁
    private UsbSell factory = new UsbKingFactory();

    /**
     * 实现销售u盘的功能
     *
     * @param amount 一次购买的数量
     * @return 表示一个u盘的价格
     */
    @Override
    public float sell(int amount) {
        // 向厂家发送订单,告诉厂家,我买了u盘,厂家发货
        float price = factory.sell(amount);
        // 商家需要加价,也就是代理要增加价格
        // 增强功能,代理类在完成目标类方法调用后,增强的功能
        // 在目标类的方法调用后,你做的其他功能,都是增强的意思
        price = price + 25;
        return price;
    }
}

客户端购买u盘

package com.itvip666.proxy;

import com.itvip666.proxy.service.shangjia.TaoBao;

/**
 * Created by gysui on 2020/10/11
 */
public class ShopMain {
    public static void main(String[] args) {
        // 创建代理的淘宝对象
        TaoBao taoBao = new TaoBao();
        float price = taoBao.sell(234);
        System.out.println("通过淘宝的商家,购买u盘的单价 = " + price);
    }
}

代理类完成的功能

  • 目标类中方法调用
  • 功能增强

    静态代理缺点

当你的项目中目标类和代理类有很多的时候,有以下缺点:

  • 当目标类增加,代理类可能也需要成倍的增加,代理类数量过多
  • 当您的接口中,功能增加了,会影响众多的实现类 (厂家类和代理类都得需要修改),影响的比较多

    动态代理概念

动态代理

  • 在程序执行的过程中,使用JDK的反射机制,创建代理类对象,并动态的指定要代理目标类 (厂家)
  • 换句话说,动态代理是一种创建java对象的能力,让你不用创建taobao类,就能创建代理对象

  动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。

本文主要介绍Java中两种常见的动态代理方式:JDK原生动态代理和CGLIB动态代理。

动态代理优点

  1. 在静态代理中目标类很多的时候,可以使用动态代理,避免静态代理的缺点
  2. 动态代理中目标类即使很多,代理类数量可以很少,当你修改了接口中的方法时候,不会影响代理类

    动态代理实现方式

    JDK动态代理(理解)

使用java反射包中的类和接口实现动态代理的功能

反射包 java.lang.reflect,里面有三个类
- InvocationHandler
- Method
- Proxy

使用JDK的 Proxy实现代理

  • 要求目标类与代理类实现相同的接口。若目标类不存接口,则无法使用该方式实现
  • 对于无接口的可以使用 CGLIB动态代理

    CGLIB动态代理(了解)

  • cglib是第三方的工具库,创建代理对象

  • cglib的原理是继承,cglib通过继承目标类,创建它的子类,在子类中重写父类中同名的方法,实现功能的修改
  • 因为cglib是继承,重写方法,所以要求目标类不能是 final的,方法也不是final的
  • cglib要求目标类比较宽松,只要能继承就可以了,cglib在很多框架中使用,比如mybatis,spring框架中都有使用

    动态代理制反射 Method

Method类,表示方法。类中的方法,通过Method可以执行某个方法

/**
 * Created by gysui on 2020/10/11
 */
public class TestMain {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//        HelloService helloService = new HelloServiceImpl();
//        helloService.say("杨洋");

        // 使用反射机制执行say方法, 核心 Method (类中的方法)
        HelloService target = new HelloServiceImpl();
        Method method = HelloService.class.getMethod("say", String.class);
        // 通过method可以执行say的方法的调用
        /**
         * 表示执行方法的调用
         * 参数1: 表示对象,要执行这个对象的xxx方法
         * 参数2: 方法的参数
         * 返回值: Object表示方法执行后的返回值
         */
        method.invoke(target,"小阳");
    }
}

说明

  • method.invoke() 就是用来执行目标方法的,等同于静态代理中的
// 向厂家发送订单,告诉厂家,我买了u盘,厂家发货
float price = factory.sell(amount);

InvocationHandler (调用处理器)

  • 是一个接口,就有一个方法 invoke()
    • invoke() 表示代理对象要执行的功能代码
    • 你的代理类要完成的功能就写在invoke()方法中
  • 代理类完成的功能
    • 调用目标方法,执行目标方法
    • 功能增强: 在目标方法调用后需要做的事情

参数:

  • Object proxy : jdk创建的代理对象,无需赋值
  • method : 目标类中的方法 , jdk提供的method对象
  • args : 目标类中的方法参数 ,Jdk提供
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

怎么用:(== InvocationHandler 接口表示代理要做什么==)

  • 创建一个类实现接口 InvocationHandler
  • 重写 invoke()方法,把原来静态代理中代理类要完成的功能,写在这里

    反射包Proxy类 (核心)

作用: 创建代理对象

之前创建对象方式

  • new 类的构造方法

现在我们使用Proxy类的方法,代替new的使用

方法: 静态方法 newProxyInstance()
作用: 创建代理对象,等同于静态代理对象 new TaoBao()

参数

  • ClassLoader loader : 类加载器,负责向内存中加载对象的
    • 使用反射获取对象的 ClassLoader
    • 比如有一个类a , a.getClass().getClassLoader()
    • 这个是目标对象的类加载器
  • Class<?>[] interfaces : 目标所实现的接口,也是通过反射获取的
  • InvocationHandler h : 我们自己写的,代理类要完成的功能
  • 返回值就是代理对象
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

jdk动态代理实现

步骤:

  • 创建接口,定义目标类要完成的功能
  • 创建目标类实现接口
  • 创建 InvocationHandler 接口的实现类,在invoke方法中完成代理类的功能
    • 调用目标方法
    • 增加功能
  • 使用 Proxy类的静态方法来创建代理对象,并把返回值转为接口类型

目标接口

/**
 * Created by gysui on 2020/10/11
 * 目标接口
 */
public interface UsbSell {
    float sell(int amount);
}

金士顿工厂接口

/**
 * Created by gysui on 2020/10/11
 * 目标类
 */
public class UsbKingFactory implements UsbSell {
    /**
     * 目标方法
     * @param amount
     * @return
     */
    @Override
    public float sell(int amount) {
        System.out.println("目标类中执行了sell目标方法");
        return 85.0f;
    }
}

InvocationHandler 接口的实现类

/**
 * Created by gysui on 2020/10/11
 * 必须实现 InvocationHandler接口,完成代理类要做的功能
 * 1. 调用目标方法
 * 2. 功能增强
 */
public class MySellHandler implements InvocationHandler {
    private Object target = null;

    /**
     * 动态代理: 目标对象是活动的,不是固定的,需要传进来
     * 传入谁,就给谁创建代理
     * @param target
     */
    public MySellHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res = null;
        // 向厂家发送订单,告诉厂家,我买了u盘,厂家发货
        // 执行目标方法
        res = method.invoke(target,args);
        // 商家需要加价,也就是代理要增加价格
        // 增强功能,代理类在完成目标类方法调用后,增强的功能
        // 在目标类的方法调用后,你做的其他功能,都是增强的意思
        // price = price + 25;

        if (res != null) {
            Float price = (Float) res;
            price = price + 25;
            res = price;
        }
        return res;
    }
}

使用 Proxy类的静态方法来创建代理对象,并把返回值转为接口类型

/**
 * Created by gysui on 2020/10/11
 */
public class ProxyMain {
    public static void main(String[] args) {
        // 1. 创建目标对象
        UsbSell usbSell = new UsbKingFactory();
        // 2. 创建InvocationHandler对象
        InvocationHandler invocationHandler = new MySellHandler(usbSell);
        // 3. 创建代理
        UsbSell proxy = (UsbSell) Proxy.newProxyInstance(usbSell.getClass().getClassLoader(),
                usbSell.getClass().getInterfaces(), invocationHandler);
        // com.sun.proxy.$Proxy0 这是jdk动态代理创建的对象类型
        System.out.println("proxy = " + proxy.getClass().getName());

        // 4. 通过代理执行方法
        float sell = proxy.sell(1);
        System.out.println("通过动态代理对象,调用方法 = " + sell);

    }
}

动态代理项目中的应用

在程序开发中的意思:

  • 比如你所在的项目中,有一个功能是其他人写好的 (公司的其他部门或者其他小组)
    • GoNong.class , GoNong gn = new GONong() gn.print()
    • 但是现在你发现,现在功能缺点,不能完全满足我项目的需要,需要在gn.print执行后,需要自己增加代码
    • 用代理实现gn.print()调用时候,增加自己代码,而不是改原来的GoNong文件