代理模式

代理模式

定义:为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。
举例:我想购买一个火车票,正常我们是去火车站进行买,但是,由于我们家比较远,我们可能去火车票代售点进行购买,而且,火车票代售点可能会有电话预约的额外功能,但是,可能不支持退票服务。就像我们的代理模式:去掉功能服务,增加额外服务。

常见代理模式

1.远程代理:比如说我有一个售货店,我可以通过远程代理监控各个店铺使之能直观的了解店内信息。
2.虚拟代理: 根据需要将资源消耗很大的对象进行延迟,真正需要的时候,进行创建。比如,我们浏览新闻的时候,有图片和文字,如果图片的内容很大,我们就可以给一张虚拟的图片来代替原来的图片,这样就可以看文字了。当图片加载完毕以后再进行显示 。
3.保护代理:权限控制
4.智能引用代理:提供一些额外服务。

代理实现方式

静态代理 代理和被代理对象在代理之前都是确定的。他们都实现相同的接口或者继承相同的抽象类。
示例:我们有一个车,这个车有一个行驶的方法,我们想通过代理的方法给他增加一个纪录行驶时间的方法。

package proxy;

public interface Movable {
    void move() ;

}

先写一个Movable的实现类

package proxy;

import java.util.Random;

public class Car implements Movable {
    @Override
    public void move()  {

        try {
            //实现开车,通过线程来模拟开车时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

然后通过继承的方式实现代理:(功能增加)

package proxy;

public class Car2 extends Car {
    @Override
    public void move(){
        long startTime = System.currentTimeMillis();
        super.move();
        long stopTime = System.currentTimeMillis();
        System.out.println("汽车行驶时间:" + (stopTime - startTime));

    }
}

测试:

package proxy;

public class Car2 extends Car {
    @Override
    public void move(){
        long startTime = System.currentTimeMillis();
        super.move();
        long stopTime = System.currentTimeMillis();
        System.out.println("汽车行驶时间:" + (stopTime - startTime));

    }
}

结果如下:
在这里插入图片描述
可以看到,这个车不但可以跑而且还增加了纪录时间的功能。

刚刚是通过继承方式实现代理:下面通过聚合方式来实现代理:

package proxy;

/**
 * 用聚合的方法来实现
 * 聚合:在一个类中调用另一个对象
 * 通过构造方法把Car传进来
 */
public class Car3 implements Movable {
    private Car car;
    public Car3(Car car){
        super();
        this.car = car;
    }
    @Override
    public void move() {
        long startTime = System.currentTimeMillis();
        car.move();
        long stopTime = System.currentTimeMillis();
        System.out.println("汽车行驶时间:" + (stopTime - startTime));
    }
}

测试如下:

package proxy;

public class ProxyTest {
    public static void main(String[] args) {
       /* Movable car2 = new Car2();
        car2.move();*/
        Movable car3 = new Car3(new Car());
        car3.move();
    }
}

结果可以达到预期。
那么是用继承的方式好呢?还是用聚合的方式好呢?
首先,先看继承的方式:
在这里插入图片描述
这是我想加上行驶时间的car,如果我想加入权限,或者日志功能,而且他们之间会随意组合,则还会有很多car很不方便,不推荐。
下面用聚合的方式实现:
日志类:

package proxy;

public class CarLogProxy implements Movable {
    private Movable m;

    public CarLogProxy(Movable m) {
        this.m = m;
    }

    @Override
    public void move() {
        System.out.println("日志开始");
        m.move();
        System.out.println("日志结束");
    }
}

同时把刚刚的Car3进行更改,改成接口形式:

package proxy;

/**
 * 用聚合的方法来实现
 * 聚合:在一个类中调用另一个对象
 * 通过构造方法把Car传进来
 */
public class CarTime implements Movable {
    private Movable car;
    public CarTime(Movable car){
        super();
        this.car = car;
    }
    @Override
    public void move() {
        long startTime = System.currentTimeMillis();
        car.move();
        long stopTime = System.currentTimeMillis();
        System.out.println("汽车行驶时间:" + (stopTime - startTime));
    }
}

下面进行测试:

package proxy;

public class ProxyTest {
    public static void main(String[] args) {
       /* Movable car2 = new Car2();
        car2.move();*/
     /*   Movable car3 = new Car3(new Car());
        car3.move();*/

        Car car = new Car();
        CarTime car3 = new CarTime(car);
        CarLogProxy carLogProxy = new CarLogProxy(car3);
        carLogProxy.move();
    }
}

在这里插入图片描述
注意上面的两个箭头,成功的把日志和时间进行了增加,carTime中的move只有时间,然后把car3进行聚合,这样carlogProxy就加入了日志功能。如果想要先日志后时间,则两个箭头的代码进行相互转换就可以了。随意组合。聚合会灵活很多。
感觉,这个代理模式很强。挺有意思。
静态代理还可以进行扩展,扩展成普通代理:普通代理能使调用者只知道代理存在就可以,不用知道代理了谁,简化了场景类:简化了调用者:
下面以打游戏为例子:模拟出使用静态代理和普通代理的区别;
下面模拟普通的静态代理
先定义一个抽象主题类的接口:

package proxy;

public interface IGamePlayer {
    public void login(String user, String password);

    public void killBoss();

    public void upgrade();
}

再定义一个真实主题类

package proxy;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

public class GamePlayer implements IGamePlayer {
    private String name = "";

    public GamePlayer(String name){
        this.name = name;
    }
    @Override
    public void login(String user, String password) {
        System.out.println("登陆");
    }

    @Override
    public void killBoss() {
        System.out.println("打怪");
    }

    @Override
    public void upgrade() {
        System.out.println("升级");
    }
}

下面实现的是代理类:

package proxy;
// 代理类也要实现抽象主题接口
public class GamePlayerProxy implements IGamePlayer {
    private IGamePlayer gamePlayer = null;
    public GamePlayerProxy(IGamePlayer _gamePlayer){
        this.gamePlayer = _gamePlayer;
    }
    @Override
    public void login(String user, String password) {
        this.gamePlayer.login(user,password);
    }

    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }
}

这时候,我们再写一个场景类:

package proxy;

public class Client {
    public static void main(String[] args) {
        GamePlayer player = new GamePlayer("张三");
        GamePlayerProxy proxy = new GamePlayerProxy(player);
        // 开始打游戏
        proxy.login("张三","password");

        // 开始杀怪
        proxy.killBoss();

        //升级
        proxy.upgrade();

    }
   
}

测试后结果如下所示:
在这里插入图片描述
可以发现满足预期,但是,这里的问题也出现了;
这里需要给传入真实对象;
在这里插入图片描述
需要知道代理了谁;
这样对使用者来说比较麻烦,而且也不希望可以来使用new出这个对象,因为可能需要保护这个对象;
这时候,静态代理的普通代理就可以解决这个问题:
先定义一个抽象主题类的接口:

package proxy;

public interface IGamePlayer {
    public void login(String user, String password);

    public void killBoss();

    public void upgrade();
}

再修改一下真实主题类

package proxy;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

public final class GamePlayer implements IGamePlayer {
    private String name = "";

       public GamePlayer(IGamePlayer _gamePlayer, String _name) throws Exception{
           // 就这个例子中这个,IGamePlayer是没有用的的,但是,在这里可以检查谁可以创建真实对象;
        if(_gamePlayer.getClass() != GamePlayerProxy.class){
            System.out.println("如果传入的不是GamePlayerProxy的claa对象就不能创建对象");
            throw   new  Exception("不能创建真实对象");
        }else {
            this.name = name;
        }
    }
    @Override
    public void login(String user, String password) {
        System.out.println("登陆");
    }

    @Override
    public void killBoss() {
        System.out.println("打怪");
    }

    @Override
    public void upgrade() {
        System.out.println("升级");
    }
}


再写一下代理类:

package proxy;

// 代理类也要实现接口的方法
public class GamePlayerProxy implements IGamePlayer {
    private IGamePlayer gamePlayer = null;

    public GamePlayerProxy(String name) {
        try {
        // 传入的是this所以可以创建对象
            gamePlayer = new GamePlayer(this, name);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void login(String user, String password) {
        this.gamePlayer.login(user, password);
    }

    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }
}

在写一下client场景类:

package proxy;

public class Client {
    public static void main(String[] args) {

        GamePlayerProxy proxy = new GamePlayerProxy("zhangsan");
        // 开始打游戏
        proxy.login("张三","password");

        // 开始杀怪
        proxy.killBoss();

        //升级
        proxy.upgrade();

    }

}

结果对的:
而且,传递进来只是一个代理者名称,即可进行代理,调用者只知道代理存在就可以,不用知道代理了谁。代码也会简介很多:
在这里插入图片描述

这时候,问题又来了,我们现在解决的是汽车的代理,比如说我们来了一个火车,自行车,也是要计算运行时间,和日志记录功能,可以吗?不可能在写一个TrainLogProxy吧,因为这是相同代码。
所以,可以看到静态代理的主要问题是,每个代理角色代理了一个真实角色,如果需要被代理的真实角色很多,我们就不得不写多个代理角色,这样就会使代码比较难维护;
这时候,就可以使用动态代理来解决这个问题:

动态代理

先写一个接口

package proxy;

public interface IGamePlayer {
    public void login(String user, String password);

    public void killBoss();

    public void upgrade();
}

在写一个实现类:

package proxy;

public final class GamePlayer implements IGamePlayer {
    private String name = "";
    

    public GamePlayer(String name) {
        this.name = name;
    }

    @Override
    public void login(String user, String password) {
        System.out.println("登陆" + user + " " + password);
    }

    @Override
    public void killBoss() {
        System.out.println("打怪");
    }

    @Override
    public void upgrade() {
        System.out.println("升级");
    }
}

下面写一个动态代理类:

package proxy;

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

public class GamePlayIH implements InvocationHandler {
    // 被代理者 感觉这个可以不要,不要也是可以执行的
    Class cls = null;
    // 被代理的实例
    Object obj = null;
    // 我要代理谁
    public GamePlayIH(Object _obj){
        this.obj = _obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(obj, args);
        return result;
    }
}

在客户端上进行测试:

package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 定义一个痴迷的玩家
        IGamePlayer player = new GamePlayer("张三");

        // 定义一个handler
        InvocationHandler handler = new GamePlayIH(player);
        // 开始打游戏,记下时间戳
        System.out.println("开始时间是:2020-8-8 10:22");
        // 获得类的class loader
        ClassLoader cl = player.getClass().getClassLoader();
        // 动态产生一个代理者
        IGamePlayer proxy = (IGamePlayer)Proxy.newProxyInstance(cl, new Class[]{IGamePlayer.class}, handler);
        // 登录
        proxy.login("zhangsan","password");
        // 开始杀怪
        proxy.killBoss();
        // 记录游戏结束时间
        System.out.println("结束时间是: 2020-8-8 12:11");
    }

}

结果如下:
在这里插入图片描述
这就是动态代理,当我们需要增加一个功能,比如说:游戏登录后发一个信息给我们;
这时候,我们就可以修改GamePlayIH动态代理类;
在这里插入图片描述
在进行测试:结果如下:
在这里插入图片描述
动态代理:首先要实现一个动态代理类:如上是GamePlayIH类;然后,执行动态代理对象的所有方法时,都会被替换成执行它的invoke方法;
然后,动态产生一个代理者,执行它的方法就是执行动态代理类的invoke方法了;
在这里插入图片描述
注意实现动态代理的首要条件,被代理类必须实现一个接口;而不能实现类:那么为什么呢?
在这里插入图片描述
我们首先获取代理对象的.class文件的方法,这样就可以获取到该对象了;
会发现该对象会继承Proxy,然后,再实现我们的IGamePlayer接口:
在这里插入图片描述
这个时候,我们就可以分析出来 它为什么调用了Proxy.newProxyInstance() 方法后,就产生了代理对象,同时,为什么被代理类要实现一个接口;因为Java不支持多继承,而JDK的动态代理在创建代理对象时,默认让代理对象继承了Proxy类,所以,JDK只能通过接口去实现动态代理;$Proxy0实现了IGamePlayer接口,所以重写了接口中的三个方法;
在这里插入图片描述
当调用upgrade()时,它会去调用super.h.invoke方法,super代表的是父类,h就是我们定义的InvocationHandler
在这里插入图片描述
所以,会调用到GamePlayIH.invoke()方法,然后在通过反射method.invoke()去调用目标对象的方法;在这里做到增强的作用;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值