Java动态代理

喜欢ヅ旅行 2022-01-29 17:25 432阅读 0赞

转载:https://juejin.im/post/5ad3e6b36fb9a028ba1fee6a

转载:https://www.cnblogs.com/xiaoluo501395377/p/3383130.html

一、概述

1. 什么是代理

我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:

优点一:可以隐藏委托类的实现;

优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

2. 静态代理

若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。 下面我们用Vendor类代表生产厂家,BusinessAgent类代表微商代理,来介绍下静态代理的简单实现,委托类和代理类都实现了Sell接口,Sell接口的定义如下:

  1. /**
  2. * 委托类和代理类都实现了Sell接口
  3. */
  4. public interface Sell {
  5. void sell();
  6. void ad();
  7. }
  8. 复制代码

Vendor类的定义如下:

  1. /**
  2. * 生产厂家
  3. */
  4. public class Vendor implements Sell {
  5. public void sell() {
  6. System.out.println("In sell method");
  7. }
  8. public void ad() {
  9. System,out.println("ad method");
  10. }
  11. }
  12. 复制代码

代理类BusinessAgent的定义如下:

  1. /**
  2. * 代理类
  3. */
  4. public class BusinessAgent implements Sell {
  5. private Sell vendor;
  6. public BusinessAgent(Sell vendor){
  7. this.vendor = vendor;
  8. }
  9. public void sell() {
  10. vendor.sell();
  11. }
  12. public void ad() {
  13. vendor.ad();
  14. }
  15. }
  16. 复制代码

从BusinessAgent类的定义我们可以了解到,静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。

下面我们考虑一下这个需求:给Vendor类增加一个过滤功能,只卖货给大学生。通过静态代理,我们无需修改Vendor类的代码就可以实现,只需在BusinessAgent类中的sell方法中添加一个判断即可如下所示:

  1. /**
  2. * 代理类
  3. */
  4. public class BusinessAgent(){ implements Sell {
  5. private Sell vendor;
  6. public BusinessAgent(Sell vendor){
  7. this.vendor = vendor;
  8. }
  9. public void sell() {
  10. if (isCollegeStudent()) {
  11. vendor.sell();
  12. }
  13. }
  14. public void ad() {
  15. vendor.ad();
  16. }
  17. }
  18. 复制代码

这对应着我们上面提到的使用代理的第二个优点:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。静态代理的局限在于运行前必须编写好代理类,下面我们重点来介绍下运行时生成代理类的动态代理方式。

二、动态代理

1. 什么是动态代理

代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。 这么说比较抽象,下面我们结合一个实例来介绍一下动态代理的这个优势是怎么体现的。

现在,假设我们要实现这样一个需求:在执行委托类中的方法之前输出“before”,在执行完毕后输出“after”。我们还是以上面例子中的Vendor类作为委托类,BusinessAgent类作为代理类来进行介绍。首先我们来使用静态代理来实现这一需求,相关代码如下:

  1. public class BusinessAgent implements Sell {
  2. private Vendor mVendor;
  3. public BusinessAgent(Vendor vendor) {
  4. this.mVendor = vendor;
  5. }
  6. public void sell() {
  7. System.out.println("before");
  8. mVendor.sell();
  9. System.out.println("after");
  10. }
  11. public void ad() {
  12. System.out.println("before");
  13. mVendor.ad();
  14. System.out.println("after");
  15. }
  16. }
  17. 复制代码

从以上代码中我们可以了解到,通过静态代理实现我们的需求需要我们在每个方法中都添加相应的逻辑,这里只存在两个方法所以工作量还不算大,假如Sell接口中包含上百个方法呢?这时候使用静态代理就会编写许多冗余代码。通过使用动态代理,我们可以做一个“统一指示”,从而对所有代理类的方法进行统一处理,而不用逐一修改每个方法。下面我们来具体介绍下如何使用动态代理方式实现我们的需求。

2. 使用动态代理

2.1 InvocationHandler接口

在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口,这个接口的定义如下:

  1. /**
  2. * 调用处理程序
  3. */
  4. public interface InvocationHandler {
  5. Object invoke(Object proxy, Method method, Object[] args);
  6. }
  7. 复制代码

从InvocationHandler这个名称我们就可以知道,实现了这个接口的中介类用做“调用处理器”。当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。因此我们只需在中介类的invoke方法实现中输出“before”,然后调用委托类的invoke方法,再输出“after”。下面我们来一步一步具体实现它。

2.2 委托类的定义

动态代理方式下,要求委托类必须实现某个接口,这里我们实现的是Sell接口。委托类Vendor类的定义如下:

  1. public class Vendor implements Sell {
  2. public void sell() {
  3. System.out.println("In sell method");
  4. }
  5. public void ad() {
  6. System,out.println("ad method");
  7. }
  8. }
  9. 复制代码

2.3中介类

上面我们提到过,中介类必须实现InvocationHandler接口,作为调用处理器”拦截“对代理类方法的调用。中介类的定义如下:

  1. public class DynamicProxy implements InvocationHandler {
  2. //obj为委托类对象;
  3. private Object obj;
  4. public DynamicProxy(Object obj) {
  5. this.obj = obj;
  6. }
  7. @Override
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. System.out.println("before");
  10. Object result = method.invoke(obj, args);
  11. System.out.println("after");
  12. return result;
  13. }
  14. }
  15. 复制代码

从以上代码中我们可以看到,中介类持有一个委托类对象引用,在invoke方法中调用了委托类对象的相应方法,看到这里是不是觉得似曾相识?

通过聚合方式持有委托类对象引用,把外部对invoke的调用最终都转为对委托类对象的调用。这不就是我们上面介绍的静态代理的一种实现方式吗?

实际上,中介类与委托类构成了静态代理关系,在这个关系中,中介类是代理类,委托类就是委托类;

代理类与中介类也构成一个静态代理关系,在这个关系中,中介类是委托类,代理类是代理类。

也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。下面我们来介绍一下如何”指示“以动态生成代理类。

2.4动态生成代理类

动态生成代理类的相关代码如下:

  1. public class Main {
  2. public static void main(String[] args) {
  3. //创建中介类实例
  4. DynamicProxy inter = new DynamicProxy(new Vendor());
  5. //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
  6. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
  7. //获取代理类实例sell
  8. Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter));
  9. //通过代理类对象调用代理类方法,实际上会转到invoke方法调用
  10. sell.sell();
  11. sell.ad();
  12. }
  13. }
  14. 复制代码

在以上代码中,我们调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。这个方法的声明如下:

  1. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
  2. 复制代码

方法的三个参数含义分别如下:

loader:定义了代理类的ClassLoder; interfaces:代理类实现的接口列表 h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例

我们运行一下,看看我们的动态代理是否能正常工作。我这里运行后的输出为

image

说明我们的动态代理确实奏效了。

上面我们已经简单提到过动态代理的原理,这里再简单的总结下:首先通过newProxyInstance方法获取代理类实例,而后我们便可以通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。

三、代理模式

这个应该是设计模式中最简单的一个了,类图

image

代理模式最大的特点就是代理类和实际业务类实现同一个接口(或继承同一父类),代理对象持有一个实际对象的引用,外部调用时操作的是代理对象,而在代理对象的内部实现中又会去调用实际对象的操作

Java动态代理其实内部也是通过Java反射机制来实现的,即已知的一个对象,然后在运行时动态调用其方法,这样在调用前后作一些相应的处理,这样说的比较笼统,举个简单的例子

比如我们在应用中有这样一个需求,在对某个类的一个方法的调用前和调用后都要做一下日志操作,

一个普通的接口

  1. public interface AppService {
  2. public boolean createApp(String name);
  3. }
  4. 复制代码

该接口的默认实现类

  1. public class AppServiceImpl implements AppService {
  2. public boolean createApp(String name) {
  3. System.out.println("App["+name+"] has been created.");
  4. return true;
  5. }
  6. }
  7. 复制代码

日志处理器(实质充当了中介类)

  1. /**
  2. * 注意需实现Handler接口
  3. */
  4. public class LoggerInterceptor implements InvocationHandler {
  5. private Object target;//目标对象的引用,这里设计成Object类型,更具通用性
  6. public LoggerInterceptor(Object target){
  7. this.target = target;
  8. }
  9. public Object invoke(Object proxy, Method method, Object[] arg) throws Throwable {
  10. System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()+",with arguments{"+arg[0]+"}");
  11. Object result = method.invoke(target, arg);//调用目标对象的方法
  12. System.out.println("Before return:"+result);
  13. return result;
  14. }
  15. }
  16. 复制代码

外部调用

  1. public class Main {
  2. public static void main(String[] args) {
  3. AppService target = new AppServiceImpl();//生成目标对象
  4. //接下来创建代理对象
  5. AppService proxy = (AppService) Proxy.newProxyInstance(
  6. target.getClass().getClassLoader(),
  7. target.getClass().getInterfaces(), new LoggerInterceptor(target));
  8. proxy.createApp("Kevin Test");
  9. }
  10. }
  11. 复制代码

关注下面的标签,发现更多相似文章
链接:https://juejin.im/post/5ad3e6b36fb9a028ba1fee6a

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

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

InvocationHandler:

  1. InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
  2. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

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

  1. Object invoke(Object proxy, Method method, Object[] args) throws Throwable

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

  1. Object invoke(Object proxy, Method method, Object[] args) throws Throwable
  2. proxy:  指代我们所代理的那个真实对象
  3. method:  指代的是我们所要调用真实对象的某个方法的Method对象
  4. args:  指代的是调用真实对象某个方法时接受的参数

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

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

  1. Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

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

  1. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
  2. Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

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

复制代码

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

复制代码

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

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

  1. public interface Subject
  2. {
  3. public void rent();
  4. public void hello(String str);
  5. }

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

复制代码

  1. public class RealSubject implements Subject
  2. {
  3. @Override
  4. public void rent()
  5. {
  6. System.out.println("I want to rent my house");
  7. }
  8. @Override
  9. public void hello(String str)
  10. {
  11. System.out.println("hello: " + str);
  12. }
  13. }

复制代码

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

复制代码

  1. public class DynamicProxy implements InvocationHandler
  2. {
  3. // 这个就是我们要代理的真实对象
  4. private Object subject;
  5. // 构造方法,给我们要代理的真实对象赋初值
  6. public DynamicProxy(Object subject)
  7. {
  8. this.subject = subject;
  9. }
  10. @Override
  11. public Object invoke(Object object, Method method, Object[] args)
  12. throws Throwable
  13. {
  14. //  在代理真实对象前我们可以添加一些自己的操作
  15. System.out.println("before rent house");
  16. System.out.println("Method:" + method);
  17. // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
  18. method.invoke(subject, args);
  19. //  在代理真实对象后我们也可以添加一些自己的操作
  20. System.out.println("after rent house");
  21. return null;
  22. }
  23. }

复制代码

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

复制代码

  1. public class Client
  2. {
  3. public static void main(String[] args)
  4. {
  5. // 我们要代理的真实对象
  6. Subject realSubject = new RealSubject();
  7. // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
  8. InvocationHandler handler = new DynamicProxy(realSubject);
  9. /*
  10. * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
  11. * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
  12. * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
  13. * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
  14. */
  15. Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
  16. .getClass().getInterfaces(), handler);
  17. System.out.println(subject.getClass().getName());
  18. subject.rent();
  19. subject.hello("world");
  20. }
  21. }

复制代码

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

复制代码

  1. $Proxy0
  2. before rent house
  3. Method:public abstract void com.xiaoluo.dynamicproxy.Subject.rent()
  4. I want to rent my house
  5. after rent house
  6. before rent house
  7. Method:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
  8. hello: world
  9. after rent house

复制代码

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

  1. Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
  2. .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方法去执行:

复制代码

  1. public Object invoke(Object object, Method method, Object[] args)
  2. throws Throwable
  3. {
  4. //  在代理真实对象前我们可以添加一些自己的操作
  5. System.out.println("before rent house");
  6. System.out.println("Method:" + method);
  7. // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
  8. method.invoke(subject, args);
  9. //  在代理真实对象后我们也可以添加一些自己的操作
  10. System.out.println("after rent house");
  11. return null;
  12. }

复制代码

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

  1. public abstract void com.xiaoluo.dynamicproxy.Subject.rent()
  2. public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)

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

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

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

复制代码

  1. 您可以通过点击 右下角 的按钮 来对文章内容作出评价, 也可以通过左下方的 关注按钮 来关注我的博客的最新动态。
  2. 如果文章内容对您有帮助, 不要忘记点击右下角的 推荐按钮 来支持一下哦
  3. 如果您对文章内容有任何疑问, 可以通过评论或发邮件的方式联系我: 501395377@qq.com / lzp501395377@gmail.com
  4. 如果需要转载,请注明出处,谢谢!!

复制代码

分类: Java

发表评论

表情:
评论列表 (有 0 条评论,432人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java--代理模式(动态代理

    Java的三种代理模式 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实

    相关 java动态代理

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知

    相关 java 动态代理

    我觉得首先要知道代理的意思。一个简单的例子,上学的时候老师收作业,这些作业不能是学生一个个的拿给老师,老师在一个个面授吧。一般情况下都是由班干部统一收上去,然后抱到老师办公室的

    相关 java动态代理

    class文件简介及加载   Java编译器编译好Java文件之后,产生.class 文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。