• 静态代理与动态代理


    简介

    代理模式作用是:在不修改被代理对象功能的基础上,通过对代理类进行扩展,进行一些功能上的附加与增强。如Spring AOP就是用动态代理来实现的。下图可以表述代理模式的流程:

     

    静态代理

    我们在app上看电影的时候会看到很多广告,app厂家拿到电影的代理权限后,再通过穿插各种广告来增加自己的收益,下面通过代理模式来模拟一下

    创建代表电影播放的接口 

    1. public interface Movie {
    2. void play();
    3. }

    实现电影的播放接口

    1. public class MovieProxy implements Movie {
    2. private String movieName;
    3. public MovieProxy(String movieName) {
    4. this.movieName = movieName;
    5. }
    6. @Override
    7. public void play() {
    8. System.out.println("正在播放" + movieName);
    9. }
    10. }

    创建电影的代理类,表示上述例子中的app 

    1. public class Player implements Movie {
    2. private String movieName;
    3. public Player(String movieName) {
    4. this.movieName = movieName;
    5. }
    6. @Override
    7. public void play() {
    8. System.out.println("片头广告....");
    9. MovieProxy movieProxy = new MovieProxy(movieName);
    10. movieProxy.play();
    11. System.out.println("片尾广告....");
    12. }
    13. }

    测试类

    1. public class Test {
    2. public static void main(String[] args) {
    3. Player player = new Player("功夫熊猫");
    4. player.play();
    5. }
    6. }

    结果

    以上就是静态代理的实现,通过拓展代理类进行一些功能的拓展或增强 ,什么叫静态代理呢?就是增强的内容是事先定义好的,如果需要在运行时进行增强就需要用到动态代理。

    动态代理

    JDK动态代理

    JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,用JDK动态代理模拟看电影的例子

    代码演示

    创建接口,JDK代理中被代理的对象必须实现接口

    1. public interface Movie {
    2. void play();
    3. }

    接口实现类

    1. public class MyMovie implements Movie {
    2. @Override
    3. public void play() {
    4. System.out.println("正在播放电影");
    5. }
    6. }

    测试类

    1. public class Test {
    2. public static void main(String[] args) {
    3. // 保存动态代理生成的文件
    4. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    5. ClassLoader classLoader = MyMovie.class.getClassLoader();
    6. Class[] interfaces = MyMovie.class.getInterfaces();
    7. // proxy代理对象本身
    8. // method被执行的方法
    9. // args1方法参数
    10. Movie movie = (Movie) Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args1) -> {
    11. MyMovie myMovie = new MyMovie();
    12. System.out.println("片头广告");
    13. Object invoke = method.invoke(myMovie, args1);
    14. System.out.println("片尾广告");
    15. return invoke;
    16. });
    17. movie.play();
    18. }
    19. }

    源码分析

    newProxyInstance方法

    在此方法中可以分为三个步骤

    1)查找或生成指定的代理接口类,如果将sun.misc.ProxyGenerator.saveGeneratedFiles设置为true,则会在com.sun.proxy路径下保存生成的代理类

     2)获取构造方法

    3)利用反射实例化代理类

     

    CGLIB代理 

    cglib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

    代码演示

     创建被代理类 

    1. public class Movie {
    2. public void play() {
    3. System.out.println("播放电影");
    4. }
    5. }

    创建回调方法

    1. public class MyMethodInterceptor implements MethodInterceptor {
    2. @Override
    3. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    4. System.out.println("片头广告");
    5. Object o1 = methodProxy.invokeSuper(o, objects);
    6. System.out.println("片尾广告");
    7. return o1;
    8. }
    9. }

    测试类

    1. public class Test {
    2. public static void main(String[] args) {
    3. // 生成的代理类存储路径
    4. System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,".\\code");
    5. // 在创建Enhancer时会通过动态代理将EnhanceKey创建好,
    6. Enhancer enhancer = new Enhancer();
    7. // 设置父类
    8. enhancer.setSuperclass(Movie.class);
    9. // 设置回调方法
    10. enhancer.setCallback(new MyMethodInterceptor());
    11. // 创建代理类
    12. Movie movie = (Movie) enhancer.create();
    13. movie.play();
    14. }
    15. }

    总结

    JDK和CGLIB动态代理的区别:

    1. JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
      CGLIB代理使用字节码处理框架asm,对代理对象类的class文件加载进来,通过修改字节码生成子类。
    2. JDK创建代理对象效率较高,执行效率较低;
      CGLIB创建代理对象效率较低,执行效率高。
    3. JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;
      CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,因为是继承机制,不能代理final修饰的类。
  • 相关阅读:
    第2-3-2章 环境搭建-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss
    Spring Cloud Netfilx Ribbon(负载均衡工具)
    2022年全球及中国工厂模拟软件行业头部企业市场占有率及排名调研报告
    猿创征文|一名大三学生的前端学习之路(真情流露)
    Android问题解决
    Redis从入门到放弃(5):事务
    用户级线程和内核级线程
    Mac环境部署单机版Hbase及使用JavaAPI对Hbase增删改查
    C# 实现电子签名
    【C++并发编程】(一)线程管理
  • 原文地址:https://blog.csdn.net/qq_35597828/article/details/126287829