• Java设计模式(八)----代理模式


    代理模式
    1.生活中:
    代理就是一个人或者一个组织代表其它人去做一件事的现实生活中的。

    在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象能够在client和目标对象之间起到中介的作用。


    2.官方:
    代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象。并由代理对象控制对原对象的引用

    一、静态代理

    类图结构例如以下

    这里写图片描写叙述
    在代理模式中的角色:
      ●抽象主题角色:声明了目标对象和代理对象的共同接口,这样一来在不论什么能够使用目标对象的地方都能够使用代理对象。


      ●真实主题角色:定义了代理对象所代表的目标对象。


      ●代理主题角色:代理对象内部含有目标对象的引用,从而能够在不论什么时候操作目标对象;代理对象提供一个与目标对象同样的接口。以便能够在不论什么时候替代目标对象。代理对象通常在client调用传递给目标对象之前或之后。执行某个操作。而不是单纯地将调用传递给目标对象。

    它能够添加一些真实主题里面没有的功能。

    生活中的样例:过春节加班比較忙,没空去买火车票,这时能够打个电话到附近的票务中心,叫他们帮你买张回家的火车票,当然这会附加额外的劳务费。但要清楚票务中心自己并不卖票,仅仅有火车站才真正卖票,票务中心卖给你的票事实上是通过火车站实现的。这点非常重要!


    上面这个样例。你就是“客户”,票务中心就是“代理角色”,火车站是“真实角色”。卖票称为“抽象角色”!

    代码
    抽象主题角色

    //抽象角色:声明真实对象和代理对象的共同接口;
    public interface  TicketManager { 
        /**
         * 售票
         */
        public  void  soldTicket();
        /**
         * 改签
         */
        public void changeTicket();
        /**
         * 退票
         */
        public void returnTicket();
    }   

    真实主题角色

    public class TicketManagerImpl implements TicketManager {
    
        @Override
        public void soldTicket() {
            //checkIdentity();
            System.out.println("售票");
        }
    
        @Override
        public void changeTicket(){
            //checkIdentity();
            System.out.println("改签");
        }
    
        @Override
        public void returnTicket() {
            //checkIdentity();
            System.out.println("退票");
        }
    
        /**
         * 身份验证
         */
        public void checkIdentity(){
            System.out.println("身份验证");
        }
    }

    代理主题角色(加入了身份验证功能)

    public class StaticProxyTicketManager implements TicketManager {
        TicketManager ticketManager;//目标对象的引用
    
        public StaticProxyTicketManager(TicketManager ticketManager) {
            this.ticketManager = ticketManager;
        }
    
        @Override
        public void soldTicket() {
            checkIdentity();
            ticketManager.soldTicket();
        }
    
        @Override
        public void changeTicket() {
            checkIdentity();
            ticketManager.changeTicket();
        }
    
        @Override
        public void returnTicket() {
            checkIdentity();
            ticketManager.changeTicket();
        }
        /**
         * 身份验证
         */
        public void checkIdentity(){
            System.out.println("身份验证--------------");
        }
    
    }

    第二个代理主题角色(加入了日志功能)

    //代理类  实现同一个接口
    public class LogProxy implements TicketManager {
        TicketManager ticketManager;//目标类的引用
        public LogProxy(TicketManager ticketManager){
            this.ticketManager=ticketManager;
        }
        @Override
        public void soldTicket() {
            ticketManager.soldTicket();
            log();//后置增强
        }
    
        @Override
        public void changeTicket() {
            ticketManager.changeTicket();
            log();
        }
    
        @Override
        public void returnTicket() {
            ticketManager.returnTicket();
            log();
    
        }
        //增强
        private void log() {
            System.out.println("日志...");
    
        }
    
    }

    client

    public class Test {
        public static void main(String[] args) {
    
            //装饰模式   new TicketManagerImpl()  真实的目标对象
            //TicketManager tm=new StaticProxyTicketManager(new TicketManagerImpl());
            TicketManager tm=new LogProxy(new StaticProxyTicketManager(new TicketManagerImpl()));
    
            tm.soldTicket();
            tm.changeTicket();
            tm.returnTicket();
        }
    }

    结果:
    身份验证————–
    售票
    日志…
    身份验证————–
    改签
    日志…
    身份验证————–
    改签
    日志…
    从上面样例能够看出 client通过代理来购票 而代理实际上不能卖票给客户,他实际上是通过目标对象卖票给客户的,也就是说他是通过真实主题的目标对象实现给client卖票的功能,他仅仅是一个中介。但我们能够在它里面添加一些功能,比方身份验证或者宣传打广告等其它的功能。

    静态代理类:在程序执行前。代理类的.class文件就已经存在了,已确定被代理的对象
    静态代理:
    长处:对真实对象进行封装,不会改动目标类的代码。


    缺点:
    1.多个不同类型目标对象须要代理时,我就须要建立多个代理类,造成类的膨胀
    2.代码的冗余
    3.编译期加入。不够灵活

    二、动态代理

    描写叙述(这个描写叙述从网上看到的,相对照较easy理解)
    动态代理(Dynamic Proxy):相比静态代理,动态代理具有更强的灵活性。由于它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们能够把这样的指定延迟到程序执行时由JVM来实现。
    所谓代理,就是须要代理类和被代理类有同样的对外接口或者说成服务。所以代理类一般都必须实现了全部被代理类已实现的接口,由于接口就是制定了一系列对外服务的标准。
    1.JDK实现动态代理

    正由于动态代理有这样灵活的特性,所以我们在设计动态代理类(DynamicProxy)时不用显式地让它实现与真实主题类(RealSubject)同样的接口(interface),而是把这样的实现推迟到执行时。
    为了能让DynamicProxy类能够在执行时才去实现RealSubject类已实现的一系列接口并执行接口中相关的方法操作,须要让 DynamicProxy类实现JDK自带的java.lang.reflect.InvocationHandler接口,该接口中的invoke() 方法能够让DynamicProxy实例在执行时调用被代理类的“对外服务”,即调用被代理类须要对外实现的全部接口中的方法。也就是完毕对真实方法的调 用。Java帮助文档中称这些真实方法为处理程序。
    依照上面所述,我们肯定必须先把被代理类RealSubject已实现的全部interface都载入到JVM中。不然JVM怎么能够找到这些方法呢?明确了这个道理,那么我们就能够创建一个被代理类的实例,获得该实例的类载入器ClassLoader。
    所谓的类载入器ClassLoader,就是具有某个类的类定义,即类的内部相关结构(包含继承树、方法区等等)。
    更重要的是,动态代理模式能够使得我们在不改变原来已有的代码结构的情况下,对原来的“真实方法”进行扩展、增强其功能,而且能够达到控制被代理对 象的行为的目的。

    请详看以下代码中的DynamicProxy类,当中必须实现的invoke()方法在调用被代理类的真实方法的前后都可进行一定的特殊 操作。

    这是动态代理最明显的长处

    类图

    这里写图片描写叙述

    代码

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class DynamicProxyTicketManager implements InvocationHandler {
        private Object targetObject;
    
        /**
         * 目标的初始化方法,依据目标生成代理类
         * 
         * @param targetObject
         * @return
         */
        public Object newProxyInstance(Object targetObject) {
            this.targetObject = targetObject;
            // 第一个參数,目标对象 的装载器
            // 第二个參数。目标接口已实现的全部接口,而这些是动态代理类要实现的接口列表
            // 第三个參数, 调用实现了InvocationHandler的对象生成动态代理实例,当你一调用代理,代理就会调用InvocationHandler的invoke方法
            return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),
                    this);
        }
    
        /**
         * 反射,这样你能够在不知道详细的类的情况下,依据配置的參数去调用一个类的方法。

    在灵活编程的时候非常实用。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 检查 checkIdentity(); Object ret = null; try { // 调用目标方法 ret = method.invoke(targetObject, args); // 执行成功,打印成功信息 log(); } catch (Exception e) { e.printStackTrace(); // 失败时,打印失败信息 System.out.println("error-->>" + method.getName()); throw e; } return ret; } /** * 身份验证 */ public void checkIdentity(){ System.out.println("身份验证--------------"); } public void log(){ System.out.println("日志..." ); } }

    client

    public class Test {
        public static void main(String[] args) {
            DynamicProxyTicketManager dynamicProxyTicketManager=new DynamicProxyTicketManager();
            TicketManager tm=(TicketManager) dynamicProxyTicketManager.newProxyInstance(new TicketManagerImpl());
    
            tm.soldTicket();
            tm.changeTicket();
            tm.returnTicket();
        }
    }

    结果同上
    优缺点
    长处:
    1、一个动态代理类更加简单了。能够解决创建多个静态代理的麻烦。避免不断的反复多余的代码
    2、调用目标代码时,会在方法“执行时”动态的加入,决定你是什么类型。才调谁。灵活
    缺点:
    1、系统灵活了,可是相比而言,效率减少了,比静态代理慢一点
    2、动态代理比静态代理在代码的可读性上差了一点,不太easy理解
    3、JDK动态代理仅仅能对实现了接口的类进行代理
    总结
    各有各的好,详细情况详细讨论

    2.Cglib实现动态代理

    描写叙述(网上整理)
    AOP的源代码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。


    两种方法同一时候存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的 ,cglib动态代理底层则是借助asm来实现的。总的来说。反射机制在生成类的过程中比較高效,而asm在生成类之后的相关执行过程中比較高效(能够通 过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。另一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。假设没有 上述前提。jdk动态代理不能应用。由此能够看出。jdk动态代理有一定的局限性。cglib这样的第三方类库实现的动态代理应用更加广泛, 且在效率上更有优势。


    JDK的动态代理机制仅仅能代理实现了接口的类,否则不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖当中方法实现增强,但由于採用的是继承。所以不能对final修饰的类进行代理。

    介绍:
    CGLIB的核心类:
    net.sf.cglib.proxy.Enhancer – 基本的增强类
    net.sf.cglib.proxy.MethodInterceptor – 基本的方法拦截类。它是Callback接口的子接口。须要用户实现
    net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,能够方便的实现对源对象方法的调用,如使用:
    Object o = methodProxy.invokeSuper(proxy, args);//尽管第一个參数是被代理对象,也不会出现死循环的问题。
    net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它常常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口仅仅定义了一个方法
    public Object intercept(Object object, java.lang.reflect.Method method,
    Object[] args, MethodProxy proxy) throws Throwable;
    第一个參数是代理对像,第二和第三个參数各自是拦截的方法和方法的參数。原来的方法可能通过使用java.lang.reflect.Method 对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使 用。由于它更快

    代码

    public class CglibDynamicProxyTicketManager implements MethodInterceptor  {
        private Object targetObject;//目标对象
        /** 
         * 创建代理对象 
         *  
         * @param targetObject 
         * @return 
         */  
        public Object getInstance(Object targetObject) {  
            this.targetObject = targetObject;  
            Enhancer enhancer = new Enhancer();  // 用这个类来创建代理对象(被代理类的子类): 并设置父类;设置回调。
            enhancer.setSuperclass(this.targetObject.getClass()); // 设置被代理类作为其父类的代理目标
            // 回调方法  
            enhancer.setCallback(this);  // 设置回调--当这个代理对象的方法被调用时 回调方法intercept()会被执行
            // 创建代理对象  
            return enhancer.create();  
        }  
    
        @Override
        //回调方法
        // methodProxy 代理的类的方法
        /**
         * methodProxy 会调用父类(目标对象)的被代理的方法,比方soldTicket方法等
         */
        public Object intercept(Object obj, Method method, Object[] args,  
                MethodProxy methodProxy) throws Throwable {
            Object result = null;
            checkIdentity();//前置增强
            result=methodProxy.invokeSuper(obj, args); //调用新生成的cglib的代理对象 所属的父类的被代理的方法
             log();//后置增强
            return result;
        }
    
        /**
         * 身份验证
         */
        public void checkIdentity(){
            System.out.println("身份验证--------------");
        }
        public void log(){
            System.out.println("日志..." );
        }
    
    
    }

    client

    public class Test {
        public static void main(String[] args) {
            CglibDynamicProxyTicketManager cglibdynamicProxyTicketManager=new CglibDynamicProxyTicketManager();
            //生成代理对象
            TicketManager tm=(TicketManager) cglibdynamicProxyTicketManager.getInstance(new TicketManagerImpl());
    
            tm.soldTicket();//当调用代理对象的被代理对象的方法时  会自己主动回调 代理类中的Intercept()方法
            tm.changeTicket();
            tm.returnTicket();
        }
    }

    结果同上

  • 相关阅读:
    (转)-为什么分布式一定要有Redis?
    (九)redis使用lua脚本
    (八)redis实现分布式锁
    (七)面试题-Redis比较常见的面试题-转载
    (六)redis缓存穿透,击穿,雪崩以及解决方案
    (五)redis的主从复制
    (四)redis的数据持久化-RDB,AOF
    (三)redis的其他功能-Bitmap,HyperLogLog,GEO
    JsTracker
    Web开发者助手 FeHelper
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/7373382.html
Copyright © 2020-2023  润新知