Java - 动态代理机制讲解(Proxy.newProxyInstance)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Java - 动态代理机制讲解(Proxy.newProxyInstance)

在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是Java的动态代理机制,所以本篇随笔就是对Java的动态机制进行一个回顾。


首先问一个问题,为什么需要动态代理?

  • 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。比如:每新添加了方法,还得继续写日志和验证。
  • 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。比如:现在此类的方法里都显示方法名,那么每个方法都得一个一个改。

机制讲解

在Java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看Java的API帮助文档是怎么样对这两个类进行描述的:

InvocationHandler:

InvocationHandleristheinterfaceimplementedbytheinvocationhandlerofaproxyinstance. 
Eachproxyinstancehasanassociatedinvocationhandler. Whenamethodisinvokedonaproxyinstance, themethodinvocationisencodedanddispatchedtotheinvokemethodofitsinvocationhandler.

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

Objectinvoke(Objectproxy, Methodmethod, Object[] args) throwsThrowable

我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?

Objectinvoke(Objectproxy, Methodmethod, Object[] args) throwsThrowableproxy:  -指代我们所代理的那个真实对象method: -指代的是我们所要调用真实对象的某个方法的Method对象args:  -指代的是调用真实对象某个方法时接受的参数

如果不是很明白,等下通过一个实例会对这几个参数进行更深的讲解。

接下来我们来看看 Proxy 这个类:

Proxyprovidesstaticmethodsforcreatingdynamicproxyclassesandinstances, anditisalsothesuperclassofalldynamicproxyclassescreatedbythosemethods. 

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

publicstaticObjectnewProxyInstance(ClassLoaderloader, Class<?>[] interfaces,  InvocationHandlerh)  throwsIllegalArgumentException
Returnsaninstanceofaproxyclassforthespecifiedinterfacesthatdispatchesmethodinvocationstothespecifiedinvocationhandler.

这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:

publicstaticObjectnewProxyInstance(ClassLoaderloader, Class<?>[] interfaces, InvocationHandlerh) throwsIllegalArgumentExceptionloader:      一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了h:           一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:

首先我们定义了一个Subject类型的接口,为其声明了两个方法:

publicinterfaceSubject{
publicvoidrent();
publicvoidhello(Stringstr);
}

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:

publicclassRealSubjectimplementsSubject{
@Overridepublicvoidrent()
    {
System.out.println("I want to rent my house");
    }
@Overridepublicvoidhello(Stringstr)
    {
System.out.println("hello: "+str);
    }
}

下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

publicclassDynamicProxyimplementsInvocationHandler{
// 这个就是我们要代理的真实对象privateObjectsubject;
// 构造方法,给我们要代理的真实对象赋初值publicDynamicProxy(Objectsubject)
    {
this.subject=subject;
    }
@OverridepublicObjectinvoke(Objectobject, Methodmethod, Object[] args)
throwsThrowable    {
// 在代理真实对象前我们可以添加一些自己的操作System.out.println("before rent house");
System.out.println("Method:"+method);
// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用method.invoke(subject, args);
// 在代理真实对象后我们也可以添加一些自己的操作System.out.println("after rent house");
returnnull;
    }
}

最后,来看看我们的Client类:

publicclassClient{
publicstaticvoidmain(String[] args)
    {
// 我们要代理的真实对象SubjectrealSubject=newRealSubject();
// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的InvocationHandlerhandler=newDynamicProxy(realSubject);
/** 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数* 第一个参数handler.getClass().getClassLoader(),我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了* 第三个参数handler,我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上*/Subjectsubject= (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject                .getClass().getInterfaces(), handler);
System.out.println(subject.getClass().getName());
subject.rent();
subject.hello("world");
    }
}

我们先来看看控制台的输出:

$Proxy0beforerenthouseMethod:publicabstractvoidcom.xiaoluo.dynamicproxy.Subject.rent()
IwanttorentmyhouseafterrenthousebeforerenthouseMethod:publicabstractvoidcom.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
hello: worldafterrenthouse

我们首先来看看 $Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?

Subjectsubject= (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);

可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。


接着我们来看看这两句  

subject.rent();
subject.hello("world");

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的 invoke 方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject 类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的 invoke 方法去执行:

publicObjectinvoke(Objectobject, Methodmethod, Object[] args) throwsThrowable{
//  在代理真实对象前我们可以添加一些自己的操作System.out.println("before rent house");
System.out.println("Method:"+method);
//    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用method.invoke(subject, args);
//  在代理真实对象后我们也可以添加一些自己的操作System.out.println("after rent house");
returnnull;
}

我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:

publicabstractvoidcom.xiaoluo.dynamicproxy.Subject.rent()
publicabstractvoidcom.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)

正好就是我们的Subject接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

这就是我们的java动态代理机制。


本篇随笔详细的讲解了java中的动态代理机制,这个知识点非常非常的重要,包括我们Spring的AOP其就是通过动态代理的机制实现的,所以我们必须要好好的理解动态代理的机制。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
18天前
|
监控 算法 Java
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,特别是垃圾回收(Garbage Collection, GC)机制。我们将从基础概念开始,逐步解析垃圾回收的工作原理、不同类型的垃圾回收器以及它们在实际项目中的应用。通过实际案例,读者将能更好地理解Java应用的性能调优技巧及最佳实践。
59 0
|
2天前
|
Java
深入理解Java中的异常处理机制
【9月更文挑战第34天】在Java的世界里,异常处理是代码健壮性的守护神。本文将带你探索Java异常处理的奥秘,从基础语法到高级技巧,我们一步步揭开异常处理的面纱。你将学会如何捕获、声明和处理异常,以及如何自定义异常类型。让我们开始这段旅程,让代码更加稳健和可靠吧!
|
16天前
|
Java 程序员
深入理解Java异常处理机制
【9月更文挑战第20天】在Java编程世界中,异常处理是一项基础而重要的技能。本文将通过通俗易懂的语言和生动的比喻,带你走进Java异常的世界,了解它们的本质、分类以及如何优雅地处理这些不请自来的特殊“客人”。从简单的try-catch语句到复杂的异常链追踪,我们将一步步揭开异常处理的面纱,让你在遇到问题时不再手足无措。
43 21
|
6天前
|
Java 程序员 开发者
深入理解Java中的异常处理机制
【9月更文挑战第31天】在Java编程中,异常处理是维护程序健壮性的关键。本文将通过浅显易懂的语言和生动的例子,带你了解Java异常处理的基本概念、分类以及如何优雅地处理它们。从初学者到资深开发者,每个人都能从中获得新的洞见和技巧,让你的代码更加健壮和易于维护。
11 4
|
5天前
|
Java 编译器 开发者
Java中的异常处理机制:从基础到进阶
本文深入探讨Java编程语言中的异常处理机制,从基础知识出发,逐步解析异常的分类、捕获和处理方法。通过实际案例分析,展示如何在开发过程中有效利用异常处理提高代码的稳定性和可维护性。进一步探讨了自定义异常的创建和使用场景,以及在Java中进行异常处理的最佳实践。文章旨在为Java开发者提供一个全面而详细的异常处理指南,帮助开发者更好地理解和运用Java的异常处理机制。
|
9天前
|
Java 数据库连接
深入理解Java异常处理机制
【9月更文挑战第28天】在Java编程中,异常处理是确保程序健壮性的关键。本文通过浅显易懂的语言和生动的例子,带你一步步了解Java的异常处理机制。从try-catch-finally的基本用法,到自定义异常类,再到异常处理的最佳实践,我们将一起探索如何在代码中优雅地处理那些不期而遇的小插曲。
15 4
|
11天前
|
Java 程序员 数据库连接
Java中的异常处理机制:理解与实践
本文将深入探讨Java语言中异常处理的核心概念、重要性以及应用方法。通过详细解析Java异常体系结构,结合具体代码示例,本文旨在帮助读者更好地理解如何有效利用异常处理机制来提升程序的健壮性和可维护性。
|
11天前
|
Java 开发者 UED
深入理解Java中的异常处理机制
本文旨在通过通俗易懂的语言,详细解析Java异常处理的核心概念及应用。从异常的基本分类到具体处理方法,再到最佳实践和常见误区,一步步引领读者深入理解这一关键技术,提升编程质量和效率。
16 2
|
11天前
|
Java 程序员 数据库连接
深入理解Java中的异常处理机制
【9月更文挑战第25天】在Java的海洋中航行,不可避免地会遇到异常的风暴。本文将作为你的航海图,指引你穿越异常处理的迷雾,让你学会如何使用try-catch语句、finally块以及throw和throws关键字来驾驭这些风暴。我们将一起探索自定义异常的岛屿,并了解如何创建和使用它们。准备好了吗?让我们启航,确保你的代码不仅能够抵御异常的狂澜,还能优雅地处理它们。
|
11天前
|
Java 开发者
Java中的异常处理机制深度解析
在Java编程中,异常处理是保证程序稳定性和健壮性的重要手段。本文将深入探讨Java的异常处理机制,包括异常的分类、捕获与处理、自定义异常以及一些最佳实践。通过详细讲解和代码示例,帮助读者更好地理解和应用这一机制,提升代码质量。
12 1
下一篇
无影云桌面