JDK 动态代理 源码简单分析

代理的作用就是在访问真实对象之前或者之后可以额外加入一些操作。

JDK  的动态代理 只需要 5 步。

  1. 真实对象必须要实现接口,首先创建一个接口

    public interface HelloWorld {
        void sayHellowWorld();
    }
  2. 创建真实对象。

    public class HelloWorldImpl implements HelloWorld {
        @Override
        public void sayHellowWorld() {
            System.out.println("Hello World !");
        }
    }
  3. 创建代理类

    public class JdkProxySImpleDemo implements InvocationHandler {
    
    //代理类必须实现 InvocationHandler  接口
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable{
    
            }
    
    }    
  4. 建立代理对象和真实对象之间的关系。

    public class JdkProxySImpleDemo implements InvocationHandler {
    
        //真实对象
        public Object target = null;
        //建立代理对象和真实对象的代理关系,并返回代理对象
        public Object bind(Object target){
            this.target = target;
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
    
        }
    }
  5. 实现代理方法。

     @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
            System.out.println("sayHelloWorld 执行前");
    
            //实现代理方法
            Object Obj = method.invoke(target,args);
            System.out.println("sayHelloWorld 执行后");
            return Obj;
    
        }
  6. 使用代理对象代理真实对象

     public static void main(String[] args) {
    
            JdkProxySImpleDemo porxy = new JdkProxySImpleDemo();
            HelloWorld  helloWorld  = (HelloWorld)porxy.bind(new HelloWorldImpl());
            helloWorld.sayHellowWorld();
        }
    
    //执行结果
    sayHelloWorld 执行前
    Hello World !
    sayHelloWorld 执行后

源码分析

1. 创建代理对象 使用的是Porxy类的静态方法  newProxyInstance  他需要三个参数。

  1. ClassLoader loader 真实对象的类加载器
  2. Class<?>[] interfaces 真实对象的接口
  3. 代理对象本身

2 创建一个对象的过程: .java 文件编译为.class 字节码文件,加载字节码文件生成Class对象,Class对象创建 实例对象。

3. 查找或者创建代理类的Class对象,使用   getProxyClass0(loader, intfs);  方法

只有同一个类加载器加载的字节码生成的对象才是同一个对象,这里的传入真实对象的类加载器,一来确定此代理类是否被此加载器加载,二来如果没有加载则指定使用真实对象的类加载器加载代理类。

真实对象实现的接口不能超过65535个。

4. proxyClassCache.get(loader, interfaces);  使用真实对象的类加载器加载代理对象的字节码,如果已经加载过则返回 Class 对象的副本,如果没有加载则使用 ProxyClassFactory 进行加载

这里出现了ConcurrentMap ,所有代理类的Class对象会存储在这。它是一个二级map,最外层map的key就是 类加载器,值也是一个ConcurrentMap ,这个map 的key存放代理类Class 对象,value 是一个Boolean 值 。

这里根据 类加载器 找值 如果为空则进行初始化创建 ConcurrentMap。

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); 生成代理类的字节码并创建代理类Class对象

apply 方法是Proxy类的内部类ProxyClassFactory的方法,它可以生成字节码,并且创建Class对象

首先会检查确保真实对象实现的接口是由同一个类加载器加载的,还会检查是否是接口

检查通过后就会生成代理类对象的字节码 .class

byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); 生成二进制字节码。

生成的字节码保存在缓存中,创建字节码比较耗费性能,所以一次创建好后保存在缓存中下次可以直接使用减少性能开销。

defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length); 这里使用 一个本地方法生成 代理类的Class对象。

5. 生成 代理类的Class对象后 创建一个 Factory 并将 Class对象放入二级ConcurrentMap 中

6.使用生成的代理类的Class对象构造实例对象

c1 就是 代理类的Class对象 ,return cons.newInstance(new Object[]{h});  创建代理对象的实例;

7. 由于代理类的字节码存在内存中所以无法直接反编译查看,可以使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 把字节码保存到本地

使用反编译工具打开$Ptoxy0.class(IDEA自带反编译工具)

可以看到 代理类 继承自 Proxy 类,实现了被代理类的接口。

这也是为什么被代理对象要实现接口,要想代理必须要有相同的方法替代真实方法执行,一种就是继承被代理类,另一种就是拥有共同接口,由于Java只能单继承所以只能实现共同的接口

8 查看 代理对象的 代理方法 发现它只是做了一个转发

super.h就是 代理对象自己 ,这相当于执行 代理对象重载的invoke方法

Method 对象  m3 = Class.forName("day0826_proxy.HelloWorld").getMethod("sayHellowWorld"); 存储了真实对象方法的信息

最后利用反射 调用真实对象的 方法。动态代理完成

原文地址:https://www.cnblogs.com/mibloom/p/9538883.html

时间: 2024-10-10 03:56:16

JDK 动态代理 源码简单分析的相关文章

netty 源码简单分析一

周末简单看了下netty5的源码,只看懂了个大概,记录下成果,方便下次再看的时候回忆. 上服务端代码: public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.grou

kafka 0.8.1 新producer 源码简单分析

1 背景 最近由于项目需要,需要使用kafka的producer.但是对于c++,kafka官方并没有很好的支持. 在kafka官网上可以找到0.8.x的客户端.可以使用的客户端有C版本客户端,此客户端虽然目前看来还较为活跃,但是代码问题还是较多的,而且对于c++的支持并不是很好. 还有c++版本,虽然该客户端是按照c++的思路设计,但是最近更新时间为2013年12月19日,已经很久没有更新了. 从官方了解到,kafka作者对于现有的producer和consumer的设计是不太满意的.他们打算

Javac源码简单分析之解析和填充符号表

一.说明 符号表是由一组符号地址和符号信息构成的表格.符号表中所登记的信息在编译的不同阶段都要用到,在语义分析(后面的步骤)中,符号表所登记的内容将用于语义检查和产生中间代码,在目标代码生成阶段,党对符号名进行地址分配时,符号表是地址分配的依据. 二.主要的类与方法 解析和填充符号表这个过程主要由com.sun.tools.javac.comp.Entry及com.sun.tools.javac.comp.MemberEnter两个类来实现的. com.sun.tools.javac.comp.

Javac源码简单分析之Javac简单介绍

一.简单介绍 javac 是java语言编程编译器.javac工具读由java语言编写的类和接口的定义,并将它们编译成字节代码的class文件. 二.源码获取 OpenJDK6源码:http://download.java.net/openjdk/jdk6/ Javac的源码就在OpenJDK源码里面. 或者在CSDN下载:http://download.csdn.net/detail/p_3er/7383741 三.Javac的包 Javac的公共入口点是com.sun.tools.javac

FFmpeg源码简单分析:结构体成员管理系统-AVOption

===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFmpeg源码结构图 - 编码 [通用] FFmpeg 源码简单分析:av_register_all() FFmpeg 源码简单分析:avcodec_register_all() FFmpeg 源码简单分析:内存的分配和释放(av_malloc().av_free()等) FFmpeg 源码简单分析:常

FFmpeg源码简单分析:libswscale的sws_scale()

===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFmpeg源码结构图 - 编码 [通用] FFmpeg 源码简单分析:av_register_all() FFmpeg 源码简单分析:avcodec_register_all() FFmpeg 源码简单分析:内存的分配和释放(av_malloc().av_free()等) FFmpeg 源码简单分析:常

Android属性动画AnimatorSet源码简单分析

跟上之前的两篇文章 Android属性动画ValueAnimator源码简单分析 Android属性动画ObjectAnimator源码简单分析 继续看AnimatorSet源码的大概过程. AnimatorSet 提供了一种把多个动画放到一起,按照某种特定的顺序来播放,比如一个接一个的播放或者多个动画一起播放. AnimatorSet简单使用随便举一个最简单的例子 //AnimatorSet AnimatorSet animSet = new AnimatorSet(); ObjectAnim

JDK动态代理和CGLIB动态代理+源码下载

在上一篇文章-java代理详解讲解实现机制,一种是继承另外一种是组合,而且通过做实现也证明使用组合的方式更加的灵活.之后提到了代理的两种种类,一种是静态代理,另外一种是动态代理.上一篇文件中着重介绍的是静态代理(相对于动态代理很容易理解).这一片文章就接着介绍动态代理. 动态代理实现的最终效果:通过以一个统一的方式实现对任意的接口/类的代理.相比较静态代理而言,我们可以不用再无限制的增加代理类,不用再写许多重复的代码.很符合面向对象设计原则中的"开闭原则":对修改关闭,对扩展开放. 动

Hessian 源码简单分析

Hessian 是一个rpc框架, 我们需要先写一个服务端, 然后在客户端远程的调用它即可. 服务端: 服务端通常和spring 做集成. 首先写一个接口: public interface HelloService { void sayHello(String name); } 然后一个实现,实现使用@Service("helloService")   实现spring bean注册. @Service("helloService") public class H