代理模式

本文详细介绍了Java中的静态代理和动态代理概念,通过案例展示了静态代理的实现过程,以及动态代理中JDK动态代理的工作原理和使用方法。动态代理在不修改原有代码的情况下,能够灵活地扩展功能,提高了代码的复用性和灵活性。文中强调了动态代理必须基于接口的原因,并给出了动态代理在实际场景中的应用示例。
摘要由CSDN通过智能技术生成

静态代理

我们平常去电影院电影,电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告

注意这里有电影、电影院、客户、广告等元素

举例

首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。

public interface Movie {
    void play();
}

然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。

这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放

//"被代理类"对象;
public class RealMovie_TomAndJerry implements Movie {
    public void play() {
        System.out.println("正在观看电影:《猫和老鼠》");
    }
}

Proxy 代理。Cinema 就是 Proxy 代理对象,它有一个 play() 方法。除了调用 play() 方法,它还放广告。
代理对象的私有字段的类型应该为接口类型

public class Cinema implements Movie {
 	// 私有一个被代理类的父类引用,这样做是为了适应所有的被代理类对象,只要实现了接口就好;
    private	Movie movie;
	// 传入被代理类对象,这里的作用是初始化"代理类"中的"被代理类"对象;
    public Cinema(Movie movie) {
        super();
        this.movie = movie;
    }

	// 代理类本身自带功能;
    public void play() {

        advertisement(true);

        movie.play();

        advertisement(false);
    }
	
    //增强服务和功能;
    public void advertisement(boolean isStart) {
        if (isStart) {
            System.out.println("电影马上开始了");
        } else {
            System.out.println("电影马上结束了");
        }
    }
}

客户去电影院看电影


public class Client {
    public static void main(String[] args) {
        RealMovie_TomAndJerry realMovie_tomAndJerry = new RealMovie_TomAndJerry();
        Movie movie = new Cinema(realMovie_tomAndJerry);
        movie.play();

    }
}
/*
电影马上开始了
正在观看电影:《猫和老鼠》
电影马上结束了
*/

上新电影了呢

public class RealMovie_Soul implements Movie {
    public void play() {
        System.out.println("正在播放《心灵奇旅》");
    }
}

客户看完《猫和老鼠》,再看《心灵奇旅》

public class Client {
public static void main(String[] args) {
    RealMovie_TomAndJerry realMovie_tomAndJerry = new RealMovie_TomAndJerry();
    Movie movie = new Cinema(realMovie_tomAndJerry);
    movie.play();
    
    RealMovie_Soul realMovie_soul = new RealMovie_Soul();
    movie = new Cinema(realMovie_soul);
    movie.play();
	}
}
/*
电影马上开始了
正在观看电影:《猫和老鼠》
电影马上结束了

电影马上开始了
正在播放《心灵奇旅》
电影马上结束了
*/

静态代理的优劣

现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

优:

  • 职责清晰。可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情 。
  • 扩展性好。公共业务发生扩展时变得更加集中和方便 。
  • 运行速度快。对于静态代理而言,在程序运行之前,代理类和被代理类的.class文件就已经存在了,因为安排的明明白白,所以运行起来的时候会比动态代理快。(为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。)

劣:

  • 可维护性低。由于代理类和被代理类都实现了同一个接口,如果接口发生了更改,那么被代理类和所有的代理类都要进行修改,比如接口新增一个方法,那么所有的代理类和被代理类都要重写这个方法,这无疑增加了巨大的工作量。
  • 可重用性低。通过观察可以发现,代理类们的代码大体上其实是差不多的,但是由于个别的差异,导致我们不得不重新写一个新的代理类。(不同电影播放前需求不同,3D眼镜、广告、口罩?)

动态代理

  • 动态代理的角色和静态代理的一样 .

  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的

  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

    • 基于接口的动态代理----JDK动态代理
    • 基于类的动态代理–cglib
    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

JDK动态代理需了解的接口和类

核心 : InvocationHandler接口 和 Proxy类

1.InvocationHandler(调用处理器)

public interface InvocationHandler
//InvocationHandler接口  是  proxy代理实例  的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。 

(当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:)

/**
* proxy:代理类代理的真实代理对象
* method:我们所要调用某个对象真实的方法的Method对象
* args:代理实例上方法调用中传递的参数值的对象数组,如果接口方法不带参数, null 
*/

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
//处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。 

2.Proxy

Proxy类它提供了很多方法,但是我们最常用的是newProxyInstance方法。

Proxy类方法:
getInvocationHandler:返回指定代理实例的调用处理程序
getProxyClass:给定类加载器和接口数组的代理类的java.lang.Class对象。
isProxyClass:当且仅当使用getProxyClass方法或newProxyInstance方法将指定的类动态生成为代理类时,才返回true。
newProxyInstance:返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

遇到的bug:假设getInstance()方法里面return newProxyInstance(),在测试类里实例化被代理类的对象时,

Son instance=(Son) new getInstance(object); 则会报错 com.sun.proxy.$Proxy0 cannot be cast to

应改为 Father instance= (Father) new getInstance(object)

因为newProxyInstance定义为:返回指定接口的代理类的实例

因为被代理的类没有继承接口,而是继承了一个基类

问:为什么动态代理必须针对接口?
**答:**JDK动态代理的原理是根据定义好的规则,用传入的接口创建一个新类,这就是为什么采用动态代理时为什么只能用接口引用指向代理,而不能用传入的类引用执行动态类。

Proxy.newProxyInstance返回的是接口类型 而我 却用实现类来接受 就出现了类型不匹配的问题(总之一句话,jdk动态代理必须是接口。cglib 都行。)

/**
*loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
*interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
*h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
*/
public static Object newProxyInstance(ClassLoader loader, 
                                        Class<?>[] interfaces, 
                                        InvocationHandler h)

例子(万能代理)

定义抽象接口(被代理类)

public interface mathematics {
    public void add();
    public void subtract();
    public void multiply();
    public void divide();
}

定义具体对象

public class Calculate implements mathematics {
    public void add() {System.out.println("加法");}

    public void subtract() {System.out.println("减法");}

    public void multiply() {System.out.println("乘法");}

    public void divide() {System.out.println("除法");}
}

定义动态代理

public class ProxyInvocationHandler implements InvocationHandler {
    private Object object;//编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

	//实例化对象
    public Object getInstance(Object object){
        this.object = object;//合并setObject方法到此
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
    }

	//方法调用器
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object invoke = method.invoke(object, args);
        return invoke;
    }

    //代理类增加的方法,不改变具体类的源码
    public void log(String msg){
        System.out.println("调用"+msg+"方法");
    }
}

测试

public class Test {
    public static void main(String[] args) {
        mathematics mathematics = new Calculate();
        ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
        mathematics instance = (mathematics) invocationHandler.getInstance(mathematics);
//        instance.add();
        instance.multiply();
    }
}
/*
调用multiply方法
乘法
*/

动态代理其他例子

public class demo {
//Subject:接口,这个接口含有代理类和被代理类共同需要做的事,action方法
interface Subject {
    void action();
}

//RealSubject:被代理类,也就是真实的类,真正做事的类
static class RealSubject implements Subject {
    public void action() {
        System.out.println("我是被代理类!!真正做事的类");
    }
}

//ProxySubject:代理类,也就是代理对象,中介
//涉及到动态代理需要实现InvocationHandler接口
static class ProxySubject implements InvocationHandler { 
    // (实现了接口的)被代理类对象的引用声明;
    private Object object;

    public Object getNewInstance(Object object) {
        // 实例化被代理类的对象;
        this.object = object;
        
        // 返回一个动态代理类的对象;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
    }
/*
这里的newProxyInstance的三个参数:(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
1.第一个参数是需要传入类的加载器,这里指的是被代理类的类加载器,简单来说就是和被代理类使用相同的类加载器;(不一定)
2.第二个参数是需要传入类的接口,也就是说,这个类实现了哪些接口,我都要传过来;
3.第三个参数是需要传入的一个InvocationHandler对象,指的是代理类对象,也就是调用这个函数的this对象(ProxySubject对象);
*/
    
    // 当通过动态代理类的对象发起对被重写的方法的调用时,都会转换为对以下invoke方法的调用;
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强代码(前);
        System.out.println("前增强功能!!");

        // 被代理类的方法;
        Object value = method.invoke(object, args);

        // 增强代码(后);
        System.out.println("后增强功能!!");
        return value;
    }
}

    
public static class DynamicProxyDemo {
    public static void main(String[] args){
        // 1.创建被代理类对象;
        RealSubject realSubject = new RealSubject();
        
        // 2.创建一个实现了InvocationHandler接口的类的对象;
        ProxySubject proxySubject = new ProxySubject();
        
        // 3.父类引用指向子类对象;
        Subject subject = (Subject)proxySubject.getNewInstance(realSubject);
        
        // 4.执行代理类的方法;
        subject.action();
    }
}

}

代理类

ProxySubject类

//ProxySubject:代理类,也就是代理对象,中介
//涉及到动态代理需要实现InvocationHandler接口
static class ProxySubject implements InvocationHandler { 
    // 实现了接口的  被代理类的对象  引用声明;
    private Object object;

    public Object getNewInstance(Object object) {
        // 实例化被代理类的对象;
        this.object = object;
        
        // 返回一个代理类的对象;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
    }

    // 当通过代理类的对象发起对被重写的方法的调用是,都会转换为对以下invoke方法的调用;
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强代码(前);
        System.out.println("前增强功能!!");

        // 被代理类的方法;
        Object value = method.invoke(object, args);

        // 增强代码(后);
        System.out.println("后增强功能!!");
        return value;
    }
}

该类为代理类。其持有一个被代理类的父类引用object,在getNewInstance这个函数中,我们传入需要被代理的类对象,将ProxySubject中的Object实例化,同时根据这个传入的object对象,获取其类加载器接口,以及其代理类对象;通过Proxy类本身的静态方法newProxyInstance得到一个代理类对象,并将其返回,后续我们就会操作这个返回的代理类对象

其中还有一个invoke方法,这里大概的意思就是当我们上述方法创建的代理类对象进行方法调用的时候,都会转化为对invoke方法的调用,例如我们调用通过getNewInstance方法产生的代理类的sell方法,这个sell方法就会传入invoke中,然后转化成对invoke的调用(这里的invoke方法其实就相当于代理类的sell方法,但是它没有写死,而是你传入什么方法,我就调用这个方法,实现了动态调用,与被代理类完全分割开来,完成解耦)。

万能的体现

public class Test {
    public static void main(String[] args) {
        mathematics mathematics = new Calculate();
        ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
        mathematics instance = (mathematics) invocationHandler.getInstance(mathematics);
        instance.multiply();

        System.out.println("==================================");
        Rent rent = new Host();
        Rent instance1 = (Rent) invocationHandler.getInstance(rent);
        instance1.rent();

        System.out.println("==================================");
        a a = new b();
        a instance2 = (com.kuang.demo03.a) invocationHandler.getInstance(a);
        instance2.func();
    }
}
/*
调用multiply方法
乘法
==================================
调用rent方法
房屋出租
==================================
调用func方法
哈哈
*/


  Rent rent = new Host();
        Rent instance1 = (Rent) invocationHandler.getInstance(rent);
        instance1.rent();

        System.out.println("==================================");
        a a = new b();
        a instance2 = (com.kuang.demo03.a) invocationHandler.getInstance(a);
        instance2.func();
    }
}
/*
调用multiply方法
乘法
==================================
调用rent方法
房屋出租
==================================
调用func方法
哈哈
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值