java基础-设计模式之代理模式(JDK代理)

1. 什么是代理:由接口、实现类、以及代理类组成的一种设计模式(我自己的理解,可以看下面的快速入门案例)

2. 为什么要用代理:在不侵入目标方法的前提下增强目标方法的功能,这也符合Java的开闭原则

3. 怎样使用代理:代理分为静态代理和动态代理,我们一个一个的看

4. 首先准备好接口和被代理类以及要用到的公共方法,code

interface SingPlay {
    void singPlay();
}

class Singer implements SingPlay {

    @Override
    public void singPlay() {
        System.out.println("演唱...");
    }
}

class Common {
    public static void giveMoney() {
        System.out.println("先给表演费...");
    }
}

4. 静态代理,code:

// 静态代理类
class SingPlayProxy implements SingPlay {

    private final Singer singer;

    public SingPlayProxy(Singer singer) {
        this.singer = singer;
    }

    @Override
    public void singPlay() {
        Common.giveMoney(); // 我们加入了演唱前先给钱的方法
        singer.singPlay();
    }
}


// 测试静态代理
Singer singer = new Singer();
SingPlayProxy singPlayProxy = new SingPlayProxy(singer);
singPlayProxy.singPlay();

// 测试结果
先给表演费...
演唱...

5. 上面就是我们静态代理的整个过程。此时我们的需求变了,我想在演唱的同时,加上讲相声的表演,于是我们需要修改我们的接口以及代理类,code

interface SingPlay {
    void singPlay();
    void play();
}

class Singer implements SingPlay {

    @Override
    public void singPlay() {
        System.out.println("演唱...");
    }

    @Override
    public void play() {
        System.out.println("讲相声...");
    }
}

class Common {
    public static void giveMoney() {
        System.out.println("先给表演费...");
    }
}

6. 由于我们讲相声的表演也是收费的,所以代理类的方法也需要加入收费的逻辑,code

class SingPlayProxy implements SingPlay {

    private final Singer singer;

    public SingPlayProxy(Singer singer) {
        this.singer = singer;
    }

    @Override
    public void singPlay() {
        Common.giveMoney();
        singer.singPlay();
    }

    @Override
    public void play() {
        Common.giveMoney();
        singer.play();
    }
}

7. 在以上的基础上,假如我又引入了跳舞、弹琴、敲鼓、朗诵等等一系列的收费方法以后,我们会发现代理类对于 Common.giveMoney() 这个方法的复用性太差了(没有复用)。而且接口新增方法之后,代理类也需要修改,是不是很烦?至此,我们可以引出静态代理的第一个问题:代理类在对同一个接口的多个方法实行统一处理时,代码复用性差。代理类和被代理类之间的代码耦合性强。此时,我们自然而然就想到了动态代理,接下来我们看看动态代理怎么做

8. 对同一接口的不同方法实行统一处理,动态代理,code

// 动态代理代码
Singer singer = new Singer();
SingPlay singPlayProxy = (SingPlay) Proxy.newProxyInstance(
        DynamicProxy.class.getClassLoader(), // 第一个参数:一般是动态代理类的类加载器(这个好像写Singer以及SingerPlay都可以..)
        Singer.class.getInterfaces(), // 第二个参数:被代理类的接口(这个不能写错)
        (proxy, method, args1) -> { // 第三个参数:InvocationHandler接口的lambda表达式
            Common.giveMoney(); // 这里写一次就可以了, 也可以根据不同的方法名特殊处理
            method.invoke(singer, args1); 
            return null;
        });
singPlayProxy.singPlay();
singPlayProxy.play();

// 测试结果
先给表演费...
演唱...
先给表演费...
讲相声...

9. 可以看到,动态代理 Common.giveMoney() 方法写一次就可以在所有的方法上应用了,实现了代码的复用。对于动态代理的参数什么,以及原理什么的,等下会发一个大佬的链接,这会儿可以先向下接着看。

10. 在第5步的基础上,假如我们的需求又变了,我们需要增加一个跳舞功能的接口和唱歌并列,于是我们要增加我们的代理类以及接口,code

// 唱歌、讲相声
interface SingPlay {
    void singPlay();
    void play();
}

class Singer implements SingPlay {

    @Override
    public void singPlay() {
        System.out.println("演唱...");
    }

    @Override
    public void play() {
        System.out.println("相声...");
    }
}

// 跳舞(新加入)
interface DancePlay {
    void dancePlay();
}

class Dancer implements DancePlay {

    @Override
    public void dancePlay() {
        System.out.println("跳舞...");
    }
}

class Common {
    public static void giveMoney() {
        System.out.println("先给表演费...");
    }
}

11. 我们还需要增加一个支持跳舞表演的代理类,code

// 唱歌、讲相声的代理类
class SingPlayProxy implements SingPlay {

    private final Singer singer;

    public SingPlayProxy(Singer singer) {
        this.singer = singer;
    }

    @Override
    public void singPlay() {
        Common.giveMoney();
        singer.singPlay();
    }

    @Override
    public void play() {
        Common.giveMoney();
        singer.play();
    }
}

// 跳舞的代理类(新增加)
class DancePlayProxy implements DancePlay {

    private final Dancer dancer;

    public DancePlayProxy(Dancer dancer) {
        this.dancer = dancer;
    }

    @Override
    public void dancePlay() {
        Common.giveMoney();
        dancer.dancePlay();
    }
}

12. 假如我现在还需要很多接口来表示不同的表演呢,那是不是我需要创建很多的代理类,随着代理类的越来越多,项目会越来越臃肿,越来越难维护对不对。至此,我们引出了静态代理的第二个问题:不同的接口会创建不同的代理类,造成代理类很多,代码臃肿。我们又想到了动态代理类对不对,我们一起来看一下它会怎么做

13. 针对不同接口,动态代理,code

// 唱歌、讲相声
Singer singer = new Singer();
SingPlay singPlayProxy = (SingPlay) Proxy.newProxyInstance(
        DynamicProxy.class.getClassLoader(),
        Singer.class.getInterfaces(),
        (proxy, method, args1) -> {
            Common.giveMoney(); // 先给钱
            method.invoke(singer, args1);
            return null;
        });
singPlayProxy.singPlay();
singPlayProxy.play();

System.out.println();
// 跳舞
Dancer dancer = new Dancer();
DancePlay dancerPlayProxy = (DancePlay) Proxy.newProxyInstance(
        DynamicProxy.class.getClassLoader(),
        Dancer.class.getInterfaces(),
        (proxy, method, args1) -> {
            Common.giveMoney(); // 先给钱
            method.invoke(dancer, args1);
            return null;
        });
dancerPlayProxy.dancePlay();


// 测试结果
先给表演费...
演唱...
先给表演费...
相声...

先给表演费...
跳舞...

14. 上面就是动态代理的例子,和第11步相比是不是舒爽多了,而且我们还可以封装生成动态代理代码的部分,使我们的动态代理代码更加简洁。

15. 除了上面这些区别之外,我们还要在根据名字区分一下两个代理:

  • 静态代理:静态,在编译时生成代理类和.class文件
  • 动态代理:动态,在运行时生成代理类和.class文件

16. 对于动态代理的运行原理,可以参考这位大佬的博文(我就不按照大佬的思路造轮子了,仰视一下大佬吧,哈哈哈哈~):

https://mp.weixin.qq.com/s/h-hNC45BvTR499kDNg-Vuw

===============================================================================================

上面这些就是自己对java代理的理解,学无止境,可能有些不对的地方,希望大佬们指正。

拜拜~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章