06.代理模式

87 阅读4分钟

Proxy(代理模式)

描述

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

根据代理的创建时期,代理模式分为静态代理和动态代理。

  • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态:在程序运行时,运用反射机制动态创建而成

场景

使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

  • AOP
  • RPC

写法

静态代理

抽象主题

interface Subject {
    void request();
}

真实主题

class RealSubject implements Subject {
    public void request() {
        System.out.println("访问真实主题方法...");
    }
}

代理

class Proxy implements Subject {
    private RealSubject realSubject;
    public void request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.request();
        postRequest();
    }
    public void preRequest() {
        System.out.println("访问真实主题之前的预处理。");
    }
    public void postRequest() {
        System.out.println("访问真实主题之后的后续处理。");
    }
}

图解

3-1Q115093011523.gif

动态代理

基于JDK(需要传入目标类的实现):调用JDK自带的 Proxy 方法生成代理类,实现方式基于内部聚合,通过反射机制实现。

写法一:直接调用Proxy根据接口生成,传入实现 InvocationHandler 接口的具体业务增强类 BizInvocationHandler 。

Subject proxy2 = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, new BizInvocationHandler(subject));

实现对应的逻辑增强接口 InvocationHandle 时,应持有代理目标的引用,一般通过构造函数实现。

public class BizInvocationHandler implements InvocationHandler {
    // 这个是个动态代理的核心类,它实现了InvocationHandler接口,它的作用是在调用真实对象的方法之前和之后做一些事情。
    private Object target;
​
    public BizInvocationHandler(Object target) {
        this.target = target;
    }
​
    @Override
    public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable {
        System.out.println("before2");
        Object result = method.invoke(target, args);
        System.out.println("after2");
        return result;
    }
}

写法二:将生成逻辑封装到实现 InvocationHandler 接口的具体业务增强类中,简化调用逻辑。

public class DynamicProxy<T> implements InvocationHandler {
    private Object target;
    public DynamicProxy(Object target) {
        this.target = target;
    }
    T getInstance(T target) {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }
    private void before(){
        System.out.println("before");
    }
    private void after(){
        System.out.println("after");
    }
}

图解

image.png

基于Cglib(无需传入目标类的实现):使用第三方CGlibProxy,通过工具类Enhancer实现。

写法一:直接使用工具类Enhancer生成,传入实现 MethodInterceptor接口的具体业务增强类到Callback中。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Subject.class);
enhancer.setCallback(new BizMethodInterceptor());
Subject sub = (Subject) enhancer.create();
sub.realSubject();
public class BizMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("after");
        return result;
    }
}

写法二:将生成逻辑封装到实现 MethodInterceptor 接口的具体业务增强类中,简化调用逻辑。

public class CGlibProxy implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) throws Exception{
        //相当于Proxy,代理的工具类
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }

    private void before(){
        System.out.println("before");
    }

    private void after(){
        System.out.println("after");
    }
}

手写动态代理

类加载器:重写findClass方法,先读取目标类的字节码文件 .class 调用 defineClass 将二进制文件转化为内存中的 class;

public class GPClassLoader extends ClassLoader {

    private File classPathFile;
    public GPClassLoader(){
        String classPath = GPClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        String className = GPClassLoader.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();
                }
            }
        }
        return null;
    }
}

回调接口:用于完成代理模式下的逻辑增强

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

动态代理工具类:根据需要代理的接口实现代理类

public class GPProxy {
    public static Object newProxyInstance(GPClassLoader classLoader, Class<?> [] interfaces, GPInvocationHandler h){
        //1、动态生成源代码.java文件
        //2、Java文件输出磁盘
        //3、把生成的.java文件编译成.class文件
        //4、编译生成的.class文件加载到JVM中来
        //5、返回字节码重组以后的新的代理对象
        Class proxyClass =  classLoader.findClass("$Proxy0");
        Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
        /* 
        	将增强逻辑类通过构造函数放入代理类中
        	返回一个拥有增强逻辑引用,切实现代理目标接口的代理类。
        */
        return c.newInstance(h);
    }
}

优点

  • 客户端与目标类分离解耦,从而易于扩展
  • 起到控制保护目标对象的作用
  • 增强目标对象

缺点

  • 增加类的数目
  • 增加了代理对象,影响执行效率
  • 增加系统复杂度

理解

静态代理

代理类中包含代理目标Target类,实现Target类中所有接口,并整合代理的逻辑对代理对象功能进行增强。使用方式为 new 代理类传入代理对象。

动态代理

代理类通过JDK或者Cglib提供的方法实现代理,只是意义上的代理,实现机制和基础类图不是一致的。

其他

设计模式

不要纠结于具体实现的代码结构,设计模式更多的是概念上一致而非具体是现实上的一致。

JDK Proxy

  • 基于Proxy生成的代理类默认类名规则,num是根据不同代理接口计数的。
String proxyName = proxyPkg + proxyClassNamePrefix + num;
  • 生成的代理类继承了 Proxy ,其内部 h 指向了 实际的增强扩展业务逻辑的接口(InvocationHandler)实现类。
protected InvocationHandler h;