代理模式

焦旭峰 于 June 15, 2021

代理模式

1、代理模式

代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。

换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。客户类真正的想要访问的对象是目标对象,但客户类真正可以访问的对象是代理对象。客户类对目标对象的访问是通过访问代理对象来实现的。当然,代理类与目标类要实现同一个接口。

例如:

原来的访问关系:

在这里插入图片描述

通过代理的访问关系:

在这里插入图片描述

有 A,B,C 三个类, A 原来可以调用 C 类的方法, 现在因为某种原因 C 类不允许A 类调用其方法,但 B 类可以调用 C 类的方法。A 类通过 B 类调用 C 类的方法。这里 B 是 C的代理。 A 通过代理 B 访问 C。

1.1、代理模式的作用

1.1.1、控制访问

代理类不让客户类访问目标类。例如商家(代理类)不让用户(客户类)访问厂家(目标类)。

1.1.2、增强功能

在原有功能的基础上,增加额外的功能。 新增加的功能,叫做增强功能。

1.2、代理模式的分类

可以将代理模式分为静态代理和动态代理。

2、静态代理

静态代理是指,代理类在程序运行前就已经定义好.java 源文件,其与目标类的关系在程序运行前就已经确立。在程序运行前代理类已经编译为.class 文件。

2.1、静态代理的使用

(1)定义目标接口

package com.scut.service;
// 表示功能的,厂家,商家都要完成的功能
public interface UsbSell {
    //定义方法 参数 amount:表示一次购买的数量,暂时不用
    //返回值表示一个u盘的价格。
    float sell(int amount);
}

(2)定义目标类

package com.scut.factory;
import com.scut.service.UsbSell;
//目标类: 闪迪厂家, 不接受用户的单独购买。
public class UsbSanFactory implements UsbSell {
    @Override
    public float sell(int amount) {
        System.out.println("闪迪 目标类中的方法调用 , UsbSanFactory 中的sell ");
        //一个128G的u盘是 85元。
        //后期根据amount ,可以实现不同的价格,例如10000个,单击是80, 50000个75
        return 85.0f;
    }
}

(3)定义代理类

package com.scut.shangjian;

import com.scut.factory.UsbKingFactory;
import com.scut.factory.UsbSanFactory;
import com.scut.service.UsbSell;

//taobao是一个商家
public class TaoBaoSanDi implements UsbSell {

    //声明商家代理的厂家具体是谁
    private UsbSanFactory factory = new UsbSanFactory();

    @Override
    //实现销售u盘功能
    public float sell(int amount) {
        //向厂家发送订单,告诉厂家,我买了u盘,厂家发货
        float price = factory.sell(amount); 
        price = price + 25; //增强功能,代理类在完成目标类方法调用后,增强了功能。
        //在目标类的方法调用后
        System.out.println("淘宝商家,给你返一个优惠券,或者红包");

        //增加的价格
        return price;
    }
}

(4)定义调用类

package com.scut;
import com.scut.shangjian.TaoBao;
import com.scut.shangjian.WeiShang;

public class ShopMain {

    public static void main(String[] args) {
        //创建代理类对象
        TaoBaoSanDi taoBao = new TaoBaoSanDi();
        //调用代理类对象的方法
        float price = taoBao.sell(1);
        System.out.println("购买u盘单价:"+price);
    }
}

2.2、静态代理的缺点

(1)代码复杂,难于管理

代理类和目标类实现了相同的接口,每个代理都需要实现目标类的方法,这样就出现了大量的代码重复。如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

(2)代理类依赖目标类,代理类过多

代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理,静态代理在程序规模稍大时就无法胜任了,代理类数量过多。

3、动态代理

动态代理是指代理类对象在程序运行时由 JVM 根据反射机制动态生成的。动态代理不需要定义代理类的.java 源文件。动态代理其实就是JDK运行期间,动态创建 class 字节码并加载到 JVM。动态代理的实现方式常用的有两种:使用 JDK 代理代理,与通过 CGLIB 动态代理。

3.1、JDK动态代理

JDK动态代理是基于 Java的反射机制实现的。使用JDK中接口和类实现代理对象的动态创建。JDK 的动态要求目标对象必须实现接口,这是 Java设计上的要求。 从 JDK1.3 以来,java 语言通过 java.lang.reflect 包提供三个类支持代理模式 Proxy, Method和 InovcationHandler

3.1.1、JDK动态代理的使用

(1)定义目标接口
package com.scut.service;

//目标接口
public interface UsbSell {

    float sell(int amount);

    void print();
}

(2)定义目标类
package com.scut.factory;

import com.scut.service.UsbSell;

//目标类
public class UsbKingFactory implements UsbSell {
    @Override
    public float sell(int amount) {
        //目标方法
        System.out.println("目标类中,执行sell目标方法");
        return 85.0f;
    }

    @Override
    public void print() {

    }
}
(3)定义InvocationHandler接口实现类
package com.scut.handler;

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

//必须实现InvocationHandler接口,完成代理类要做的功能(1.调用目标方法,2.功能增强)
public class MySellHandler implements InvocationHandler {

    private Object target = null;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object res  = null;
        //向厂家发送订单,告诉厂家,我买了u盘,厂家发货
        //float price = factory.sell(amount); //厂家的价格。
        res =  method.invoke(target,args); //执行目标方法



        //商家 需要加价, 也就是代理要增加价格。
        //price = price + 25; //增强功能,代理类在完成目标类方法调用后,增强了功能。
        if( res != null ){
            Float price = (Float)res;
            price = price + 25;
            res = price;
        }

        //在目标类的方法调用后,你做的其它功能,都是增强的意思。
        System.out.println("淘宝商家,给你返一个优惠券,或者红包");
        //记录数据库

        //增加的价格
        return res;

    }
}

(4)创建动态代理对象
package com.scut;

import com.scut.factory.UsbKingFactory;
import com.scut.handler.MySellHandler;
import com.scut.service.UsbSell;

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

public class MainShop {

    public static void main(String[] args) {
        //创建代理对象,使用Proxy
        //1. 创建目标对象
        // UsbKingFacotry  factory = new UsbKingFactory();
        UsbSell factory = new UsbKingFactory();
        //2.创建InvocationHandler调用处理器对象
        InvocationHandler handler = new MySellHandler(factory);

        //3.创建代理对象
        UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
                factory.getClass().getInterfaces(),
                handler);

        //com.sun.proxy.$Proxy0 : 这是jdk动态代理创建的对象类型。
        System.out.println("proxy:"+proxy.getClass().getName());
        //4.通过代理执行方法
        float price = proxy.sell(1);
        System.out.println("通过动态代理对象,调用方法:"+price);
    }
}

3.2、CDLIB动态代理

CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP。

使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用 CGLIB 来实现。

CGLIB 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 的类。

CGLIB 经常被应用在框架中,例如 Spring ,Hibernate 等。CGLIB 的代理效率高于 JDK。对于CGLIB 一般的开发中并不使用。简单了解即可。