Java 設計模式系列(十二)代理模式

Java 設計模式系列(十二)代理模式

代理模式是對象的結構模式。代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。java

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();
}

很明顯靜態代理每一個被代理的類都要手寫一個代理類,當修改被代理的類時也要修改對應的代理類。解決這個問題則是由程序來生成對應的代理類,這就是動態代理。git

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 中所要進行動態代理的類必需要實現一個接口 ,也就是說只能對該類所實現接口中定義的方法進行代理,這在實際編程中具備必定的侷限性,並且使用反射的效率也並非很高。github

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: $Proxy0app

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 接口ide

@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

天天用心記錄一點點。內容也許不重要,但習慣很重要!

相關文章
相關標籤/搜索