谈谈Java的代理模式及动态代理

Java的动态代理在实践中有着广泛的使用场景,比如最场景的Spring AOP、Java注解的获取、日志、用户鉴权等。本篇文章带大家了解一下代理模式、静态代理以及基于JDK原生动态代理。

代理模式
无论学习静态代理或动态代理,我们都要先了解一下代理模式。

先看百度百科的定义:

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

直接看定义可能有些难以理解,我们就以生活中具体的实例来说明一下。

我们都去过超市购买过物品,超市从厂商那里购买货物之后出售给我们,我们通常并不知道货物从哪里经过多少流程才到超市。

在这个过程中,等于是厂商“委托”超市出售货物,对我们来说是厂商(真实对象)是不可见的。而超市(代理对象)呢,作为厂商的“代理者”来与我们进行交互。

同时,超市还可以根据具体的销售情况来进行折扣等处理,来丰富被代理对象的功能。

通过代理模式,我们可以做到两点:

1、隐藏委托类的具体实现。

2、实现客户与委托类的解耦,在不改变委托类代码的情况下添加一些额外的功能(日志、权限)等。

代理模式角色定义
在上述的过程中在编程的过程中我们可以定义为三类对象:

Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法。比如:广告、出售等。
RealSubject(真实主题角色):真正实现业务逻辑的类。比如实现了广告、出售等方法的厂家(Vendor)。
Proxy(代理主题角色):用来代理和封装真实主题。比如,同样实现了广告、出售等方法的超时(Shop)。
以上三个角色对应的类图如下:

Java代理及动态代理详解

静态代理实例
静态代理是指代理类在程序运行前就已经存在,这种情况下的代理类通常都是我们在Java代码中定义的。

下面我们就以具体的实例来演示一下静态代理。

首先定义一组接口Sell,用来提供广告和销售等功能。然后提供Vendor类(厂商,被代理对象)和Shop(超市,代理类),它们分别实现了Sell接口。

Sell接口定义如下:

/**

  • 委托类和代理类都实现了Sell接口
  • @author sec
  • @version 1.0
  • @date 2020/3/21 9:30 AM
    **/
    public interface Sell {

    /**

    • 出售
      */
      void sell();

    /**

    • 广告
      */
      void ad();
      }
      Vendor类定义如下:

/**

  • 供应商
  • @author sec
  • @version 1.0
  • @date 2020/3/21 9:30 AM
    **/
    public class Vendor implements Sell{

    @Override
    public void sell() {
    System.out.println("Shop sell goods");
    }

    @Override
    public void ad() {
    System.out.println("Shop advert goods");
    }
    }
    Shop类定义如下:

/**

  • 超市,代理类
  • @author sec
  • @version 1.0
  • @date 2020/3/21 9:30 AM
    **/
    public class Shop implements Sell{

    private Sell sell;

    public Shop(Sell sell){
    this.sell = sell;
    }

    @Override
    public void sell() {
    System.out.println("代理类Shop,处理sell");
    sell.sell();
    }

    @Override
    public void ad() {
    System.out.println("代理类Shop,处理ad");
    sell.ad();
    }
    }
    其中代理类Shop通过聚合的方式持有了被代理类Vendor的引用,并在对应的方法中调用Vendor对应的方法。在Shop类中我们可以新增一些额外的处理,比如筛选购买用户、记录日志等操作。

下面看看在客户端中如何使用代理类。

/**

  • 静态代理类测试方法
  • @author sec
  • @version 1.0
  • @date 2020/3/21 9:33 AM
    **/
    public class StaticProxy {

    public static void main(String[] args) {

    // 供应商---被代理类
    Vendor vendor = new Vendor();
    
    // 创建供应商的代理类Shop
    Sell sell = new Shop(vendor);
    
    // 客户端使用时面向的是代理类Shop。
    sell.ad();
    sell.sell();

    }
    }
    在上述代码中,针对客户看到的是Sell接口提供了功能,而功能又是由Shop提供的。我们可以在Shop中修改或新增一些内容,而不影响被代理类Vendor。

静态代理的缺点
静态代理实现简单且不侵入原代码,但当场景复杂时,静态代理会有以下缺点:

1、当需要代理多个类时,代理对象要实现与目标对象一致的接口。要么,只维护一个代理类来实现多个接口,但这样会导致代理类过于庞大。要么,新建多个代理类,但这样会产生过多的代理类。

2、当接口需要增加、删除、修改方法时,目标对象与代理类都要同时修改,不易维护。

于是,动态代理便派上用场了。

动态代理
动态代理是指代理类在程序运行时进行创建的代理方式。这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据Java代码中的“指示”动态生成的。

相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

基于JDK原生动态代理实现
实现动态代理通常有两种方式:JDK原生动态代理和CGLIB动态代理。这里,我们以JDK原生动态代理为例来进行讲解。

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。

InvocationHandler接口定义了如下方法:

/**

  • 调用处理程序
    */
    public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args);
    }
    顾名思义,实现了该接口的中介类用做“调用处理器”。当调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了具体调用的是代理类的哪个方法,args为该方法的参数。这样对代理类中的所有方法的调用都会变为对invoke的调用,可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。

Proxy类用于获取指定代理对象所关联的调用处理器。

下面以添加日志为例来演示一下动态代理。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class LogHandler implements InvocationHandler {
Object target; // 被代理的对象,实际的方法执行者

public LogHandler(Object target) {
    this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    before();
    Object result = method.invoke(target, args);  // 调用 target 的 method 方法
    after();
    return result;  // 返回方法的执行结果
}
// 调用invoke方法之前执行
private void before() {
    System.out.println(String.format("log start time [%s] ", new Date()));
}
// 调用invoke方法之后执行
private void after() {
    System.out.println(String.format("log end time [%s] ", new Date()));
}

}
客户端编写程序使用动态代理代码如下:

import java.lang.reflect.Proxy;

/**

  • 动态代理测试
  • @author sec
  • @version 1.0
  • @date 2020/3/21 10:40 AM
    **/
    public class DynamicProxyMain {

    public static void main(String[] args) {
    // 创建中介类实例
    LogHandler logHandler = new LogHandler(new Vendor());
    // 设置该变量可以保存动态代理类,默认名称$Proxy0.class
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

    // 获取代理类实例Sell
    Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler));
    
    // 通过代理类对象调用代理类方法,实际上会转到invoke方法调用
    sell.sell();
    sell.ad();

    }
    }
    执行之后,打印日志如下:

调用方法sell之【前】的日志处理
Shop sell goods
调用方法sell之【后】的日志处理
调用方法ad之【前】的日志处理
Shop advert goods
调用方法ad之【后】的日志处理
经过上述验证,我们发现已经成功为我们的被代理类统一添加了执行方法之前和执行方法之后的日志。

在上述实例中为了看一下生成的动态代理类的代码,我们添加了下面的属性设置(在生产环境中需要去掉该属性)。

// 设置该变量可以保存动态代理类,默认名称$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
那么,我们可以执行main方法之后,还生成了一个名字为$Proxy0.class类文件。通过反编译可看到如下的代码:

package com.sun.proxy;

import com.choupangxia.proxy.Sell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Sell {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws  {
    super(var1);
}

public final boolean equals(Object var1) throws  {
    try {
        return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
        throw var3;
    } catch (Throwable var4) {
        throw new UndeclaredThrowableException(var4);
    }
}

public final String toString() throws  {
    try {
        return (String)super.h.invoke(this, m2, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

public final void ad() throws  {
    try {
        super.h.invoke(this, m4, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

public final void sell() throws  {
    try {
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

public final int hashCode() throws  {
    try {
        return (Integer)super.h.invoke(this, m0, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

static {
    try {
        m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
        m2 = Class.forName("java.lang.Object").getMethod("toString");
        m4 = Class.forName("com.choupangxia.proxy.Sell").getMethod("ad");
        m3 = Class.forName("com.choupangxia.proxy.Sell").getMethod("sell");
        m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    } catch (NoSuchMethodException var2) {
        throw new NoSuchMethodError(var2.getMessage());
    } catch (ClassNotFoundException var3) {
        throw new NoClassDefFoundError(var3.getMessage());
    }
}

}
可以看到$Proxy0(代理类)继承了Proxy类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法。

由于动态代理类继承了Proxy类,所以每个代理类都会关联一个InvocationHandler方法调用处理器。

类和所有方法都被public final修饰,所以代理类只可被使用,不可以再被继承。

每个方法都有一个Method对象来描述,Method对象在static静态代码块中创建,以“m+数字”的格式命名。

调用方法的时候通过super.h.invoke(this,m1,(Object[])null);调用。其中的super.h.invoke实际上是在创建代理的时候传递给Proxy.newProxyInstance的LogHandler对象,它继承InvocationHandler类,负责实际的调用处理逻辑。

小结
关于代理和动态代理相关的内容,我们就讲这么多。了解了代理模式可以让我们的系统设计的更加具有可扩展性。而动态代理的应用就更广了,各类框架及业务场景都在使用。有了两个基础,就能够更好的学习其他框架。

关于CGLIB动态代理的内容,我们下篇文章再来聊一聊。

参考资料:
1、https://www.cnblogs.com/liudianjia/p/12535157.html
2、https://www.cnblogs.com/liudianjia/p/12513320.html
3、https://www.cnblogs.com/liudianjia/p/12513272.html
4、https://www.cnblogs.com/liudianjia/p/12495990.html
5、https://www.cnblogs.com/liudianjia/p/12488902.html
6、https://www.cnblogs.com/liudianjia/p/12484952.html
7、https://www.cnblogs.com/liudianjia/p/12479110.html
8、https://www.cnblogs.com/liu2020/p/12515245.html
9、https://www.cnblogs.com/liu2020/p/12515240.html
10、https://www.cnblogs.com/liu2020/p/12496066.html
11、https://www.cnblogs.com/liu2020/p/12489032.html
12、https://www.cnblogs.com/liu2020/p/12485055.html
13、https://www.cnblogs.com/liu2020/p/12481993.html
14、https://www.cnblogs.com/awzh2020/p/12541824.html
15、https://www.cnblogs.com/awzh2020/p/12513557.html
16、https://www.cnblogs.com/awzh2020/p/12513483.html
17、https://www.cnblogs.com/awzh2020/p/12496324.html
18、https://www.cnblogs.com/awzh2020/p/12496174.html
19、https://www.cnblogs.com/awzh2020/p/12496159.html
20、https://www.cnblogs.com/awzh2020/p/12489362.html
21、https://www.cnblogs.com/awzh2020/p/12485546.html
22、https://www.cnblogs.com/awzh2020/p/12482641.html
23、https://www.cnblogs.com/xiangcunjiaoshi/p/12541689.html
24、https://www.cnblogs.com/xiangcunjiaoshi/p/12535313.html
25、https://www.cnblogs.com/xiangcunjiaoshi/p/12513716.html
26、https://www.cnblogs.com/xiangcunjiaoshi/p/12513642.html
27、https://www.cnblogs.com/xiangcunjiaoshi/p/12507406.html
28、https://www.cnblogs.com/xiangcunjiaoshi/p/12492412.html
29、https://www.cnblogs.com/xiangcunjiaoshi/p/12492287.html
30、https://www.cnblogs.com/xiangcunjiaoshi/p/12485190.html
31、https://www.cnblogs.com/xiangcunjiaoshi/p/12482707.html

原文地址:https://blog.51cto.com/14760794/2480744

时间: 2024-10-13 04:23:10

谈谈Java的代理模式及动态代理的相关文章

Java设计模式-代理模式之动态代理(附源码分析)

Java设计模式-代理模式之动态代理(附源码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的区别就是:动态代理是在运行时刻动态的创建出代理类及其对象.上篇中的静态代理是在编译的时候就确定了代理类具体类型,如果有多个类需要代理,那么就得创建多个.还有一点,如果Subject中新增了一个方法,那么对应的实现接口的类中也要相应的实习该方法,不符合设计模式原则. 动态代理的做法:在运行时刻,可以动态创建出一个实现了多个接口的代理类.每个代理类的对象都会关联一个表示内部处理

Java设计模式-代理模式之动态代理(附源代码分析)

Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代理是在编译的时候就确定了代理类详细类型.假设有多个类须要代理.那么就得创建多个. 另一点,假设Subject中新增了一个方法,那么相应的实现接口的类中也要相应的实现这些方法. 动态代理的做法:在执行时刻.能够动态创建出一个实现了多个接口的代理类.每一个代理类的对象都会关联一个表示内部处理逻辑的Inv

设计模式(11)--代理模式之动态代理

保护代理 在调用方法前后,进行一些动作. 动态代理之所以叫动态代理是因为,是运行时才将它的类创建出来. 日志 . 连接. 事务. Java在java.lang.reflect包中有自己的代理支持,利用这个包你可以要运行时动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类.因为实际的代理类是在运行时创建类,我们称这个Java技术为:动态代理. Java为你创建Proxy类,因为Proxy不是你直接实现的,你让代理执行的代理应该放在InvocationHandler中. In

程序设计优化——(2)代理模式之动态代理

简介:动态代理是指在运行时,动态生成代理类.即代理类的字节码将在运行时生成并载入当前的ClassLoader.与静态代理类相比,有诸多好处.不需要为真实主题写一个形式上完全一样的封装类,若接口过多,则会代理接口也很多,接口稍微改动,代理接口也要进行变更:其次,使用动态代理可以在运行时指定代理类的执行逻辑,从而提升系统的灵活性. 动态代理有JDK自带的动态代理,CGLIB.Javassist或者ASM库.JDK的动态代理简单,内置在JDK中,因此不需要引入第三方jar包,但功能较弱.CGLIB和J

代理模式之动态代理

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //接口 interface MyInterface { public void action(); } // 被代理类 class MyObject implements MyInterface { @Override public void action() { System.

设计模式_代理模式_动态代理

转自:https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html 动态代理是指在运行时动态生成代理类.即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader.与静态处理类相比,动态类有诸多好处.首先,不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也很麻烦.如果接口有变动,则真实主题和代理类都要修改,不利于系统维护:其次,使用一些动态代理的生成

动态代理模式——JDK动态代理

今天,我就来讲一下动态代理的设计模式. 动态代理的意义在于生成一个代理对象,来代理真实对象,从而控制真实对象的访问.操作动态代理需要两个步骤:一.代理对象和真实对象建立代理关系.二.实现代理对象的代理逻辑方法. 在Java中,有很多的动态代理技术.如:JDK.CGLIB.Javassist.ASM,其中最常用的动态代理技术有两种:一种是JDK动态代理,这是JDK自带的功能:另一种就是CGLIB,这是第三方提供的一种技术. 这次主要讲的是JDK动态代理和CGLIB动态代理.在JDK动态代理中,我们

代理模式 & Java原生动态代理技术 & CGLib动态代理技术

第一部分.代理模式  代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务.(其实就是在代理类中关联一个委托类的实例,然后在代理类中进行包装). UML图如下: 第二部分.在Java中实现代理模式  按照代理的创建时期,代理类可以分

代理模式(静态代理+动态代理)——JAVA

代理模式是常用的java设计模式,他的特征是代理类与目标类有同样的接口,代理类主要负责为目标类预处理消息.过滤消息.把消息转发给目标类,以及事后处理消息等.代理类与目标类之间通常会存在关联关系,一个代理类的对象与一个目标类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用目标类的对象的相关方法,来提供特定的服务. 结构图如下: 按照代理的创建时期,代理类可以分为静态代理和动态代理. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类(Proxy)的.clas