java实战,轻松理解魔法般的代理模式

简介: 最近写了和内省的文章,就想继续深入再写一篇介绍java的代理模式的文章,因为java的代理模式也会用到反射。

前言

最近写了和内省的文章,就想继续深入再写一篇介绍java的代理模式的文章,因为java的代理模式也会用到反射。


什么是代理模式

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

其实代理模式是一种和编程语言无关的设计模式,但是二当家的只有java掌握的还行,所以还是限定在java范围吧。


代理模式中的角色

在这里插入图片描述


抽象主题角色

抽象主题角色是通过接口或抽象类声明真实主题角色需要实现的业务方法。是客户端仅需要关心的角色。

例子中,我们用笔记本卖家作为抽象主题角色。

package com.secondgod.proxy;

/**
 * 笔记本卖家(抽象主题角色)
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public interface INotebookSeller {

    /**
     * 卖笔记本
     *
     * @param quantity
     */
    void sell(int quantity);
}

真实主题角色

真实主题角色就是对抽象主题角色声明的业务方法的真实实现者。

例子中,我们用笔记本工厂作为真实主题角色。

package com.secondgod.proxy;

import java.text.MessageFormat;

/**
 * 笔记本工厂(真实主题)
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class NotebookFactory implements INotebookSeller {
    /**
     * 厂长名
     */
    private final String name;

    public NotebookFactory(String name) {
        this.name = name;
        System.out.println(MessageFormat.format("[{0}]的笔记本工厂开业了。", name));
    }

    @Override
    public void sell(int quantity) {
        System.out.println(MessageFormat.format("[{0}]卖掉[{1}]个笔记本。", name, quantity));
    }
}

客户端角色

客户端角色是需要使用抽象主题角色声明的功能的角色。

例子中,我们用买家作为客户端角色。

package com.secondgod.proxy;

import java.text.MessageFormat;

/**
 * 打工人
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Worker {
    /**
     * 打工人的名字
     */
    private final String name;

    public Worker(String name) {
        this.name = name;
        System.out.println(MessageFormat.format("打工人[{0}],为了得到自己想要的东西拼命赚钱。", name));
    }

    public void buy(INotebookSeller seller, int quantity) {
        System.out.println(MessageFormat.format("打工人[{0}]要买[{1}]个笔记本。", name, quantity));
        seller.sell(quantity);
    }

    private static void newDays() {
        System.out.println();
        System.out.println("有一天:");
    }

    public static void main(String[] args) {
        // 打工人的故事开始了
        newDays();
        INotebookSeller seller = new NotebookFactory("大当家的");
        Worker worker = new Worker("小明");
        worker.buy(seller, 5);
    }
}

为了减少一个类,我就直接把main方法写在客户端角色了。
在这里插入图片描述

一般情况下,这样就已经圆满了。但是好景不长,因为工厂都开在比较偏僻的地方,大家买笔记本太不方便了。于是需求出现了,机会也出现了,然而工厂和打工人都不愿意因此做出改变,那样代价太大了。


静态代理的出现

代理角色作为客户端角色和真实主题角色的中介。

例子中,我们用商店作为代理角色。

package com.secondgod.proxy;

import java.text.MessageFormat;

/**
 * 商店(代理主题)
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Store implements INotebookSeller {
    /**
     * 名称
     */
    private final String          name;
    /**
     * 供应商品的工厂
     */
    private final NotebookFactory notebookFactory;
    /**
     * 合同约定按照固定量的倍数采购
     */
    private final int             purchaseQuantity;
    /**
     * 库存
     */
    private       int             stockBalance;

    public Store(String name, NotebookFactory notebookFactory, int purchaseQuantity) {
        this.name = name;
        this.notebookFactory = notebookFactory;
        this.purchaseQuantity = purchaseQuantity;
        System.out.println(MessageFormat.format("[{0}]的笔记本商店开业了,请笔记本工厂做了自己的供应商,他们约定每次按照[{1}]个的倍数采购。", name, purchaseQuantity));
    }


    @Override
    public void sell(int quantity) {
        if (quantity > stockBalance) {
            int needPurchaseQuantity = ((quantity - stockBalance) + (purchaseQuantity - 1)) / purchaseQuantity * purchaseQuantity;
            System.out.println(MessageFormat.format("商店笔记本库存不足,于是向供应商采购了[{0}]个。", needPurchaseQuantity));
            notebookFactory.sell(needPurchaseQuantity);
            stockBalance += needPurchaseQuantity;
        }
        stockBalance -= quantity;
        System.out.println(MessageFormat.format("商店卖出了[{0}]个笔记本,库存余[{1}]个。", quantity, stockBalance));
    }
}

我们还需要对main方法做一点修改来引入代理角色。需要注意的是,我们的客户端角色,抽象主题角色和真实主题角色都不需要做修改。

package com.secondgod.proxy;

import java.text.MessageFormat;

/**
 * 打工人
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Worker {
    /**
     * 打工人的名字
     */
    private final String name;

    public Worker(String name) {
        this.name = name;
        System.out.println(MessageFormat.format("打工人[{0}],为了得到自己想要的东西拼命赚钱。", name));
    }

    public void buy(INotebookSeller seller, int quantity) {
        System.out.println(MessageFormat.format("打工人[{0}]要买[{1}]个笔记本。", name, quantity));
        seller.sell(quantity);
    }

    private static void newDays() {
        System.out.println();
        System.out.println("有一天:");
    }

    public static void main(String[] args) {
        // 打工人的故事开始了
        newDays();
        NotebookFactory notebookFactory = new NotebookFactory("大当家的");
        INotebookSeller seller = new Store("二当家的", notebookFactory, 10);
        Worker worker = new Worker("小明");
        worker.buy(seller, 15);
    }
}

在这里插入图片描述

商店作为代理,做了库存储备,这样打工人只需要在自己家楼下就能很快买到笔记本了。

但是当抽象主题,真实主题增加时,静态代理就显得不太合理了,因为它要跟着一起改变。

比如,很快又有了钢笔的卖家,和钢笔厂,商店想要代理,就需要做出改变,之后每次要代理卖新的东西,商店都要做出调整,商店不开心了。


动态代理的出现

商店做大做强后,发现其实各种卖家和工厂都有着一些共性,而客户端也有着相似的需求,所以商店决定成立一个平台,想办法统一流程。

引入新的抽象主题,钢笔卖家。

package com.secondgod.proxy;

/**
 * 钢笔卖家(抽象主题角色)
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public interface IPenSeller {

    /**
     * 卖钢笔
     *
     * @param quantity
     */
    void sell(int quantity);
}

引入新的真实主题,钢笔工厂。

package com.secondgod.proxy;

import java.text.MessageFormat;

/**
 * 钢笔工厂(真实主题)
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class PenFactory implements IPenSeller {
    /**
     * 厂长名
     */
    private final String name;

    public PenFactory(String name) {
        this.name = name;
        System.out.println(MessageFormat.format("[{0}]的钢笔工厂开业了。", name));
    }

    @Override
    public void sell(int quantity) {
        System.out.println(MessageFormat.format("[{0}]卖掉[{1}]个钢笔。", name, quantity));
    }
}

引入新的代理,电商平台,很快我们就可以看到动态代理相对于静态代理的优势。

package com.secondgod.proxy;

import java.lang.reflect.Proxy;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

/**
 * 平台
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Platform {
    /**
     * 
     */
    private final String                name;
    /**
     * 平台下的卖家
     */
    private final Map<Class<?>, Object> storeMap = new HashMap<>();

    public Platform(String name) {
        this.name = name;
        System.out.println(MessageFormat.format("[{0}]的电商平台开业了。", name));
    }

    /**
     * 供普通卖家注册为平台卖家
     *
     * @return
     */
    public <T> void registerStore(String name, Class<T> clazz, T factory, int purchaseQuantity) {
        T seller = (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{clazz}, new PlatformStore(name, this, factory, purchaseQuantity));
        storeMap.put(clazz, seller);
    }

    /**
     * 供用户查找
     *
     * @return
     */
    public <T> T findStore(Class<T> clazz) {
        return (T) storeMap.get(clazz);
    }
}

平台化以后,商店也要有所改变。

package com.secondgod.proxy;

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

/**
 * 平台店
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class PlatformStore implements InvocationHandler {
    /**
     * 商品名
     */
    private final String   name;
    /**
     * 平台
     */
    private final Platform platform;
    /**
     * 背后是真实卖家
     */
    private final Object   factory;
    /**
     * 合同约定按照固定量的倍数采购
     */
    private final int      purchaseQuantity;
    /**
     * 库存
     */
    private       int      stockBalance;

    public PlatformStore(String name, Platform platform, Object factory, int purchaseQuantity) {
        this.name = name;
        this.platform = platform;
        this.factory = factory;
        this.purchaseQuantity = purchaseQuantity;
        System.out.println(MessageFormat.format("平台的[{0}]商店开业了,请[{1}]工厂做了自己的供应商,他们约定每次按照[{2}]个的倍数采购。", name, name, purchaseQuantity));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        int quantity = (int) args[0];
        if (quantity > stockBalance) {
            int needPurchaseQuantity = ((quantity - stockBalance) + (purchaseQuantity - 1)) / purchaseQuantity * purchaseQuantity;
            System.out.println(MessageFormat.format("平台[{0}]库存不足,于是向供应商采购了[{1}]个。", name, needPurchaseQuantity));
            args[0] = needPurchaseQuantity;
            method.invoke(factory, args);
            stockBalance += needPurchaseQuantity;
        }
        stockBalance -= quantity;
        System.out.println(MessageFormat.format("平台卖出了[{0}]个[{1}],库存余[{2}]个。", quantity, name, stockBalance));
        return null;
    }
}

接下来就是我们的买方,他除了想要买笔记本,也想要买钢笔了。main方法将描绘一个新的故事。

package com.secondgod.proxy;

import java.text.MessageFormat;

/**
 * 打工人
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Worker {
    /**
     * 打工人的名字
     */
    private final String name;

    public Worker(String name) {
        this.name = name;
        System.out.println(MessageFormat.format("打工人[{0}],为了得到自己想要的东西拼命赚钱。", name));
    }

    public void buy(INotebookSeller seller, int quantity) {
        System.out.println(MessageFormat.format("打工人[{0}]要买[{1}]个笔记本。", name, quantity));
        seller.sell(quantity);
    }

    public void buy(IPenSeller seller, int quantity) {
        System.out.println(MessageFormat.format("打工人[{0}]要买[{1}]个钢笔。", name, quantity));
        seller.sell(quantity);
    }

    private static void newDays() {
        System.out.println();
        System.out.println("有一天:");
    }

    public static void main(String[] args) {
        // 打工人的故事开始了
        newDays();
        Platform platform = new Platform("二当家的");
        platform.registerStore("笔记本", INotebookSeller.class, new NotebookFactory("大当家的"), 100);
        platform.registerStore("钢笔", IPenSeller.class, new PenFactory("大当家的"), 100);

        INotebookSeller notebookSeller = platform.findStore(INotebookSeller.class);
        IPenSeller penSeller = platform.findStore(IPenSeller.class);
        Worker worker = new Worker("小明");

        worker.buy(notebookSeller, 10);
        worker.buy(penSeller, 10);
    }
}

在这里插入图片描述

以后,再开别的工厂,买方想再买别的什么东西,平台都不需要修改了。这就是动态代理的魅力,也是平台化的魅力。


回顾动态代理的使用

静态代理是在编码阶段就确定的;而动态代理则是在程序运行时才动态创建。

在程序运行时去动态创建类,显然普通的方式是无法做到的,JDK为我们提供了API,就是在java.lang.reflect包下的Proxy类,我们的例子中使用了他的newProxyInstance静态方法,他的方法签名如下。

  • loader是类加载器
  • interfaces是动态代理需要实现的接口(我们的例子中就是商品卖家接口)
  • h则是动态代理方法被调用时的处理器(我们的例子中就是平台店)

在这里插入图片描述


尾声

代理模式的使用非常广泛。像spring,hibernate,mybatis等框架都有使用代理模式来帮助提供非侵入性的,透明的功能。值得一提的是文中没有提cglib代理,一般介绍代理模式的文章都会单独介绍一下它,我觉得cglib也算是动态代理,所以就没有再多讲了,请勿怪罪。


非常感谢你阅读本文~
放弃不难,但坚持一定很酷~
希望我们大家都能每天进步一点点~
本文由 二当家的白帽子:https://developer.aliyun.com/profile/sqd6avc7qgj7y 博客原创~
相关文章
|
2月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
67 2
|
9天前
|
Java 程序员
Java基础却常被忽略:全面讲解this的实战技巧!
小米,29岁程序员,分享Java中`this`关键字的用法。`this`代表当前对象引用,用于区分成员变量与局部变量、构造方法间调用、支持链式调用及作为参数传递。文章还探讨了`this`在静态方法和匿名内部类中的使用误区,并提供了练习题。
14 1
|
20天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
42 6
|
19天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
2月前
|
存储 消息中间件 安全
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
|
2月前
|
开发框架 Java 程序员
揭开Java反射的神秘面纱:从原理到实战应用!
本文介绍了Java反射的基本概念、原理及应用场景。反射允许程序在运行时动态获取类的信息并操作其属性和方法,广泛应用于开发框架、动态代理和自定义注解等领域。通过反射,可以实现更灵活的代码设计,但也需注意其性能开销。
53 1
|
3月前
|
缓存 负载均衡 Dubbo
Dubbo技术深度解析及其在Java中的实战应用
Dubbo是一款由阿里巴巴开源的高性能、轻量级的Java分布式服务框架,它致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
84 6
|
3月前
|
设计模式 Java 数据安全/隐私保护
Java设计模式-代理模式(7)
Java设计模式-代理模式(7)
|
3月前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
38 7
|
3月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
159 1