设为首页 加入收藏

TOP

万字干货_JDK动态代理及其源码解析 拿捏了(一)
2023-07-25 21:32:41 】 浏览:98
Tags:干货 _JDK 解析

作者:小牛呼噜噜 | https://xiaoniuhululu.com
计算机内功、JAVA底层、面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」

大家好,我是呼噜噜,在之前的一篇文章-Java注解中,我们详细讲解了Java注解及其原理,其中反射调用注解的时候(class.getAnnotation),会继承动态代理类AnotationInvocationHandler,创建注解的代理实例,来让开发者后续操作注解。本篇文章将深入聊聊什么是动态代理

代理模式

首先我们要明白动态代理属于设计模式中的代理模式
所谓代理模式是指通过访问目标对象的代理对象,再由代理对象去访问目标对象

通俗点讲,本来我们只可直接去商店买药 ;突然有一天,我们的车坏了,导致我们无法直接去商店买药。这个时候,又急着需要药,我们可以打电话叫代理人:小张去商店帮我们买药,然后再让他把药给我们带回来。这样最终我们拿到了药。

这样一来就可以在不修改原目标对象的前提下,提供额外的功能操作,实现扩展目标对象的功能。

静态代理

代理模式有静态代理和动态代理两种实现方式

静态代理和动态代理的区别?什么是静态、动态?

从 JVM 层面来说:

  1. 静态代理:
  • 在编译时就已经实现,编译完成后代理类是一个实际的class文件
  • 代理类和委托类的关系在程序运行前就已确定
  1. 动态代理:
  • 在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
  • 代理类和委托类的关系是在程序运行时确定

静态代理的使用步骤

我们先聊聊静态代理, 其一般使用步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后就可以在代理类的对应方法调用目标类中的对应方法

示例

我们来模拟一下上文买药的例子,另外我们想代理人顺便帮我们在买点水果啥的

定义一个接口,来代表我们的目标

public interface OurService {
    void buyMed();
}

再实现我们的接口

public class OurServiceImpl implements OurService {
    @Override
    public void buyMed() {
        System.out.println("买药。。。");
    }
}

创建代理类并额外附表其他目标,比如买蛋糕、水果啊之类的

public class MyStaticProxy implements OurService {
    private OurService ourService;

    public MyStaticProxy(OurService ourService) {
        this.ourService = ourService;
    }
    @Override
    public void buyMed() {

        System.out.println("买药前,先去买蛋糕。。。");
        ourService.buyMed();
        System.out.println("买药后,再去买水果。。。");
    }
}

最后测试类

public class TestStaticProxy {
    public static void main(String[] args) {
        OurService ourService = new OurServiceImpl();
        //userService.buyMed(); 直接执行
        MyStaticProxy myStaticProxy = new MyStaticProxy(ourService);
        myStaticProxy.buyMed();//委托 代理类 去执行
    }
}

结果:

买药前,先去买蛋糕。。。

买药。。。

买药后,再去买水果。。。

静态代理的缺陷

从上面的例子,我们可以发现静态代理非常容易地实现了对一个类的代理操作,但是也有几个缺点:

  1. 静态代理不能使一个代理类反复作用于多个目标对象,代理对象直接持有目标对象的引用,这导致代理对象和目标对象类型紧密耦合了在一起,需要对每个目标类都单独写一个代理类
  2. 不易维护,一旦接口更改,代理类和目标类都需要更改,比较繁琐。

解决静态代理的缺陷的思路

通过上文我们可以发现静态代理最大的缺点,就是不能使一个代理类反复作用于多个目标对象,要想实现不同的增强功能,必须编写不同的代理类,耦合性高。那我们能不能对于不同的源程序,让JVM自动生成对应的代理类?如果可以的话,这样不就可以解决问题了嘛。

首先我们得思考一个问题,java怎样才能动态地生成代理类?
我们先来回顾一下对象的创建过程

推荐阅读:https://mp.weixin.qq.com/s/tsbDfyYLqr3ctzwHirQ8UQ

创建一个实例对象的底层逻辑,其实与.class文件和Class对象息息相关
"没有对象, 那就new一个",对于每个javar来说都太熟悉了,但这样往往忽视了底层的细节--最核心就是得到对应的Class对象

在文章https://mp.weixin.qq.com/s/v91bqRiKDWWgeNl1DIdaDQ中,我们聊到了JVM类的加载过程

加载阶段:指的是将类对应的.class文件中的二进制字节流读入到内存中,将这个字节流转化为方法区的运行时数据结构,然后在堆区创建一个java.lang.Class 对象,作为对方法区中这些数据的访问入口

其中将类对应的.class文件中的二进制字节流读入到内存中,JVM虚拟机规范并没有
指明二进制字节流必须得从某个Class文件中获取,确切地说是根本没有指明要从哪里获取、如何获取。
所以获取类的二进制字节流(class字节码)有很多途径:

  • 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
  • 从网络中获取,典型的应用是 Applet
  • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
  • 由其他文件生成,典型场景是JSP应用,由JSP文件生成对应的Class文件。
  • 从数据库中读取,这种场景相对少见些,例如有些中间件服务器(如SAP Netweaver)可以选择 把程序安装到数据库中来完成程序代码在集群间的分发。
  • 可以从加密文件中获取,这是典型的防Class文件被反编译的保护措施,通过加载时解密Class文 件来保障程序运行逻辑不被窥探。

在笔者之前讲解Java反射的文章https://mp.weixin.qq.com/s/_n8HTIjkw7Emcunpb4-Iwg中,我们知晓:

  1. 类也是可以用来存储数据的,Class类就像 普通类的模板 一样
首页 上一页 1 2 3 4 5 6 7 下一页 尾页 1/9/9
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇this和super关键字 下一篇Java入门5(多态)

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目