Java代理模式之静态代理和动态代理

本文深入探讨Java代理模式,包括静态代理与动态代理的概念、实现及优劣对比。通过生动的购物场景,解析如何在不修改目标对象的前提下,通过代理对象实现功能增强。

Java代理模式之静态代理和动态代理

代理模式定义

给目标对象提供一个代理对象,并由代理对象控制对目标对象的访问。
在这里插入图片描述
看上面的类图,我们知道代理对象和目标对象需要实现一样的接口。


代理模式目的

1、通过代理对象间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;
2、通过代理对象对原有业务做增强。


静态代理与动态代理

我们先从简单的静态代理入手,以便更好地理解代理模式,然后分析静态代理的不足,接着引出动态代理。


静态代理

我们都知道由于关税和汇率的原因,去香港买东西便宜不少,质量也有保障,于是很多人买化妆品、奶粉、电子产品都会去香港。

我们就模拟这样一个场景,先定义接口:

/**
  * 女人购物 
  *
  * @author wanglingqiang
  * @date 2020/7/18 下午1:19
  **/
public interface IWomanShopping {

    /**
     * 买xxx化妆品
     * @param name
     */
    void buyMakeup(String name);

}

具体实现:

public class WomanShopping implements IWomanShopping {

    @Override
    public void buyMakeup(String name) {
        System.out.println("买" + name + "的化妆品");
    }
}
}

测试:

public class Test {

    public static void main(String[] args) {
        IWomanShopping womanShopping = new WomanShopping();
        // 买美白保湿的化妆品
        womanShopping.buyMakeup("美白保湿");
    }
}

结果:

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java 
买美白保湿的化妆品

Process finished with exit code 0

但如果买的东西不多,自己办签证、来回路费、坐车逛街时间等等,算下来就不划算了,所以我们会请代购帮忙购买。

/**
 * 代理对象
 * 跟真实对象实现同一个接口,
 * 包含真实对象,
 * 对真实对象增加
 *
 * @author wanglingqiang
 * @date 2020/7/18 下午1:27
 **/
public class Agent implements IWomanShopping {

    //被代理的真实对象
    private IWomanShopping womanShopping;

    public Agent(IWomanShopping womanShopping) {
        this.womanShopping = womanShopping;
    }

    @Override
    public void buyMakeup(String name) {
        doBefore();   //前置增强
        womanShopping.buyMakeup(name);
        doAfter();    //后置增强
    }

    private void doBefore() {
        System.out.println("----买前挑选----");
    }

    private void doAfter() {
        System.out.println("----买后包装邮寄----");
    }
}

测试:

public class Test {

    public static void main(String[] args) {
        IWomanShopping womanShopping = new WomanShopping();
        // 买美白保湿的化妆品
        //womanShopping.buyMakeup("美白保湿");

        Agent agent = new Agent(womanShopping);
        agent.buyMakeup("美白保湿");
    }
}

运行结果:

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java 
----买前挑选----
买美白保湿的化妆品
----买后包装邮寄----

Process finished with exit code 0

上面代码示例很好说明了代理模式的思想,在不修改目标对象的前提下扩展目标对象的功能。但也存在缺点:
1、不易维护。比如除了买化妆品,还买个包包,那就得在IWomanShopping接口类增加一个买包包的接口方法,同时WomanShopping、Agent两个类都要做对应实现。违反了开闭原则。
2、冗余。由于代理对象要实现与目标对象一样的接口,会产生很多代理类。想一想Spring的事务、Mybatis的Mapper,就明白了。


我们来看看不易维护的示例。
情况一:IWomanShopping接口类增加一个买包包的接口方法。
在这里插入图片描述

情况二:男士想找代购购买Mac笔记本。
男士购买接口:

/**
  * 男人购物
  *
  * @author wanglingqiang
  * @date 2020/7/18 下午1:01
  **/
public interface IManShopping {

    /**
     * 买某个尺寸的Mac笔记本
     * @param size
     */
    void buyMac(String size);

}

男士购物的实现类:

public class ManShopping implements IManShopping {

    @Override
    public void buyMac(String size) {
        System.out.println("买" + size + "寸的Mac笔记本");
    }
}

这个时候,实现类和静态代理类都要修改。
在这里插入图片描述
在这里插入图片描述
所以,这时就需要动态代理了。



动态代理

动态代理又分为JDK和CGLIB动态代理。

JDK动态代理:
使用Proxy和InvocationHandler来实现,InvocationHandler用于实现接口的方法并做增强,Proxy通过反射机制和调用native方法在内存中生成实现指定接口的实现类的字节码,从而实现对目标对象的代理功能。
CGLIB动态代理:
利用ASM开源包,把代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

这里我们以JDK的动态代理来做讲解。

静态代理与动态代理的区别:
1、 静态代理在编译时就已经实现代理过程,编译完成后代理类是一个实际存在的 class 文件;
2、动态代理是在运行时动态生成的,即编译完成后没有实际存在的 class 文件,而是在运行时动态生成类字节码,并加载到 JVM 中。


用动态代理的好处:
1、对原有代码无侵入增强;
2、不会影响原接口的使用。


动态代理的代码示例:

/**
  * 动态代理
  *
  * @author wanglingqiang
  * @date 2020/7/18 下午2:03
  **/
public class AgentCompany implements InvocationHandler {

    //被代理对象
    private Object proxyObject;

    //通过动态代理对象对方法进行增强
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doBefore();
        Object result = method.invoke(proxyObject, args);
        doAfter();

        return result;
    }

    //通过Proxy获取动态代理的对象
    public Object getProxyInstance(){
        /*
         * ClassLoader loader,
         * Class<?>[] interfaces,
         * InvocationHandler h
         */
        return Proxy.newProxyInstance(
                proxyObject.getClass().getClassLoader(),
                proxyObject.getClass().getInterfaces(),
                this
        );
    }

    public AgentCompany() {
    }

    public AgentCompany(Object proxyObject) {
        this.proxyObject = proxyObject;
    }

    public Object getProxyObject() {
        return this.proxyObject;
    }

    public void setProxyObject(Object proxyObject) {
        this.proxyObject = proxyObject;
    }

    private void doBefore() {
        System.out.println("----买前挑选----");
    }

    private void doAfter() {
        System.out.println("----买后包装邮寄----\n");
    }
}

只需要这一个代理类,就能满足上面前面静态代理的不足。

测试一下:

public class Test {

    public static void main(String[] args) {
        IWomanShopping womanShopping = new WomanShopping();
        // 买美白保湿的化妆品
//        womanShopping.buyMakeup("美白保湿");

//        Agent agent = new Agent(womanShopping);
//        agent.buyMakeup("美白保湿");

        AgentCompany agentCompany = new AgentCompany(womanShopping);
        womanShopping = (IWomanShopping) agentCompany.getProxyInstance();
        womanShopping.buyMakeup("美白保湿");
        womanShopping.buyBag("LV");

        IManShopping manShopping = new ManShopping();
        agentCompany.setProxyObject(manShopping);
        manShopping = (IManShopping) agentCompany.getProxyInstance();
        manShopping.buyMac("15.4");
        
    }
}

运行结果:

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java 
----买前挑选----
买美白保湿的化妆品
----买后包装邮寄----

----买前挑选----
买LV的包包
----买后包装邮寄----

----买前挑选----
买15.4寸的Mac笔记本
----买后包装邮寄----

至此,代理模式已经分享完。后面有时间我会继续分享其他设计模式。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值