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

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"); 存储了真实对象方法的信息

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

最新文章

  1. 基于MVC4+EasyUI的Web开发框架形成之旅--附件上传组件uploadify的使用
  2. iOS iOS9.0 的CoreLocation定位
  3. Java:JSTL遍历数组,List,Set,Map
  4. 超级链接a中javascript:void(0)弹出另外一个框问题
  5. [ACM_数学] 大菲波数 (hdu oj 1715 ,java 大数)
  6. discuz x3在DIY模块中调用伪静态不成功,显示动态链接的解决办法
  7. 【Cocos2d-Js基础教学(2)类的使用和面向对象】
  8. php分享三十三:用php中的register_shutdown_function和fastcgi_finish_request
  9. sql 的错误处理功能很弱
  10. java14 处理流
  11. Qss All
  12. linux常见设备类型及文件系统
  13. Ubuntu 12.04(32位)下PHP环境的搭建(LAMP)
  14. 【转】char *str 和 char str[]的区别
  15. SQL 2005/2008 连接SQL 2000报18456错误
  16. C++函数式编程实现牛顿法
  17. 「Algospot」量化QUANTIZE
  18. C++入门笔记(二)变量和基本类型
  19. Ajax状态值及状态码整理
  20. php 生成xml文件

热门文章

  1. Week1 Team Homework #1 from Z.XML-项目选择思路--基于对曾经大作业项目的思考
  2. PostgreSQL 建库建表脚本
  3. MySQL初始4--去重
  4. systemPath
  5. setcookie函数
  6. [剑指Offer] 34.第一个只出现一次的数
  7. 【Python】Python中子类怎样调用父类方法
  8. elasticsearch-1.7.1 (es Windows 64)
  9. [洛谷P3377]【模板】左偏树(可并堆)
  10. Python之利用reduce函数求序列的最值及排序