• Java 设计模式系列(十二)代理模式


    设计模式之美 - 代理模式

    设计模式之美目录:https://www.cnblogs.com/binarylei/p/8999236.html

    代理模式:给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。GoF 的《设计模式》一书中把 RPC 称作远程代理。其它应用场景如缓存、监控、统计、鉴权、限流、事务、幂等、日志等。

    package com.github.binarylei.design.proxy;
    
    public interface UserService {
        public void say();
    }
    
    public class UserServiceImpl implements UserService {
        @Override
        public void say() {
            System.out.println("Hello World!");
        }
    }
    

    1. 静态代理

    public void test() {
        UserServiceImpl obj = new UserServiceImpl();
        UserService userService = new UserService() {
            @Override
            public void say() {
                System.out.println("这是静态代理");
                obj.say();
            }
        };
        userService.say();
    }
    

    很明显静态代理每个被代理的类都要手写一个代理类,当修改被代理的类时也要修改对应的代理类。解决这个问题则是由程序来生成对应的代理类,这就是动态代理。

    2. 动态代理

    2.1 JDK 动态代理

    public void test1() throws Exception {
        UserServiceImpl obj = new UserServiceImpl();
    
        UserService userService = (UserService) Proxy.newProxyInstance(
                UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (method.getDeclaringClass() == Object.class) {
                            return method.invoke(obj, args);
                        } else {
                            System.out.println(proxy.getClass().getName());
                            System.out.println(method);
                            Object ret = method.invoke(obj, args);
                            return ret;
                        }
                    }
                });
        userService.say();
        System.out.println(userService);
    }
    

    注意:JDK 中所要进行动态代理的类必须要实现一个接口 ,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。

    2.2 CGLib 动态代理

    使用 CGLib 实现动态代理,完全不受代理类必须实现接口的限制,而且 CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,比使用 Java 反射效率要高。唯一需要注意的是,CGLib 不能对声明为 final 的方法进行代理,因为 CGLib 原理是动态生成被代理类的子类。

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.8</version>
    </dependency>
    
    public void test2() {
        Enhancer enhancer = new Enhancer();
        //1. 设置父类
        enhancer.setSuperclass(UserServiceImpl.class);
    
        //2. 设置回调函数
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    
                System.out.println(method + " proxy");
                Object ret = proxy.invokeSuper(obj, args);
                return null;
            }
        });
    
        //3. 获取代理对象
        UserService userService = (UserService) enhancer.create();
        userService.say();
    }
    

    参数:Object 为由 CGLib 动态生成的代理类实例,Method 为上文中实体类所调用的被代理的方法引用,Object[] 为参数值列表,MethodProxy 为生成的代理类对方法的代理引用。proxy.invokeSuper(obj, arg) 从代理实例的方法调用返回的值。

    3. 动态代理原理

    3.1 原理

    JDK 的动态代理实际上是在内存中生成了一个字节码类,并进行编译,加载。JVM 生成的类名称都是以 $ 开头,eg: $Proxy0

    public void test1() throws Exception {
        UserServiceImpl obj = new UserServiceImpl();
    
        UserService userService = (UserService) Proxy.newProxyInstance(
                UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(proxy.getClass().getName()); // $Proxy0
                        Object ret = method.invoke(obj, args);
                        return ret;
                    }
                });
        userService.say();
        System.out.println(userService);
    
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", obj.getClass().getInterfaces());
        FileOutputStream out = new FileOutputStream(
            this.getClass().getResource("").getPath() + "$Proxy0.class");
        out.write(bytes);
    }
    

    查看生成的 $Proxy0.class 类如下:

    import com.github.binarylei.design.proxy.UserService;
    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 UserService {
        private static Method m3;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
        
        public final void say() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m3 = Class.forName("com.github.binarylei.design.proxy.UserService").getMethod("say", new Class[0]);
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    
        // ...
    }
    

    3.2 手写动态代理

    (1) 定义 MyInvocationHandler 接口

    @FunctionalInterface
    public interface MyInvocationHandler {
    
        Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    }
    

    (2) 实现自己的 MyProxy 类

    package com.github.binarylei.design.proxy.my;
    
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    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: leigang
     * @version: 2018-10-02
     */
    public class MyProxy {
    
        public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) {
            FileWriter out = null;
            try {
                // 1. 动态生成源代码 .java 文件
                String src = generateSrc(interfaces);
    
                // 2. .java 文件生成到磁盘
                File file = new File(MyProxy.class.getResource("").getPath() + "$Proxy1.java");
                out = new FileWriter(file);
                out.write(src);
                out.flush();
                out.close();
    
                // 3. 把 .java 文件编译成 .class 文件
                JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
                StandardJavaFileManager manager = compiler.getStandardFileManager(
                        null, null, null);
                Iterable<? extends JavaFileObject> iterable = manager.getJavaFileObjects(file);
    
                JavaCompiler.CompilationTask task = compiler.getTask(
                        null, manager, null, null, null, iterable);
                task.call();
                manager.close();
    
                // 4. 编译生成的 .class 类到 JVM 中
                Class<?> clazz = Class.forName("com.github.binarylei.design.proxy.my.$Proxy1");
    
                // 5. 返回字节码重组以后新代理对象
                Constructor<?> constructor = clazz.getConstructor(MyInvocationHandler.class);
                return constructor.newInstance(h);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private static final String ln = "
    ";
    
        private static String generateSrc(Class<?>[] interfaces) {
            StringBuilder sb = new StringBuilder();
            sb.append("package com.github.binarylei.design.proxy.my;").append(ln);
            sb.append("import com.github.binarylei.design.proxy.UserService;").append(ln);
            sb.append("import java.lang.reflect.Method;").append(ln);
            sb.append("public final class $Proxy1 implements ").append(interfaces[0].getSimpleName()).append(" {").append(ln);
            sb.append("private static MyInvocationHandler h;").append(ln);
            sb.append("public $Proxy1(MyInvocationHandler h) throws Exception {").append(ln);
            sb.append("this.h = h;").append(ln);
            sb.append("}").append(ln);
    
            for (Class<?> clazz : interfaces) {
                Method[] methods = clazz.getMethods();
                for (Method m : methods) {
                    sb.append("public final void say() {").append(ln);
                    sb.append("try {").append(ln);
                    sb.append("Method m = Class.forName("").append(clazz.getName()).append("").getMethod("")
                            .append(m.getName()).append("", new Class[0]);").append(ln);
                    sb.append("h.invoke(this, m, (Object[]) null);").append(ln);
                    sb.append("} catch (Throwable e) {").append(ln);
                    sb.append("e.printStackTrace();").append(ln);
                    sb.append("}").append(ln);
                    sb.append("}").append(ln);
                }
            }
            sb.append("}").append(ln);
            return sb.toString();
        }
    
    }
    
    

    (3) 测试

    public void test3() {
        UserServiceImpl obj = new UserServiceImpl();
    
        UserService userService = (UserService) MyProxy.newProxyInstance(
                UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(),
                new MyInvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(proxy.getClass().getName());
                        System.out.println(method);
                        Object ret = method.invoke(obj, args);
                        return ret;
                    }
                });
        userService.say();
        System.out.println(userService);
    }
    
    

    参考:

    1. 实战CGLib系列文章 MethodInterceptor和Enhancer_CGLib:https://yq.aliyun.com/ziliao/296216

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    spring-boot-swagger2 使用手册
    mall整合Swagger-UI实现在线API文档
    MyBatis Generator 详解
    1046 划拳 (15分)
    1043 输出PATest (20分)
    1042 字符统计 (20分)
    1041 考试座位号 (15分)
    1040 有几个PAT (25分)
    1039 到底买不买 (20分)
    1038 统计同成绩学生 (20分)
  • 原文地址:https://www.cnblogs.com/binarylei/p/9012175.html
Copyright © 2020-2023  润新知