JAVA设计模式之代理模式

JAVA设计模式之代理模式

1、什么是代理模式

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式UML类图

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

2、应用场景

代理模式是一种比较常见的结构型设计模式,按照不同的场景,又可以分为保护代理,远程代理,虚拟代理,缓冲代理和智能引用代理。

  • 保护代理,就是为原对象设置不同的访问权限,处于被保护的状态,不可直接访问,用户在访问时根据自己不同的权限来访问。
  • 远程代理,在本地创建一个远程对象的本地代理,通过访问该本地代理实现访问远程对象的目的,也叫大使(Ambassador)
  • 虚拟代理,虚拟的意思是假的,当要创建一个复杂巨大的对象时,先创建一个小的代理对象,当具体使用时再创建真实对象,有点“懒加载”的意思。
  • 缓冲代理,为针对原对象的某种操作提供一个临时的缓冲代理,用来分担访问原对象的压力,以便多客户端快速共享这些资源。
  • 智能引用代理,相当于对原对象的一种功能扩展,在访问原对象时,加入了新功能,例如统计访问次数等。

3、与装饰模式的区别

https://blog.csdn.net/zhshulin/article/details/38665187

装饰者模式UML类图

在这里插入图片描述

区别:

  • 从语意上讲,代理模式是为控制对被代理对象的访问,而装饰模式是为了增加被装饰对象的功能
  • 代理类所能代理的类完全由代理类确定,装饰类装饰的对象需要根据实际使用时客户端的组合来确定
  • 被代理对象由代理对象创建,客户端甚至不需要知道被代理类的存在;被装饰对象由客户端创建并传给装饰对象

4、静态代理

所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

抽象角色

/**
 * 抽象角色
 */
public interface Subject {
    /**
     * 处理任务
     * @param taskName 任务名称
     * @return 处理任务是否成功
     */
    boolean dealTask(String taskName);

    /**
     * 销毁任务
     * @param taskName 任务名称
     * @return 销毁任务是否成功
     */
    boolean destroyTask(String taskName);
}

真实角色

/**
 * 真实角色
 */
public class RealSubject implements Subject {
    public boolean dealTask(String taskName) {
        System.out.println("正在处理任务:" + taskName);
        return true;
    }

    public boolean destroyTask(String taskName) {
        System.out.println("正在销毁任务:" + taskName);
        return true;
    }
}

代理角色

/**
 * 代理角色
 */
public class ProxySubject implements Subject {
    /**
     * 真实角色
     */
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    public boolean dealTask(String taskName) {
        System.out.println("method:dealTask is invoked before");
        boolean dealTask = realSubject.dealTask(taskName);
        System.out.println("method:dealTask is invoked after");
        return dealTask;
    }

    public boolean destroyTask(String taskName) {
        System.out.println("method:destroyTask is invoked before");
        boolean dealTask = realSubject.destroyTask(taskName);
        System.out.println("method:destroyTask is invoked after");
        return dealTask;
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        Subject subject = new ProxySubject(new RealSubject());
        if (subject.dealTask("技术分享")) {
            System.out.println("处理任务成功!");
        }
        if (subject.destroyTask("技术分享")) {
            System.out.println("销毁任务成功!");
        }
    }
}

输出结果

method:dealTask is invoked before
正在处理任务:技术分享
method:dealTask is invoked after
处理任务成功!
method:destroyTask is invoked before
正在销毁任务:技术分享
method:destroyTask is invoked after
销毁任务成功!

5、JDK动态代理

5.1、如何使用

/**
 * 抽象角色
 */
public interface Subject {
    /**
     * 处理任务
     * @param taskName 任务名称
     */
    void dealTask(String taskName);

    /**
     * 销毁任务
     * @param taskName 任务名称
     */
    void destroyTask(String taskName);
}
/**
 * 真实角色
 */
public class RealSubject implements Subject {
    public void dealTask(String taskName) {
        System.out.println("正在处理任务:" + taskName);
    }

    public void destroyTask(String taskName) {
        System.out.println("正在销毁任务:" + taskName);
    }
}
/**
 * 自定义调用处理器
 */
public class MyInvocationHandler implements InvocationHandler {
    /**
     * 真实角色
     */
    private Object realSubject;

    public MyInvocationHandler(Object realSubject) {
        this.realSubject = realSubject;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("method:" + method.getName() + " is invoked before");
        Object invoke = method.invoke(realSubject, args);
        System.out.println("method:" + method.getName() + " is invoked after");
        return invoke;
    }
}
public class Client {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        RealSubject realSubject = new RealSubject();
        ClassLoader classLoader = Client.class.getClassLoader();
        InvocationHandler handler = new MyInvocationHandler(realSubject);

        // 第一种写法
//        Class<?> proxyClass = Proxy.getProxyClass(classLoader, Subject.class);
//        Object proxyInstance = proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
//        Subject subject = (Subject) proxyInstance;
//        Subject2 subject2 = (Subject2) proxyInstance;

        // 第二种写法
        Class<?>[] interfaces = realSubject.getClass().getInterfaces();
        Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, handler);
        Subject subject = (Subject) proxyInstance;
        Subject2 subject2 = (Subject2) proxyInstance;

        subject.dealTask("技术分享");
        subject.destroyTask("技术分享");
        subject2.sayHello("World");
    }
}

5.2、关键源码解读

UML类图

在这里插入图片描述
概述
Proxy是用二级缓存WeakCache来实现的,一级Key是ClassLoader,二级Key由KeyFactory工厂生成,Value由ProxyClassFactory生成,key和value存的都是弱引用对象,JVM每次GC都会清理弱引用所引用的对象(非弱引用对象本身,请注意,指的是Reference对象中的referent属性),并将弱引用对象存到弱引用与之关联的引用队列中,这是JVM的内部实现机制,JVM为什么要这么做呢,我的理解是方便程序主动去清理这些弱引用对象。

源码分析
最重要的代码就是这一段,WeakCache的get()方法

public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);
		// 根据引用队列清理缓存,这些引用的referent已经被JVM GC时回收了,所以要将这些引用从map中删除,否则从缓存中拿到的值也是空的,没意义,谁进行引用队列的入队操作的呢,概述中讲过,JVM内部实现机制,GC时做的操作。
        expungeStaleEntries();
        // key是ClassLoader,为什么Proxy的一级Key要用ClassLoader我还不太清楚,还没怎么去研究过类加载机制,这里是用classLoader创建一个弱引用作为一级Key
        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // lazily install the 2nd level valuesMap for the particular cacheKey
        // 为指定的一级Key懒加载二级valuesMap
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        // 用subKeyFactory生成二级key
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                // 真正取值的地方,如果是Factory实例,说明还没有缓存,factory的get方法会调用valueFactory(Proxy是ProxyClassFactory)生成value,并用CacheValue<V>替换Factory,也就是生成缓存;如果是CacheValue实例,说明已有缓存,直接取值;不管是Factory还是CacheValue实例,一般来说这里都会拿到值
                V value = supplier.get();
                // 拿到值了就跳出循环(好像大佬们称为自旋)
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)
            // 上面三句话解释得很清楚,不再翻译,看源码时留意一下
            // lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                // 走到这里是Factory的get()方法为null的情况,旧的Factory无法正常生成value,所以用新的factory替换
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // 不太确定什么时候会走到这里,同步引起的问题?还没想的太明白
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

6、总结

通过这次对代理模式的学习,自我感觉提升不少,特别是看JDK源码的过程中,了解到了很多新的知识点,比如强引用、软引用、弱引用、虚引用以及与之相关的引用队列等知识点,当然还有这次的主题,JDK动态代理的实现原理,不过还是有一些没完全整明白的地方,还得多看几遍才行,温故而知新,到时候再来完善;文中可能有说的不恰当的地方,欢迎大家指正,收到第一时间回复,以免误导其他人,谢谢。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值