美文网首页
代理模式——对象结构型模式

代理模式——对象结构型模式

作者: vnsun | 来源:发表于2020-04-06 22:32 被阅读0次

    定义

      代理(proxy)模式 ,应用非常广泛的一种结构型设计模式,而且变化很多,代理对象可以在客户对象和目标对象之间起到中介的作用,去除客户看不到的操作和内容,或者为客户添加需要的额外的服务

    生活中的映射

      生活中最常见的例子就是,租房子、买房子、霸占了你微信朋友圈的微商小姐姐们、还有就是代购网站
    她们从房东、商家获取资源,然后再卖出去的一种模式


    image.png

    代理实现方式

    • 静态代理
    • 动态代理

    代理角色

    通常情况下,代理有两个角色代理角色、被代理角色(目标对象)
    代理角色通常会持有被代理角色的对象引用(方便代理角色完成工作之前或之后能够找到被代理对象,并通知被代理对象)

    区别

    静态代理和动态代理的主要区别就是静态代理代理的是比较固定的类,而动态代理代理的不是具体的类,相对于静态代理,动态代理比静态代理灵活很多
    换句话说,静态代理代理的事物都是已知的,而动态代理代理的事物都是未知的

    静态代理

    模拟一个场景,张三买房子,通过中介购买合适的房子

    基础静态代理

    package com.vnsun.proxy.staticed;
    
    /**
     * 被代理类
     */
    public class Zhangsan {
    
        public Zhangsan() {
           
        }
        /**
         * 买房子
         */
        public void buyHouse() {
            System.out.print("张三买房子,三室一厅一厨一卫120平");
        }
    }
    
    package com.vnsun.proxy.staticed;
    
    /**
     * 中介(代理类)
     * @author vnsun
     */
    public class Medium {
        private Zhangsan person;
    
        public Medium(Zhangsan person) {
            this.person = person;
        }
        // 代理增强
        public void buyHouse() {
            System.out.println("我是中介,介绍房子....");
            person.buyHouse();
            System.out.println("满意,付首付");
        }
    }
    
    /**
    * 测试静态代理
    */
    public static void main(String[] args) {
            Medium m = new Medium(new Zhangsan());
            m.buyHouse();
    }
    

    上述代理模拟实现张三买房子的场景,此时你会发现,如果张三发现首付不够,先租房子,赞两年钱后再买房子,此时,就会只能增加租房子的业务方法,而且这个中介只能为张三服务

    改造升级静态代理

    /**

    • 接口实现

    • @author vnsun
      */
      public interface Person {

      void buyHouse();

    }

    动态代理

    同样的场景用动态代理来实现

    /**
     * 人
     * @author vnsun
     */
    public interface Person {
    
        // 买房子
        void buyHouse();
        // 找工作
        void job();
    }
    
    package com.vnsun.proxy.staticed;
    
    /**
     * 张三
     */
    public class Zhangsan implements Person{
    
        /**
         * 买房子
         */
        @Override
        public void buyHouse() {
            System.out.print("张三买房子,三室一厅一厨一卫120平");
        }
        /**
         * 找工作
         */
        @Override
        public void job() {
            System.out.print("张三找工作");
        }
    }
    
    package com.vnsun.proxy.staticed;
    
    /**
     * 中介
     * @author vnsun
     */
    public class Medium {
        private Person person;
    
        public Medium(Person person) {
            this.person = person;
        }
    
        public void buyHouse() {
            System.out.println("介绍房子精装修");
            person.buyHouse();
            System.out.println("客户满意,付首付");
        }
        public void job() {
            System.out.println("推荐工作");
            person.job();
            System.out.println("面试入职");
        }
    }
    

    改造后的中介不仅可以为张三服务,还可以为其他人服务,只需要继承Person就可以为其服务,但问题由之而来,随着业务的扩展张三要买东西、找对象,此时业务接口需要增加相应的方法来完成工作,这违反了开放闭合原则。同时,这些工作都交由中介来完成是不现实的,中介也不是万能的,同时这也不满足单一职责原则

    动态代理

    JDK实现

    package com.vnsun.proxy.jdk;
    
    public interface Person {
        void buyHouse();
        void job();
    }
    
    package com.vnsun.proxy.jdk;
    
    public class Zhangsan implements Person{
    
        /**
         * 买房子
         */
        @Override
        public void buyHouse() {
            System.out.println("张三买房子,三室一厅一厨一卫120平");
        }
    
        /**
         * 找工作
         */
        @Override
        public void job() {
            System.out.println("找工作");
        }
    }
    
    package com.vnsun.proxy.jdk;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class JDKMedium implements InvocationHandler {
    
        // 被代理的类
        private Person person;
    
        public Object getInstance(Person person) {
            this.person = person;
            Class<?> clazz = person.getClass();
            // 用来生成一个新的对象(字节码重组实现)
            return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是中介收到用户需求");
            method.invoke(this.person, args);
            System.out.println("用户满意,付首付");
            return null;
        }
    }
    
    package com.vnsun.proxy.jdk;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class JDKBoos implements InvocationHandler {
    
        // 被代理的类
        private Person person;
    
        public Object getInstance(Person person) {
            this.person = person;
            Class<?> clazz = person.getClass();
            // 用来生成一个新的对象(字节码重组实现)
            return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是Boos,开始投递你的简历");
            method.invoke(this.person, args);
            System.out.println("面试,入职");
            return null;
        }
    }
    
    package com.vnsun.proxy.test;
    
    import com.vnsun.proxy.jdk.JDKBoos;
    import com.vnsun.proxy.jdk.JDKMedium;
    import com.vnsun.proxy.jdk.Person;
    import com.vnsun.proxy.jdk.Zhangsan;
    
    public class JdkProxyTest {
    
        public static void main(String[] args) {
            Person z = (Person)new JDKMedium().getInstance(new Zhangsan());
            z.buyHouse();
            Person z1 = (Person)new JDKBoos().getInstance(new Zhangsan());
            z1.job();
            System.out.println(z1);
        }
    }
    

    这时,随着业务扩展,也有了更专业的中介代理张三的需求,专人做专事,符合单一职责原则。而且关注点也放在了业务上,符合开放闭合原则
    这里注意,打印出来的z1并不是被代理对象Zhangsan也不是接口Person,而是$Proxy0,不知道哪里的一个东西,本文后续会介绍这个东西


    image.png

    JDK1.3开始提供了对动态代理的支持,java实现动态代理需要位于java.lang.reflect包中的一些类

    Proxy类

    Proxy类提供了用于创建动态代理类和实例对象的方法,它是所创建的动态代理类的父类,最常用的方法如下:
    1.)public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 该方法用于返回一个Class类型的代理类,在参数中需要提供类加载器并需要指定代理的接口数组(与真实主题类的接口列表一致)
    2.)public static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces, InvocationHadler h)该方法用于返回一个动态创建的代理类的实例,方法中的第1个参数loader表示代理类的类加载器,第2个参数interface是表示代理类所实现的接口列表,第3个参数h表示所指派的调用处理程序类

    InvocationHadler 接口

    InvocationHadler 接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHadler 接口的子类)。在该接口中声明了如下方法。
    public Object invoke(Object proxy, Method method, Object[] args)。该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时将自动调用该方法。invoke() 方法包含3个参数,其中第1个参数proxy表示代理类的实例,第2个参数method表示需要代理的方法,第3个参数args表示代理方法的参数数组。
    动态代理类需要在运行时之地东所代理真实主题类的接口,客户端在调用动态代理对象的方法时,调用请求会将请求自动转发给InvocationHandler对象的invoke()方法,有invole()方法来实现对请求的统一处理

    cglib实现动态代理

    什么是 CGLIB?

    CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。

    CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib

    CGLIB 原理

    CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

    CGLIB 底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

    CGLIB缺点:对于final方法,无法进行代理。

    CGLIB 的应用

    广泛的被许多AOP的框架使用,例如Spring AOP和dynaop。Hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联

    为什么使用 CGLIB?

    CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了

    Jar包

    • cglib-nodep-2.2.jar:使用nodep包不需要关联asm的jar包,jar包内部包含asm的类.
    • cglib-2.2.jar:使用此jar包需要关联asm的jar包,否则运行时报错

    实现

    public class Zhangsan implements Person{
    
        /**
         * 买房子
         */
        @Override
        public void buyHouse() {
            System.out.println("张三买房子,三室一厅一厨一卫120平");
        }
    
        /**
         * 找工作
         */
        @Override
        public void job() {
            System.out.println("找工作");
        }
    }
    
    package com.vnsun.proxy.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * cglib实现动态代理
     */
    public class CglibMedium implements MethodInterceptor {
    
        public Object getInstance(Class<?> clazz) throws Exception {
            Enhancer enhancer = new Enhancer();
            // 把那个类设置为即将生成新类的父类
            enhancer.setSuperclass(clazz);
            // 设置调用者
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("我是中介,开始介绍房子");
            Object o1 = methodProxy.invokeSuper(o, objects);
            System.out.println("满意,购买");
            return o1;
        }
    }
    

    返回的obj对象是一个被代理之后生成的新的对象实例

    public static void main(String[] args) {
            try {
                Person obj = (Person)new CglibMedium().getInstance(Zhangsan.class);
                obj.buyHouse();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    深究下JDK实现动态代理的原理

    首先回顾一下jdk代理的流程

    1. 拿到被代理对象的引用,并且获取它所有的接口,通过反射获取
    2. JDK Proxy类重新生成一个新的类,同时新的类要实现被代理类的所有实现的接口
    3. 动态生成java代码,把新加的的业务逻辑方法由逻辑代码去调用(运行时在代码中体现)
    4. 编译新生成的java代码,class
    5. 再把class文件加载到JVM内存中去
      以上的过程就是字节码重组

    书接上文,你是否记得JDK代理生成的 $Proxy0 这个对象,下面来探究一下里面是什么
    持久化$Proxy0实例

    // 在JDK中有个规范,$符开头的一般都是自动生成的、比如内部类
    byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
    try (FileOutputStream ous = new FileOutputStream("E:/$Proxy0.class")) {
         ous.write(bytes);
    } catch (Exception e) {
          e.printStackTrace();
    }
    

    利用IDEA工具反编译$Proxy0类内容

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    import com.vnsun.proxy.jdk.Person;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements Person {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m4;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void job() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void buyHouse() throws  {
            try {
                // h是其中实现InvocationHandler的实现类
                super.h.invoke(this, m4, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("com.vnsun.proxy.jdk.Person").getMethod("job");
                m4 = Class.forName("com.vnsun.proxy.jdk.Person").getMethod("buyHouse");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    Proxy源码中的关键代码

        /**
         * the invocation handler for this proxy instance.
         * @serial
         */
        protected InvocationHandler h;
    
        @CallerSensitive
        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }
    

    以上是JDK生成的源代码,接下来来模拟一下此过程

    JDK实现模拟

    package com.vnsun.proxy.custom;
    
    import javax.tools.JavaCompiler;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    import java.io.File;
    import java.io.FileWriter;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    
    /**
     * 代理生成类
     * @author vnsun
     */
    public class VNProxy {
    
        private final static String LN = "\r\n";
    
        public static Object newProxyInstance(VNClassLoader classLoader, Class<?>[] interfaces, VNInvocationHandler h) {
            // 动态生成源代码,java文件
            String src = generateSrc(interfaces);
            // 持久化到磁盘
            String filePath = VNProxy.class.getResource("").getPath();
            File f = new File(filePath + "$Proxy0.java");
            try {
                FileWriter fw  = new FileWriter(f);
                fw.write(src);
                fw.flush();
                fw.close();
    
                // 把生成java源码文件编译成class文件
                JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
                StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
                Iterable iterable = manager.getJavaFileObjects(f);
                JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
                task.call();
                manager.close();
                // 把编译后的文件加载到JVM中
                Class<?> proxyClass = classLoader.findClass("$Proxy0");
                Constructor<?> c = proxyClass.getConstructor(VNInvocationHandler.class);
                f.delete();
                // 返回字节码重组后的新的代理对象
                return c.newInstance(h);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
    
            return null;
        }
    
        private static String generateSrc(Class<?>[] interfaces) {
            StringBuilder sb = new StringBuilder();
            sb.append("package com.vnsun.proxy.custom;" + LN);
            sb.append("import com.vnsun.proxy.jdk.Person;" + LN);
            sb.append("import java.lang.reflect.Method;" + LN);
            sb.append("public class $Proxy0 implements " + interfaces[0].getName() + " { " + LN);
    
                sb.append("VNInvocationHandler h;" + LN);
                sb.append("public $Proxy0(VNInvocationHandler h) {" + LN);
                    sb.append("this.h = h;"+LN);
                sb.append("}"+LN);
    
            for (Method m: interfaces[0].getMethods()) {
                sb.append("public " + m.getReturnType().getName()+ " " + m.getName() + "() {" + LN);
                    sb.append("try {"+LN);
                        sb.append("Method m =" + interfaces[0].getName()+".class.getMethod(\""+m.getName()+"\");");
                        sb.append("this.h.invoke(this,m,null);");
                    sb.append("} catch(Throwable e) {"+LN);
                        sb.append("e.printStackTrace();"+ LN);
                    sb.append("}" +LN);
                sb.append("}" +LN);
            }
            sb.append("}" +LN);
            return sb.toString();
        }
    }
    
    package com.vnsun.proxy.custom;
    
    import java.io.*;
    
    /**
     * 类加载JVM中
     * @author vnsun
     */
    public class VNClassLoader  extends ClassLoader{
    
        private File classPathFile;
    
        public VNClassLoader() {
            String classPath = VNClassLoader.class.getResource("").getPath();
            this.classPathFile = new File(classPath);
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            String className = VNClassLoader.class.getPackage().getName() + "." + name;
            if (classPathFile != null) {
                File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
                if (classFile.exists()) {
                    FileInputStream in = null;
                    ByteArrayOutputStream out = null;
                    try {
                        in = new FileInputStream(classFile);
                        out = new ByteArrayOutputStream();
                        byte[] buff = new byte[1024];
                        int len;
                        while((len = in.read(buff)) != -1) {
                            out.write(buff,0,len);
                        }
                        return defineClass(className, out.toByteArray(),0, out.size());
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            if (out != null) {
                                    out.close();
                            }
                            if (in != null) {
                                in.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return null;
        }
    }
    

    重写代理类调用过程

    package com.vnsun.proxy.custom;
    
    import java.lang.reflect.Method;
    
    public interface VNInvocationHandler {
    
        Object invoke(Object proxy, Method method, Object[] args) throws Exception;
    }
    
    
    package com.vnsun.proxy.custom;
    
    import com.vnsun.proxy.jdk.Person;
    
    import java.lang.reflect.Method;
    
    public class CustomMedium implements VNInvocationHandler {
    
        // 被代理的类
        private Person person;
    
        public Object getInstance(Person person) {
            this.person = person;
            Class<?> clazz = person.getClass();
            // 用来生成一个新的对象(字节码重组实现)
            return VNProxy.newProxyInstance(new VNClassLoader(), clazz.getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
            System.out.println("我是中介收到用户需求");
            Object obj = method.invoke(this.person, args);
            System.out.println("用户满意,付首付");
            return obj;
        }
    }
    
    

    测试结果

    package com.vnsun.proxy.custom;
    
    import com.vnsun.proxy.jdk.Person;
    import com.vnsun.proxy.jdk.Zhangsan;
    
    public class CustomProxyTest {
        public static void main(String[] args) {
            Person per = (Person)new CustomMedium().getInstance(new Zhangsan());
            System.out.println(per.getClass());
            per.buyHouse();
        }
    }
    

    总结

    • 代理分为静态代理和动态代理两种
    • 在一定程度上降低了系统的耦合度,但增加了业务的复杂度
    • 增加和更换代理类无需修改源码。符合开闭原则,具有较好的灵活性
    • 本质上是为了增强现有代码功能

    相关文章

      网友评论

          本文标题:代理模式——对象结构型模式

          本文链接:https://www.haomeiwen.com/subject/nbezuhtx.html