代理(Proxy)设计模式

代理(Proxy)设计模式

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

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

举个例子:
例如: 有 A, B, C 三个类, A 原来可以调用 C 类的方法, 现在因为某种原因 C 类不允许A 类调用其方法,但 B 类可以调用 C 类的方法。 A 类通过 B 类调用 C 类的方法。这里 B 是 C的代理。 A 通过代理 B 访问 C。
原来的访问关系:
在这里插入图片描述
通过代理的访问关系:
在这里插入图片描述
再举个常见的例子:Window 系统的快捷方式也是一种代理模式。快捷方式代理的是真实的程序,双击快捷方式是启动它代表的程序。

代理模式的作用:
A、 控制访问
B、 增强功能

一、静态代理

所谓静态代理,代理类在程序运行前就已经定义好.java 源文件,其与目标类的关系在程序运行前就已经确立。 在程序运行前代理类已经编译为.class 文件。也就是说代理对象是程序员自己手动创建的。

现有如下需求:
用户需要购买 u 盘, u 盘厂家不单独接待零散购买,厂家规定一次最少购买 1000个以上,用户可以通过淘宝的代理商,那里进行购买。

  • 淘宝上的商品,微商都是 u 盘工厂的代理商, 他们代理对 u 盘的销售业务。

流程:用户购买(相当于上面的A类)-------代理商(淘宝)(相当于上面的B类) ----- u 厂家(闪迪等不同的厂家)(相当于上面的C类)

设计这个业务需要的类:

  1. 商家和厂家都是提供 sell 购买 u 盘的方法。定义购买 u 盘的接口 UsbSell
  2. 金士顿(King)对购买 1 千以上的价格是 85, 3 千以上是 80。 定义 UsbKingFactory 类,实现 UsbSell
  3. 定义淘宝的代理商 TaoBao ,实现 UsbSell
  4. 定义用户类,通过淘宝购买 u 盘

1. 购买 u 盘的接口 UsbSell

public interface UsbSell {
    //amount:购买的数量, float:单价
	public float sell(int amount) throws Exception;
}

2. 目标类 UsbKingFactory

目标类 UsbKingFactory(金士顿 u 盘),该类实现了业务接口。(金士顿 u 盘),该类实现了业务接口。

public class UsbKingFactory implements UsbSell {
    @Override
    public float sell(int amount) throws Exception {
        int price = 120; //一个的价格
        if (amount >= 3000) {
            price = 80;
        } else if (amount >= 1000) {
            price = 85;
        } else {
            throw new Exception("至少购买1000个");
        }
        return price;
    }
}

3. 创建代理类TaoBao

TaoBao 就是一个代理类, 代理厂家销售 u 盘,代理类在原有的功能上,对目标新增了功能

public class TaoBao implements UsbSell {
    //创建目标,厂家对象
    private UsbKingFactory factory = new UsbKingFactory();
    @Override
    public float sell(int amount) throws Exception {
        float price =  factory.sell(3000);
        //在单价之上,加入利润
        price = price + 25;
        return price;
    }
}

4. 创建用户类User

客户端调用者,购买商品类

public class User {
    public static void main(String[] args) {
        float price = 0;
        //购买u盘,通过代理商购买
        TaoBao taoBao  = new TaoBao();
        try {
            price = taoBao.sell(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("通过淘宝的代理商购买的单价:"+ price);
    }
}

5. 静态代理的缺点

  1. 代码复杂,难于管理
    代理类和目标类实现了相同的接口, 每个代理都需要实现目标类的方法, 这样就出现了大量的代码重复。如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
  2. 代理类依赖目标类,代理类过多
    代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理,静态代理在程序规模稍大时就无法胜任了,代理类数量过多。

二、JDK动态代理

动态代理是指代理类对象在程序运行时由 JVM 根据反射机制动态生成的。 动态代理不需要定义代理类的.java 源文件。通俗一点,动态代理其实就是 jdk 运行期间, 动态创建 .class 字节码并加载到 JVM

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

1. 相关类

(1)InvocationHandler 接口

InvocationHandler 接口叫做调用处理器,负责完调用目标方法,并增强功能。
通 过 代 理 对 象 执 行 目 标 接 口 中 的 方 法 , 会 把 方 法 的 调 用 分 派 给 调 用 处 理 器(InvocationHandler)的实现类,执行实现类中的 invoke()方法,我们需要把功能代理写在 invoke()方法中 。

package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个方法 invoke(),具体加强的代码逻辑就是定义在该方法中的。 通过代理对象执行接口中的方法时,会自动调用 invoke()方法。invoke()方法的介绍如下:

  • proxy:代表生成的代理对象
  • method:代表目标方法
  • args:代表目标方法的参数
(2)Method 类

上面InvocationHandler 接口中invoke()方法的第二个参数为 Method 类对象,该类有一个方法也叫 invoke()(不要与接口中的invoke()方法混淆),可以调用目标方法。这两个 invoke()方法,虽然同名,但无关。

  • public Object invoke ( Object obj, Object... args)
    • obj:表示目标对象
    • args:表示目标方法参数,就是InvocationHandler接口中的 invoke() 方法的第三个参数
      该方法的作用是:调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定。
(3)Proxy 类

通 过 JDKjava.lang.reflect.Proxy 类 实 现 动 态 代 理 , 会 使 用 其 静 态 方 法newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。

public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces,InvocationHandler handler)
  • loader:目标类的类加载器,通过目标对象的反射可获取
  • interfaces:目标类实现的接口数组,通过目标对象的反射可获取
  • handler: 调用处理器。

2. 新建一个接口,作为目标接口

jdk 动态代理是代理模式的一种实现方式,其只能代理接口。

public interface UsbSell {
    float sell(int amount) throws Exception;
}

3. 为接口创建一个实现类,是目标类

public class UsbKingFactory implements UsbSell {
    @Override
    public float sell(int amount) throws Exception {
        System.out.println("我是实现的目标方法UsbKingFactory");
        if(amount >= 3000){
            price = 80;
        } else if( amount >=1000){
            price= 85;
        } else {
            throw new Exception("至少购买1000个");
        }
        return price;
    }
}

4. 创建类实现 java.lang.reflect.InvocationHandler 接口

创建类实现 java.lang.reflect.InvocationHandler 接口,调用目标方法并增加其他功能代码
细节:

  • 创建一个私有的目标类对象(要增强的那个对象),通过构造方法传入目标对象
  • 接口的invoke()方法中调用method.invoke(target,args)实际上就是调用了目标类的方法
  • 注意:method.invoke(target,args)方法中的rarget不能传递proxy
public class MyInvocationHandler implements InvocationHandler {
	//目标类,即要增强的目标对象
    private Object target = null;

    //通过构造方法传入目标对象
    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    //实现代理对象要完成的功能,主要是:1.执行目标方法, 2.实现功能增强(利润)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("InvocationHandler中的invoke()");
        Object ret = null;
        System.out.println("在目标方法之前,记录日志,方法的执行时间等功能");
        //注意这里的第一个参数是target,而不是proxy,如果是proxy就成了递归调用
        ret =  method.invoke(target, 3000); //UsbKingFactory.sell(3000)
        System.out.println("在目标方法执行之后,增加的文件的处理功能");
        // 修改返回ret的值,增加利润
        if( ret != null){
            Float f =  (Float)ret;
            ret = f + 25.0F;
        }
        return ret;
    }
}

5. 创建动态代理对象Proxy

要点:

  • 用下面的代码把生成动态代理对象的class文件保存到磁盘上,位置在项目根目录下
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
    

完整代码:

public class ProxyApplication {
    public static void main(String[] args) throws Exception {

        //把生成动态代理对象的class文件保存到磁盘上,位置在项目根目录下
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

        //创建动态代理对象
        //1.创建目标对象
        UsbKingFactory factory  = new UsbKingFactory();
        //UsbSanFactory factory = new UsbSanFactory();

        //2.创建调用处理器对象
        InvocationHandler handler = new MyInvocationHandler(factory);

        //3.使用jdk中的方法创建 代理对象 (内存中的,通过参数保存到本地磁盘),类名为:com.sun.proxy.$Proxy0
        UsbSell taobao = (UsbSell) Proxy.newProxyInstance(
                factory.getClass().getClassLoader(),
                factory.getClass().getInterfaces(),
                handler);
        
        // 通过代理对象执行业务方法,会执行handler对象中的invoke()
        float price = taobao.sell(1); // super.h.invoke(this, m3, new Object[]{var1});
        System.out.println("通过taobao的代理商购买的价格:"+price);
    }
}

6. 生成的代理类对象

将保存到本地的代理类对象进行反编译,这里只选出了其中的一段代码,该代理类对象还重写了hashCode()/toString()/equals()这三个方法

private static Method m3;

public final double sale(Integer var1) throws Exception {
    try {
		//这里的h是指InvocationHandler接口的实现类
        return (Double)super.h.invoke(this, m3, new Object[]{var1});
    } catch (Exception | Error var3) {
        throw var3;
    } catch (Throwable var4) {
        throw new UndeclaredThrowableException(var4);
    }
}
static {
	m3 = Class.forName("com.bjpowernode.SaleUsb").getMethod("sale", Class.forName("java.lang.Integer"));
}

三、CGLIB动态代理

JDK动态代理是通过接口来生成代理对象,所以要求目标类必须实现了接口,而CGLIB是为没有实现接口的目标类来生成代理对象的,可以理解为通过继承实现

1. 创建目标类(父类)

注意这里没有实现接口

//工厂类,没有实现接口
public class UsbKingFactory  {
    public double sale(Integer count) throws Exception {
        if (count > 3000) {
            return 70;
        } else if (count > 1000) {
            return 90;
        } else {
            throw new Exception("小于1000不卖!!");
        }
    }
}

2. 创建代理对象

//CGLIB代理的原理是:子类增强父类,即CGLI会生成目标类的子类对象,来增强目标类
public class CGLIBProxyFactory implements MethodInterceptor {
    //目标对象
    private UsbKingFactory target;

    public CGLIBProxyFactory(UsbKingFactory target) {
        this.target = target;
    }

    //创建一个方法,该方法用于生成代理对象,返回值类型为父类类型(多态)
    public UsbKingFactory myProxyCreator() {
        //创建一个增强器对象
        Enhancer enhancer = new Enhancer();
        //指定要增强的目标类,即父类
        enhancer.setSuperclass(UsbKingFactory.class);
        //回调方法
        enhancer.setCallback(this);
        //生成器生成子类对象,即代理对象
        return (UsbKingFactory) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //调用父类的方法
        Double price = (Double) method.invoke(target, 3000);
        //新增的功能
        if (price != null) {
            price += 30;
        }
        return price;
    }
}

3. 用户类

public class User {
    public static void main(String[] args) throws Exception {
    	//生成的代理对象在内存中,让jvm保存到磁盘上,位置为第二个参数的位置
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");
        UsbKingFactory usbKingFactory = new UsbKingFactory();

        //生成的代理对象是UsbKingFactory的子类,通过多态用父类类型来接收
        UsbKingFactory taobaoProxy = new CGLIBProxyFactory(usbKingFactory).myProxyCreator();

        double price = taobaoProxy.sale(1);
        System.out.println(price);
    }
}

4. 生成的代理对象

除了实现了父类的方法外,还实现了Object类的finalize()/equals()/toString()/hashCode()/clone()方法

部分代码:

//继承了UsbKingFactory类
public class UsbKingFactory$$EnhancerByCGLIB$$4c3ae42b extends UsbKingFactory implements Factory {
	//上面第二步创建的对象
	private MethodInterceptor CGLIB$CALLBACK_0;

	static{
		CGLIB$sale$0$Method = ReflectUtils.findMethods(new String[]{"sale", "(Ljava/lang/Integer;)D"}, (var1 = Class.forName("com.bjpowernode.UsbKingFactory")).getDeclaredMethods())[0];
    	CGLIB$sale$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Integer;)D", "sale", "CGLIB$sale$0");
}

	//实现在父类的sale方法
	public final double sale(Integer var1) throws Exception {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
        	//调用了第二步对象中的intercept方法
        	//CGLIB$sale$0$Method为通过反射获取的父类中的sale方法
            Object var2 = var10000.intercept(this, CGLIB$sale$0$Method, new Object[]{var1}, CGLIB$sale$0$Proxy);
            return var2 == null ? 0.0D : ((Number)var2).doubleValue();
        } else {
            return super.sale(var1);
        }
    }
}

四、JDK动态代理实现自动生成接口的实现类

1. 创建一个接口

public interface HelloService {
    void sayHello(String name);
}

2. 生成代理类

public class Test {
    public static void main(String[] args) {
        HelloService helloService = (HelloService) Proxy.newProxyInstance(
                HelloService.class.getClassLoader(),
                //不能这么写HelloService.class.getInterfaces(),
                new Class[]{HelloService.class}, //注意这里的写法,将这个接口转为数组
                ((proxy, method, args1) -> {
                    System.out.println("Hello " + Arrays.toString(args1));
                    return null;
                })
        );
        helloService.sayHello("World");
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值