# 代理模式(Proxy Pattern)

目录: https://note.dolyw.com/design/ (opens new window)

代码地址

# 1. 介绍

代理模式为其他对象提供一种代理,以控制对这个对象的访问,代理对象在客户端和目标对象之间起到了中介的作用

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式

# 1.1. 特点

待补充

# 1.2. 结构

待补充

# 1.3. 优缺点

  • 意图:为其他对象提供一种代理以控制对这个对象的访问
  • 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层,不修改源码的基础上
  • 何时使用:想在访问一个类时做一些控制
  • 如何解决:增加中间层
  • 关键代码:实现与被代理类组合
  • 优点:
    • 将代理对象和真实被调用的目标对象分离,职责清晰
    • 降低耦合,高扩展性
    • 保护目标对象,增强目标对象
  • 缺点:
    • 由于在客户端和真实对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
    • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂
    • 造成类的数目增加,增加复杂度
  • 应用场景:
    • 买火车票不一定在火车站买,也可以去代售点
    • 远程代理,虚拟代理等
    • Windows 里面的快捷方式
    • Spring AOP

区别

  • 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口
  • 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制

# 2. 实现

分为静态代理和动态代理,动态代理一般实现方式就是 JDK 代理和 CgLib 代理,如下我们针对下面两个类进行代理处理,音乐媒体类是实现了接口,电影媒体类未实现接口

  • 音乐媒体类
/**
 * 媒体接口
 */
public interface Media {

    void play(String file);

}

/**
 * 实现媒体接口 - 音乐媒体
 */
public class MusicMediaImpl implements Media {

    @Override
    public void play(String file) {
        System.out.println("Play Music: " + file);
    }

}
  • 电影媒体类
/**
 * 直接的电影媒体类
 */
public class MovieMedia {

    public void play(String file) {
        System.out.println("Play Movie: " + file);
    }

}

# 2.1. 静态代理

通过在代码中显式地定义了一个代理类,在代理类中通过同名的方法对目标对象的方法进行包装,客户端通过调用代理类的方法来调用目标对象的方法,如下针对音乐媒体类进行两次代理

/**
 * 静态代理一
 *
 * @author wliduo[i@dolyw.com]
 * @date 2023/1/31 16:43
 */
public class ProxyMediaImpl implements Media {

    Media media;

    public ProxyMediaImpl(Media media) {
        this.media = media;
    }

    @Override
    public void play(String file) {
        System.out.println("Start");
        media.play(file);
        System.out.println("End");
    }

}
/**
 * 静态代理二
 *
 * @author wliduo[i@dolyw.com]
 * @date 2023/1/31 16:43
 */
public class TimeProxyMediaImpl implements Media {

    Media media;

    public TimeProxyMediaImpl(Media media) {
        this.media = media;
    }

    @Override
    public void play(String file) {
        System.out.println(System.currentTimeMillis());
        media.play(file);
        System.out.println(System.currentTimeMillis());
    }

}
/**
 * 静态代理事先知道要代理的是什么,目标角色固定不灵活
 *
 * @author wliduo[i@dolyw.com]
 * @date 2023/1/31 16:41
 */
public class Main {

    public static void main(String[] args) {
        // 组合嵌套多个代理类
        Media musicMedia = new MusicMediaImpl();
        // musicMedia.play("音乐");
        Media proxyMedia = new ProxyMediaImpl(musicMedia);
        // proxyMedia.play("音乐");
        Media timeProxyMedia = new TimeProxyMediaImpl(proxyMedia);
        timeProxyMedia.play("音乐");
    }

}
1675155078395
Start
Play Music: 音乐
End
1675155078396

# 2.2. 动态代理

使用 JDK 代理和 CgLib 代理实现

  • JDK 代理
/**
 * JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求
 * 1. 实现InvocationHandler接口,重写invoke()
 * 2. 使用Proxy.newProxyInstance()产生代理对象
 * 3. 被代理的对象必须要实现接口
 *
 * JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类
 * JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
 * JDK创建代理对象效率较高,执行效率较低
 *
 * @author wliduo[i@dolyw.com]
 * @date 2023/1/31 16:52
 */
public class JdkDynamicProxy implements InvocationHandler {

    /**
     * 代理的目标对象
     */
    private Object object;

    public JdkDynamicProxy(Object object) {
        this.object = object;
    }

    public Object proxy() {
        Class<?> clazz = object.getClass();
        // 生成代理对象
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeMethod(object);
        Object result = method.invoke(object, args);
        afterMethod(object);
        return result;
    }

    private void beforeMethod(Object object) {
        if (object instanceof MusicMediaImpl) {
            System.out.println("MusicMediaImpl Start");
        } else if (object instanceof MovieMedia) {
            System.out.println("MovieMedia Start");
        } else {
            throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
        }
    }

    private void afterMethod(Object object) {
        if (object instanceof MusicMediaImpl) {
            System.out.println("MusicMediaImpl End");
        } else if (object instanceof MovieMedia) {
            System.out.println("MovieMedia End");
        } else {
            throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
        }
    }
}
  • CgLib 代理
/**
 * CGLib必须依赖于CGLib的类库,需要满足以下要求
 * 1. 实现MethodInterceptor接口,重写intercept()
 * 2. 使用Enhancer对象.create()产生代理对象
 * 3. 因为是继承机制,不能代理final修饰的类
 * 
 * CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的
 * CGLIB代理使用字节码处理框架asm,对代理对象类的class文件加载进来,通过修改字节码生成子类
 * CGLIB创建代理对象效率较低,执行效率高
 *
 * @author wliduo[i@dolyw.com]
 * @date 2023/1/31 16:52
 */
public class CglibDynamicProxy implements MethodInterceptor {

    /**
     * 代理的目标对象
     */
    private Object object;

    public CglibDynamicProxy(Object object) {
        this.object = object;
    }

    public Object proxy() {
        Class<?> clazz = object.getClass();
        // 生成代理对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println(proxy.getClass().getSuperclass().getName());
        beforeMethod(object);
        Object result = methodProxy.invokeSuper(proxy, objects);
        afterMethod(object);
        return result;
    }

    private void beforeMethod(Object object) {
        if (object instanceof MusicMediaImpl) {
            System.out.println("MusicMediaImpl Start");
        } else if (object instanceof MovieMedia) {
            System.out.println("MovieMedia Start");
        } else {
            throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
        }
    }

    private void afterMethod(Object object) {
        if (object instanceof MusicMediaImpl) {
            System.out.println("MusicMediaImpl End");
        } else if (object instanceof MovieMedia) {
            System.out.println("MovieMedia End");
        } else {
            throw new RuntimeException("暂不支持代理" + object.getClass() + "类型");
        }
    }
}
  • 验证一
/**
 * 相比于静态代理,动态代理在创建代理对象上更加的灵活
 * 
 * 验证一,音乐媒体类实现接口都可以成功代理
 *
 * @author wliduo[i@dolyw.com]
 * @date 2023/1/31 16:55
 */
public class Main {

    public static void main(String[] args) {
        Media musicMedia = new MusicMediaImpl();
        // musicMedia.play("音乐");
        // 使用Jdk动态代理接口实现方式
        JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(musicMedia);
        Media jdkDynamicProxyMedia = (Media) jdkDynamicProxy.proxy();
        jdkDynamicProxyMedia.play("Jdk音乐");

        System.out.println("----------");

        // 使用Cglib动态代理接口实现方式
        CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy(musicMedia);
        Media cglibDynamicProxyMedia = (Media) cglibDynamicProxy.proxy();
        cglibDynamicProxyMedia.play("Cglib音乐");
    }
}
MusicMediaImpl Start
Play Music: Jdk音乐
MusicMediaImpl End
----------
com.design.proxy.MusicMediaImpl
MusicMediaImpl Start
Play Music: Cglib音乐
MusicMediaImpl End
  • 验证二
/**
 * 相比于静态代理,动态代理在创建代理对象上更加的灵活
 *
 * 验证二,电影媒体类未实现接口只有Cglib可以代理
 *
 * @author wliduo[i@dolyw.com]
 * @date 2023/1/31 16:55
 */
public class Main2 {

    public static void main(String[] args) {
        MovieMedia movieMedia = new MovieMedia();
        // movieMedia.play("电影");
        // 使用Cglib动态代理Class类
        CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy(movieMedia);
        MovieMedia cglibDynamicProxyMedia = (MovieMedia) cglibDynamicProxy.proxy();
        cglibDynamicProxyMedia.play("Cglib电影");

        System.out.println("----------");

        // 使用Jdk动态代理Class类,运行失败,Jdk必须实现接口才可以代理
        JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(movieMedia);
        MovieMedia jdkDynamicProxyMedia = (MovieMedia) jdkDynamicProxy.proxy();
        jdkDynamicProxyMedia.play("Jdk电影");
    }
}
com.design.proxy.MovieMedia
MovieMedia Start
Play Movie: Cglib电影
MovieMedia End
----------
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.design.proxy.MovieMedia
	at com.design.proxy.v2.Main2.main(Main2.java:27)

# 2.3. 总结

  • 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道
  • JDK 代理只能对实现了接口的类进行代理,而 CgLib 代理可以对普通类进行代理;
  • JDK 代理是通过反射的方式来实现动态代理,而 CgLib 则是通过为目标类生成一个子类的方式来实现动态代理

参考

上次更新时间: 2023-12-15 03:14:55