Proxy模式

Proxy模式初探

Proxy译为:代理。生活中有许多 代理 的例子,现在假设这样一个场景:小A怕自己有些忙不过来,于是小B成为小A的代理人,当有一些简单的事情到来(小A和小B都可以处理)时,小B可以 代替 小A完成,但当有一些事情是小B无法完成必须 被代理人 小A出面解决时,小B才会通知小A让小A解决。

过程中,小A就是一个 被代理的对象 ,而小B则是一个代理小A的 代理对象 。下面举一个具体的示例入门代理模式。

现在有一个播放器 RealPlayer 可以播放音乐,它实现了接口 Playable ,有另外一个代理播放器 ProxyPlayer ,它可以返回音乐名称、修改音乐名称,但无法播放音乐,这样代理对象无法处理的任务则需要交给被代理对象 RealPlayer 完成。

Proxy模式类图

Playable接口

Playable接口定义了获取音乐名称、设置音乐名称、播放音乐3个方法。在被代理对象中,播放音乐功能是它特有的,代理对象必须经过被代理对象即 RealPlayer 才能实现播放功能。这里所指的代理对象不具有播放音乐的功能,并不是说它真的不具有,而是为了演示在实际中,一些 被代理对象 可以做到的事情且在不修改被代理对象的情况下 代理对象 做不到的事情,需要代理对象去调用被代理对象相应的方法才能实现。

public interface Playable {
    String getMusicName();
    void setMusicName(String musicName);
    void playMusic();
}

被代理对象RealPlayer

被代理对象实现Playable接口,同时重写方法,播放的方法playMusic是被代理对象独有的。

public class RealPlayer implements Playable {
    private String musicName;

    @Override
    public String getMusicName() {
        return musicName;
    }

    @Override
    public void setMusicName(String musicName) {
        this.musicName = musicName;
    }

    @Override
    public void playMusic() {
        System.out.println("播放-" + musicName);
    }
}

代理对象ProxyPlayer

代理对象是代理模式的主角。代理对象是代理被代理对象完成某些任务的,在本例中,即代替被代理对象完成返回音乐名称、设置音乐名称。但真正需要播放音乐时,就需要被代理对象来完成,因此代理对象需要持有被代理对象。但被代理对象并不是在代理对象创建的时候创建的,当用户只是需要获取音乐名称或设置音乐名称而不播放音乐时,被代理对象实际上无需创建,只有当需要被代理对象RealPlayer来播放音乐时,才会调用realize方法实例化被代理对象。

public class ProxyPlayer implements Playable{
    private String musicName;
    private RealPlayer realPlayer;//持有被代理对象

    @Override
    public String getMusicName() {
        return musicName;
    }

    @Override
    public synchronized void setMusicName(String musicName) {
        //若被代理对象已实例化则同时设置musicName
        if (realPlayer != null) {
            realPlayer.setMusicName(musicName);
        }
        this.musicName = musicName;
    }

    /**
     * 这是代理对象无法自己完成的任务
     * 交给被代理对象 RealPlayer去完成
     */
    @Override
    public void playMusic() {
        realize();
        realPlayer.playMusic();
    }

    /**
     * 实例化被代理对象
     */
    private synchronized void realize() {
        if(realPlayer == null) {
            realPlayer = new RealPlayer();
            realPlayer.setMusicName(musicName);
        }
    }
}

Main类

主方法类中,实例化一个编译类型为Playable,运行类型为ProxyPlayer的对象player。当调用获取音乐名称、设置音乐名称时,不会创建被代理对象,当调用playMusic方法时,才真正实例化被代理对象,并通过实例化的被代理对象调用其播放音乐的方法。

public class Main {
    public static void main(String[] args) {
        Playable player = new ProxyPlayer();
        player.setMusicName("小酒窝");
        player.getMusicName();
        player.playMusic();
    }
}

Proxy模式中的角色

  • Subject 主体

        Subject 保证实际主体和代理人共同拥有的方法,在例子中即为Playable接口

  • RealSubject 实际的主体

        即被代理对象,在本例中为 RealPlayer

  • Proxy 代理人

        代理对象,在本例中为 ProxyPlayer

  • Client 请求者

        使用Proxy模式的角色

动态代理?

上面的代理实际上是静态代理(代理模式包含虚拟代理、远程代理、访问代理等等,这里也属于虚拟代理,因为只在真正需要的时候才生成被代理对象实例),静态代理是在编译期即确定了被代理类、代理类,灵活性受限,故有动态代理。

什么是动态代理?

不需要为每一个被代理对象单独创建代理类,在运行时才确定被代理的对象。动态代理包括JDK动态代理和CGLIB动态代理,这里的动态代理指JDK动态代理。

还是使用上面的例子。

JDK动态代理类图

 JDK动态代理中,被代理对象依旧要实现接口Playable,但代理对象则不需要,同时,代理对象实例是由ProxyFactory中的getProxyInstance方法返回。Main类在使用动态代理时,需要将一个RealPlayer对象传递给ProxyFactory,用于初始化ProxyFactory的target变量,该变量用于告诉底层需要被代理的对象的类加载器、其实现的接口、实现InvocationHandle接口的类的实例,这里不懂没关系,需要知道的就是:传递了这个target对象,才能生成并返回其相对应的代理对象。

下面看具体代码实现,Playable接口与RealPlayer都和前面相同。主要看代理工厂类与Main类

JDKProxyFactory代理工厂

代理工厂类中,定义了一个target类变量,并在构造器中初始化该变量。最重要的 getProxyInstance 方法中,使用Proxy类的newProxyInstance方法生成代理对象,这里底层使用的是反射的机制实现的。方法需要传入3个参数:

//newProxyInstance方法定义的源码
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
  • ClassLoader loader:被代理类的类加载器
  • Class<?>[] interfaces:被代理类实现的接口
  • InvocationHandler h

        传入实现了InvocationHandler 接口的类的实例,本例中使用的是匿名内部类的方式传入。实现该接口需要重写其中的 invoke 方法,在invoke方法中,可以在被代理对象的方法被调用之前与之后做一些操作。

public class JDKProxyFactory {
    private static Object target;

    public JDKProxyFactory(Object target) {
        this.target = target;
    }

    public static Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("调用代理对象方法前做处理");
                        Object res = method.invoke(target, args);
                        System.out.println("调用代理对象方法后做处理");
                        return res;
                    }
                }
        );
    }
}

Main类

主方法类中创建一个 被代理对象 传入代理工厂JDKProxyFactory的构造器并调用 getProxyInstance 方法获取代理对象,再使用代理对象调用 被代理对象 的 setMusicName和playMusic方法,最后一行输出proxyPlayer的类型。

public class Main {
    public static void main(String[] args) {
        Playable proxyPlayer = (Playable)new JDKProxyFactory(new RealPlayer()).getProxyInstance();
        proxyPlayer.setMusicName("圣所");
        proxyPlayer.playMusic();
        System.out.println(proxyPlayer.getClass());
    }
}

输出结果:

 可以看到,被代理对象的每个方法被调用的前后,都进行了操作,在这里即输出语句,最后输出的类型:class com.sun.proxy.$Proxy0 可以看到,获取到的的的确确是一个代理对象。

此文为阅读  结城浩 -《图解设计模式》之代理模式有感

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Proxy模式是一种常用的设计模式,也称为代理模式。它的作用是在某个对象外部提供一个代理类,用来控制对原始对象的访问。 Proxy模式通常用于以下场景: 1. 远程访问:当客户端需要访问远程对象时,我们可以通过代理类实现远程访问。代理类接收客户端的请求,并负责将请求传递给远程对象。远程对象将处理请求并将结果返回给代理类,代理类再将结果返回给客户端。 2. 安全控制:代理类可以用来限制对原始对象的访问。代理类可以检查客户端是否有足够的权限来访问原始对象,并在必要时拒绝访问。 3. 记录日志:代理类可以用来记录对原始对象的访问。代理类可以记录每个访问的时间、客户端的IP地址、请求的参数和结果等信息,从而方便后续的跟踪和分析。 4. 延迟加载:代理类可以用来实现延迟加载。当客户端请求访问原始对象时,代理类可以先返回一个占位符,并在必要时再加载原始对象。这样可以节省系统资源,提高系统的响应速度。 5. 缓存数据:代理类可以用来实现数据缓存。当客户端请求访问某个数据时,代理类先检查缓存中是否存在该数据,如果存在则直接返回缓存数据,否则才访问原始对象并将结果放入缓存中。 总之,Proxy模式是一种非常有用的设计模式,它可以在许多场合下起到很好的作用。如果您想要提高系统的性能、安全性或可维护性,那么Proxy模式是一种不错的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值