在 Java 中动态代理和代理都很常见,几乎是所有主流框架都用到过的知识。在面试中也是经常被提到的话题,于是便总结了本文。

Java动态代理的基本原理为:被代理对象需要实现某个接口(这是前提),代理对象会拦截对被代理对象的方法调用,在其中可以全然抛弃被代理对象的方法实现而完成另外的功能,也可以在被代理对象方法调用的前后增加一些额外的功能。

动态代理可以为其他对象提供一个代理以控制对某个对象的访问。

代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

JDK 动态代理

动态代理的核心其实就是代理对象的生成,即 Proxy.newProxyInstance(classLoader, proxyInterface, handler)。

让我们进入newProxyInstance方法观摩下,核心代码其实就三行。

这个方法需要三个参数:

  • ClassLoader,用于加载代理类的 Loader 类,通常这个 Loader 和被代理的类是同一个 Loader 类。
  • Interfaces,是要被代理的那些那些接口。
  • InvocationHandler,就是用于执行除了被代理接口中方法之外的用户自定义的操作,他也是用户需要代理的最终目的。用户调用目标方法都被代理到 InvocationHandler 类中定义的唯一方法 invoke 中。
1
2
3
4
5
6
//获取代理类  
Class cl = getProxyClass(loader, interfaces);  
//获取带有InvocationHandler参数的构造方法  
Constructor cons = cl.getConstructor(constructorParams);  
//把handler传入构造方法生成实例  
return (Object) cons.newInstance(new Object[] { h });

一个典型的动态代理创建对象过程可分为以下四个步骤:

1、通过实现InvocationHandler接口创建调用处理器

1
IvocationHandler handler = new InvocationHandlerImpl(...);

2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类

1
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型

1
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入

1
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

为了简化对象创建过程,Proxy类中的newProxyInstance方法封装了2~4,只需两步即可完成代理对象的创建。

1
2
Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),
        new Class[]{Subject.class}, new InvocationHandlerImpl (real));

生成的proxySubject继承Proxy类实现Subject接口。实现的Subject的方法实际是调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的方法(Object result=method.invoke(proxied,args));
重点Proxy.newProxyInstance,源码分析,会在其他文档中单独总结记录。类Proxy的getProxyClass方法调用ProxyGenerator的 generateProxyClass方法产生ProxySubject.class的二进制数据。

创建代理对象时序图

获取代理类

getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:

在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 缓存的key使用接口名称生成的List  
Object key = Arrays.asList(interfaceNames);  
synchronized (cache) {  
    do {  
Object value = cache.get(key);  
         // 缓存里保存了代理类的引用  
if (value instanceof Reference) {  
    proxyClass = (Class) ((Reference) value).get();  
}  
if (proxyClass != null) {  
// 代理类已经存在则返回  
    return proxyClass;  
else if (value == pendingGenerationMarker) {  
    // 如果代理类正在产生,则等待  
    try {  
cache.wait();  
    catch (InterruptedException e) {  
    }  
    continue;  
else {  
    //没有代理类,则标记代理准备生成  
    cache.put(key, pendingGenerationMarker);  
    break;  
}  
    while (true);  
}

生成并加载代理类

代理类的生成主要是以下这两行代码:

1
2
3
4
//生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)   
proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);   
//使用类加载器将字节码加载到内存中   
proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);  

代理类的生成过程

ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用
JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//添加接口中定义的方法,此时方法体为空  
for (int i = 0; i < this.interfaces.length; i++) {  
  localObject1 = this.interfaces[i].getMethods();  
  for (int k = 0; k < localObject1.length; k++) {  
     addProxyMethod(localObject1[k], this.interfaces[i]);  
  }  
}  
//添加一个带有InvocationHandler的构造方法  
MethodInfo localMethodInfo = new MethodInfo("<init>""(Ljava/lang/reflect/InvocationHandler;)V"1);  
//循环生成方法体代码(省略)  
//方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略)  
this.cp.getInterfaceMethodRef("InvocationHandler""invoke""Object; Method; Object;")  
//将生成的字节码,写入硬盘,前面有个if判断,默认情况下不保存到硬盘。  
localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class");  
localFileOutputStream.write(this.val$classFile);

生成的代理类源码

那么通过以上分析,我们可以推出动态代理为我们生成了一个这样的代理类。把方法doSomeThing的方法体修改为调用LogInvocationHandler的invoke方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ProxyBusiness extends Proxy implements IBusiness, IBusiness2 {  
private LogInvocationHandler h;  
@Override  
public void doSomeThing2() {  
    try {  
        Method m = (h.target).getClass().getMethod("doSomeThing2",null);  
        h.invoke(this, m, null);  
    catch (Throwable e) {  
        // 异常处理(略)  
    }  
@Override  
public boolean doSomeThing() {  
    try {  
       Method m = (h.target).getClass().getMethod("doSomeThing"null);  
       return (Boolean) h.invoke(this, m, null);  
    catch (Throwable e) {  
        // 异常处理(略)  
    }  
    return false;  
}  
public ProxyBusiness(LogInvocationHandler h) {  
    this.h = h;  
}

测试代理的代码如下:

1
2
3
4
5
6
7
//测试
public static void main(String[] args) {  
    //构建AOP的Advice  
    LogInvocationHandler handler = new LogInvocationHandler(new Business());  
    new ProxyBusiness(handler).doSomeThing();  
    new ProxyBusiness(handler).doSomeThing2();  
}

下面看一个自定义代理的实现。

被代理类接口

1
2
3
public interface Subject   {  
  public void doSomething();  
}

被代理类

1
2
3
4
5
6
//目标对象
public class RealSubject implements Subject{
  public void doSomething() {
    System.out.println( "call doSomething()" );
  }
}

调用处理器(切面)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProxyHandler implements InvocationHandler   {  
  private Object proxied;  
  public ProxyHandler( Object proxied )   {  
    this.proxied = proxied;  
  }  
  public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable {  
//在转调具体目标对象之前,可以执行一些功能处理
System.out.println( "doSomething before" );
    //转调具体目标对象的方法
    return method.invoke( proxied, args); 
//在转调具体目标对象之后,可以执行一些功能处理
System.out.println( "doSomething after" );
  }
}

测试我们的代理实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DynamicProxy   {  
  public static void main( String args[] )   {  
    RealSubject real = new RealSubject();  
    Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),
     new Class[]{Subject.class}, new ProxyHandler(real));
    proxySubject.doSomething();
    //write proxySubject class binary data to file  
    createProxyClassFile();  
  }  
  public static void createProxyClassFile()   {  
    String name = "ProxySubject";  
    byte[] data = ProxyGenerator.generateProxyClass( name, new Class[] { Subject.class } );  
    try{  
      FileOutputStream out = new FileOutputStream( name + ".class" );  
      out.write( data );  
      out.close();  
    }catch( Exception e ) {  
      e.printStackTrace();  
    }  
  }  
}

运行结果:

1
2
3
doSomething before
call doSomething()
doSomething after

Proxy 接口

Proxy 的主要静态变量

1
2
3
4
5
6
7
8
9
10
11
12
13
// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map loaderToCache = new WeakHashMap();
// 标记:用于标记一个动态代理类正在被创建中
private static Object pendingGenerationMarker = new Object();
// 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());
// 关联的调用处理器引用
protected InvocationHandler h;
Proxy的构造方法
// 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
private Proxy() {}
// 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
protected Proxy(InvocationHandler h) {this.h = h;}

ProxySubject 源码

创建的代理类 ProxySubject.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.lang.reflect.*;  
public final class ProxySubject extends Proxy  implements Subject{  
    private static Method m1;  
    private static Method m0;  
    private static Method m3;  
    private static Method m2;  
    public ProxySubject(InvocationHandler invocationhandler){  
        super(invocationhandler);  
    }  
    public final boolean equals(Object obj){  
        try {  
            return ((Boolean)super.h.invoke(this, m1, new Object[] {  
                obj  
            })).booleanValue();  
        }catch(Error _ex) {
        }catch(Throwable throwable){  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
    public final int hashCode()   { 
        try  {  
            return ((Integer)super.h.invoke(this, m0, null)).intValue();  
        }catch(Error _ex) {
        }catch(Throwable throwable){  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
    /*关键部分*/
    public final void doSomething()  {  
        try {  
            // Proxy类中protected InvocationHandler h;关联的调用处理器引用
            super.h.invoke(this, m3, null);  
            return;  
        }catch(Error _ex) {  
        }catch(Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
    public final String toString() {  
        try {  
            return (String)super.h.invoke(this, m2, null);  
        catch(Error _ex) {
        catch(Throwable throwable){  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
    static{  
        try {  
            m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[] {  
                Class.forName("java.lang.Object")  
            });  
            m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);  
            m3 = Class.forName("Subject").getMethod("doSomething"new Class[0]);  
            m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);  
        }catch(NoSuchMethodException nosuchmethodexception)   {  
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
        }catch(ClassNotFoundException classnotfoundexception){  
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
        }  
    }  
}

CGLib 动态代理

动态字节码生成。使用动态字节码生成技术实现AOP原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以使用Cglib实现AOP不需要基于接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args) {  
    byteCodeGe();  
}  
public static void byteCodeGe() {  
    //创建一个织入器  
    Enhancer enhancer = new Enhancer();  
    //设置父类  
    enhancer.setSuperclass(Business.class);  
    //设置需要织入的逻辑  
    enhancer.setCallback(new LogIntercept());  
    //使用织入器创建子类  
    IBusiness2 newBusiness = (IBusiness2) enhancer.create();  
    newBusiness.doSomeThing2();  
}  
/** 
 * 记录日志 
 */  
public static class LogIntercept implements MethodInterceptor {  
    @Override  
    public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {  
        //执行原有逻辑,注意这里是invokeSuper  
        Object rev = proxy.invokeSuper(target, args);  
        //执行织入的日志  
        if (method.getName().equals("doSomeThing2")) {  
            System.out.println("记录日志");  
        }  
        return rev;  
    }  
}

转自:https://www.xttblog.com/?p=2732

JAVA 动态代理原理和实现的更多相关文章

  1. Java 动态代理原理图解 (附:2种实现方式详细对比)

    ​ 动态代理在 Java 中有着广泛的应用,例如:Spring AOP 面向切面编程,Hibernate 数据查询.以及 RPC Dubbo 远程调用等,都有非常多的实际应用@mikechen 目录 ...

  2. java动态代理原理

    我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...

  3. Java动态代理原理及其简单应用

    概念 代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互.代理的存在对于调用者来说是透明的,调用者看到的只是接口.代理对象则可以封装一些内部的处理逻辑,如访问控制.远程通信.日志.缓存等 ...

  4. 设计模式学习——JAVA动态代理原理分析

    一.JDK动态代理执行过程 上一篇我们讲了JDK动态代理的简单使用,今天我们就来研究一下它的原理. 首先我们回忆下上一篇的代码: public class Main { public static v ...

  5. 动态代理 原理简析(java. 动态编译,动态代理)

    动态代理: 1.动态编译 JavaCompiler.CompilationTask 动态编译想理解自己查API文档 2.反射被代理类 主要使用Method.invoke(Object o,Object ...

  6. java高级---->Java动态代理的原理

    Java动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程 ...

  7. Java Proxy和CGLIB动态代理原理

    动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译时就确定了,而动态代理的代理关 ...

  8. java动态代理实现与原理详细分析

    关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式    代理模式是常用的java设计模式, ...

  9. java动态代理实现与原理详细分析(代码层面解释了AOP的实现)

    关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式    代理模式是常用的java设计模式, ...

随机推荐

  1. iscroll.js实现上拉刷新,下拉加载更多,应用技巧项目实战

    上拉刷新,下拉加载更多...仿原生的效果----iscroll是一款做滚动效果的插件,具体介绍我就不废话,看官方文档,我只写下我项目开发的一些用到的用法: (如果不好使,调试你的css,想必是个很蛋疼 ...

  2. python doc格式转文本格式

    首先python是不能直接读写doc格式的文件的,这是python先天的缺陷.但是可以利用python-docx (0.8.6)库可以读取.docx文件或.txt文件,且一路畅通无阻. 这样的话,可以 ...

  3. 51Nod--1247 可能的路径(gcd)

    根据规则可知 假设 (a,b) 可以到达坐标(aa,bb) 那么 aa=a*x+b*y  x y 必定有解  所以 我们只要求两个坐标的gcd看是否相等就好 #include<bits/stdc ...

  4. CentOS6.5安装ElasticSearch6.2.3

    CentOS6.5安装ElasticSearch6.2.3 1.Elastic 需要 Java 8 环境.(安装步骤:http://www.cnblogs.com/hunttown/p/5450463 ...

  5. CentOS7使用firewalld防火墙配置端口

    安装启用firewalld防火墙 CentOS7默认的防火墙是firewalld 如果没有firewalld防火墙,可以执行yum install firewalld 命令进行安装 firewalld ...

  6. centos7 LNMP

    Nginx1.13.5 + PHP7.1.10 + MySQL5.7.19 一.安装Nginx 1.安装依赖扩展 # yum -y install wget openssl* gcc gcc-c++ ...

  7. 【Linux网络编程】TCP网络编程中connect()、listen()和accept()三者之间的关系

    [Linux网络编程]TCP网络编程中connect().listen()和accept()三者之间的关系 基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: conn ...

  8. Vue(小案例_vue+axios仿手机app)_实现用户评论

    一.前言 1.渲染评论列表 2.点击加载按钮,加载更多    3.提交评论 二.主要内容 1.评论列表一般是注册到一个全局的公共组件中 2.请求后台数据,渲染评论列表 (1)数据格式如下 地址 /ap ...

  9. 老男孩Python全栈学习 S9 日常作业 001

    1.简述变量命名规范 必须以字母.数字.下划线命名,且不能以数字开头 不能是python的关键字 不能以中文或者拼音作为变量名 命名格式推荐以驼峰式或者下划线连接命名 区分大小写 要有意义,具有可描述 ...

  10. 盒子显隐,伪类边框,盒子阴影,2d平面形变

    -盒子显隐 显隐的盒子尽量不影响其他盒子的布局 display:none; 消失的时候不占位置,显示的时候占位 opacity:0-1; 盒子透明度 overflow: hidden; 超出部分隐藏 ...