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 = "\r\n";

    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

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

posted on 2018-05-09 06:43  binarylei  阅读(186)  评论(0编辑  收藏  举报

导航