当前位置 博文首页 > 阳阳的博客:【设计模式】代理模式

    阳阳的博客:【设计模式】代理模式

    作者:[db:作者] 时间:2021-08-14 21:04

    一、前言

    Spring中的AOP思想就是对代理模式的经典运用,下面先讲讲代理模式的核心思想,以静态代理为例。


    二、示例

    下面有这样一个例子,委托人在遭遇利益受损的时候,可以委托律师帮忙打官司。

    先定义一个描述行为的接口:

    package com.design.proxy.statics;
    
    public interface Action {
        void handle();
    }
    

    委托人,实现这个接口,主要的行为是寻找律师。

    package com.design.proxy.statics;
    
    /**
     * 委托人
     */
    public class Client implements Action {
        @Override
        public void handle() {
            System.out.println("委托人:我要找律师,帮我打官司");
        }
    }
    

    律师类,同样实现这个接口,并且持有一个委托人的引用,可以帮助委托人打官司。

    package com.design.proxy.statics;
    
    /**
     * 律师
     */
    public class Lawyer implements Action {
    
        private Action action;
    
        public Lawyer(Action action) {
            this.action = action;
        }
    
        @Override
        public void handle() {
            action.handle();
            System.out.println("律师:帮助我的委托人打官司");
        }
    }
    

    Main类,用来描述打官司的过程

    package com.design.proxy.statics;
    
    public class Main {
        public static void main(String[] args) {
            Action client = new Client();
            Action lawyer = new Lawyer(client);
            lawyer.handle();
        }
    }
    

    输出如下:

    这就是代理模式的一个运用,这里的委托人显然是被代理对象,那么律师就是代理对象,律师代理委托人进行打官司。


    三、类图

    代理模式的类图如下:

    • Subject? ? ? ? ? ? ? 抽象主题对象,是代理对象以及被代理对象都需要实现的接口或继承的类
    • Proxy? ? ? ? ? ? ? ? ?被代理对象,持有被代理对象的一个引用,控制被代理对象的行为以及访问
    • RealSubject? ? ? 被代理对象,定义具体的逻辑实现。

    代理又分为静态代理与动态代理,在上个例子中,就是一个典型的静态代理。静态代理与动态代理的区别就在于,静态代理的.class文件在程序运行前就已经存在了,而动态代理的.class文件则是利用反射机制动态生成的。

    在静态代理中,静态代理对象=实现一个与被代理对象相同的接口+增强方法。??????


    四、动态代理

    在上一节已经说过,动态代理是在程序运行时,动态生成代理类。动态代理又分为JDK动态代理与CGLIB动态代理。

    JDK动态代理

    我们现在在Action类中增加更多的行为,代表委托人的需求。

    package com.design.proxy.statics;
    
    public interface Action {
    
        /**
         * 诉讼
         */
        void litigation();
    
        /**
         * 咨询
         */
        void consult();
    
        //...
    }
    

    那么委托人:

    package com.design.proxy.statics;
    
    /**
     * 委托人
     */
    public class Client implements Action {
        
        @Override
        public void litigation() {
            System.out.println("委托人:我有诉讼的需求");
        }
    
        @Override
        public void consult() {
            System.out.println("委托人:我有咨询的需求");
        }
    }
    

    在动态代理中,就不需要一个真实的代理对象了,代理对象是在运行时动态生成的。JDK提供了一种思路,实现InvocationHandler接口,并通过Proxy.newProxyInstance获取动态生成的代理对象。

    package com.design.proxy.dynamic.jdk;
    
    import com.design.proxy.statics.Action;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class LawyerHandler implements InvocationHandler {
        private Action action;
    
        //绑定被代理对象,最后返回代理对象的实例
        public Action bind(Action action) {
            this.action = action;
            return (Action) Proxy.newProxyInstance(
                    action.getClass().getClassLoader(),
                    action.getClass().getInterfaces(),
                    this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //调用被代理对象的方法
            Object o = method.invoke(action, args);
            //增强方法
            System.out.println("律师处理委托人的需求");
            return o;
        }
    }
    

    Main类:

    package com.design.proxy.dynamic.jdk;
    
    import com.design.proxy.statics.Action;
    import com.design.proxy.statics.Client;
    
    public class Main {
        public static void main(String[] args) {
            Action action = new LawyerHandler().bind(new Client());
            action.litigation();
            action.consult();
        }
    }
    

    输出如下:

    动态代理的核心,在于怎么获取动态代理对象,也就是怎么去获取动态代理对象的class对象,而要获取一个类的class对象,需要类加载器与一个.class文件,类加载器由action.getClass().getClassLoader()提供。

    对类加载器不熟悉的同学,可以参考我的另外一篇文章类加载器与双亲委派模型

    .class文件由java类编译形成,也就是说现在需要一个动态代理类的java文件内容,由静态代理的类结构可以得出,这个动态代理类同样需要实现与被代理类相同的接口,这个接口由action.getClass().getInterfaces()提供。

    动态代理类同样需要持有被代理对象的引用,方便在增强方法中调用被代理对象的方法,这个(增强方法+被代理对象的引用+调用被代理对象方法)的组合,由InvocationHandler中的invoke方法提供。内部的method.invoke(action, args)方法,就是去调用被代理对象(action)的方法(method),方法参数为args。

    如果我们使用静态代理来处理上面这个例子,则需要新建律师类,实现Action接口,实现接口中所有的方法,并在这些方法中去做同样的增强逻辑,代码冗余度高。如果有很多类需要代理,也或造成代理类的膨胀。


    CGLIB动态代理

    JDK动态代理,需要被代理类实现某个接口,如果该类没有实现接口,则无法使用JDK动态代理,此时可以使用CGLIB动态代理。

    CGLIB是一个第三方实现的动态代理,需要引入cglib与asm的jar包,这个去maven仓库一搜就能搜到。

    我们重新改造委托人这个类,添加一个final方法

    package com.design.proxy.dynamic.cglib;
    
    /**
     * 委托人
     */
    public class Client {
    
        public void litigation() {
            System.out.println("委托人:我有诉讼的需求");
        }
    
        public void consult() {
            System.out.println("委托人:我有咨询的需求");
        }
    
        public final void renting() {
            System.out.println("委托人:我有租房的需求");
        }
    }
    

    CGLIB同样需要我们实现MethodInterceptor类

    package com.design.proxy.dynamic.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class LawyerInterceptor implements MethodInterceptor {
    
        private Object object;
    
        public Object getInstance(Object object) {
            this.object = object;
            //Cglib中的加强器,用来创建动态代理
            Enhancer enhancer = new Enhancer();
            //设置代理类的父类
            enhancer.setSuperclass(this.object.getClass());
            //设置回调,这里相当于是对于代理类上所有方法的调用,都会调用Callback,而Callback则需要实现intercept()方法进行拦截
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            Object object = proxy.invokeSuper(obj, args);
            System.out.println("律师处理委托人的需求");
            return object;
        }
    }
    

    Main类:

    package com.design.proxy.dynamic.cglib;
    
    public class Main {
        public static void main(String[] args) {
            Client client = (Client) new LawyerInterceptor().getInstance(new Client());
            client.litigation();
            client.consult();
            client.renting();
        }
    }
    

    输出得到:

    CGLIB动态代理,依据被代理类生成其对应的子类,子类就是代理类,即在子类中覆盖所有可被继承的方法,并在重写方法中对被代理类进行增强。

    委托人这个类中,由于renting方法是个final方法,无法被子类继承,因此也无法被增强。

    CGLIB动态代理最为核心的地方在于Enhancer,使用setSuperclass指明被代理类继承什么类,当调用父类中的方法时,将会触发回调,回调将会触发MethodInterceptor 中的intercept方法,在intercept方法中保存了被增强方法调用的时机。

    cs