プロキシ モード (CGLIB および JDK)

1. はじめに

1. 人生における代理人の事例

  • 住宅仲介業者:クライアントは住宅情報を持っていないため、住宅を取得するために仲介業者を探す必要があります。
  • 製品購入代理店: 当社には低価格の製品がないため、低価格の製品を入手する代理店を見つける必要があります。

消費者にとっては、購入代理店を通して自分の商品だけを気にすればよく、商品の入手方法を意識する必要がなくなり、利便性が向上します。

2. Javaでのプロキシモードの適用

  • 統合された例外処理
  • 私の靴
  • Spring の AOP 使用原則
  • ロギングフレームワークなど

3. プロキシモードとは何ですか?

エージェントパターンは、23 の設計パターンの 1 つであり、構造パターンです。これは、オブジェクト自身が実際の動作を行うのではなく、他のオブジェクトを通じて必要な結果を得るという意味であるため、対象オブジェクトは自身の実装の詳細のみを気にするだけでよく、プロキシオブジェクトによる機能拡張によって機能を拡張することができます。対象オブジェクトの。これは非常に重要なプログラミングの考え方を反映しており、ソース コードを気軽に変更することはできません。ソース コードを変更する必要がある場合は、プロキシを使用して機能を拡張するだけで済みます。
ここに画像の説明を挿入します

2. プロキシの実装方法

1 はじめに

通常はインターフェース、つまり仕様を定義し、そのインターフェースをクラスで実装し、クライアントはその実装クラスを呼び出すことで基本的な機能を利用することができます。現状では、実装クラスを直接呼び出すのではなく、プロキシクラスを介してインターフェースを実装し、事前通知と事後通知を追加してインターフェースのメソッドを強化し、クライアントはインターフェースで定義された関数を使用します。プロキシクラス
ここに画像の説明を挿入します

元素組成:

  • インターフェース: 動作と仕様を定義する
  • プロキシ クラス: ターゲット オブジェクトです
  • エージェントクラス:機能強化

2. 静的プロキシ

静的プロキシは、Java では非常に一般的な設計パターンです。このパターンでは、あるクラス (プロキシ クラス) が別のクラス (ターゲット クラス) と同じインターフェイスを提供し、呼び出しをターゲット クラスに転送します。プロキシ クラスとターゲット クラスは両方とも同じインターフェイスを実装します。このパターンの一般的な使用法は、ターゲット クラスのコードを変更せずに、ロギング、パフォーマンス メトリック、セキュリティ チェックなどのクロスアスペクト動作を追加することです。

public interface IService {
    
    
    void doSomething();
}

public class RealService implements IService {
    
    
    public void doSomething() {
    
    
        // 实际业务逻辑
    }
}

public class ProxyService implements IService {
    
    
    private IService realService;

    public ProxyService(IService realService) {
    
    
        this.realService = realService;
    }

    public void doSomething() {
    
    
        // 在调用实际方法前可以执行一些操作
        System.out.println("Before doSomething...");
        realService.doSomething();
        // 在调用实际方法后也可以执行一些操作
        System.out.println("After doSomething...");
    }
}

この例では、RealService がターゲット クラスで、ProxyService がプロキシ クラスです。ProxyService は、RealService の doSomething メソッドを呼び出す前後に、いくつかの追加の操作を実行します。静的プロキシには次の特徴があります。

  • プロキシ クラスとターゲット クラスはコンパイル時に決定され、静的です。
  • プロキシ クラスとターゲット クラスは両方とも同じインターフェイスを実装しており、プロキシ クラスはインターフェイスで定義されたメソッドを通じてターゲット クラスの実装を呼び出します。
  • ユーザーはプロキシ クラスを介してターゲット クラスのメソッドを呼び出しますが、プロキシ クラスはターゲット クラスのメソッドの呼び出しの前後に追加の処理を追加できます。

静的プロキシの主な問題は、インターフェイスで多くのメソッドが定義されている場合、プロキシ クラスがメソッドごとに転送コードを記述する必要があり、これが明らかに非常に面倒なことです。さらに、インターフェイスが変更されると、プロキシ クラスも変更する必要があり、メンテナンスが複雑になります。Java では、静的プロキシの代わりに動的プロキシ (Java の java.lang.reflect.Proxy や CGLIB などのサードパーティ ライブラリの使用など) がよく使用されるのはこのためです。

3. 動的プロキシ

Java における動的プロキシとは、実行時にプロキシ クラスを動的に生成し、プロキシ オブジェクトを作成するテクノロジを指します。Java のコア ライブラリは、主に java.lang.reflect パッケージで動的プロキシのサポートを提供します。動的プロキシは便利で強力なツールであり、主な用途にはインターセプタ、モック オブジェクト、データベース接続、トランザクション管理などがあります。Java の動的プロキシを使用するには、呼び出しハンドラーを定義する必要があります。呼び出しハンドラーは、java.lang.reflect.InvocationHandler インターフェースを実装するクラスであり、メソッドを 1 つだけ定義します。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

この呼び出しメソッドは、プロキシ インスタンスでメソッドが呼び出されるときに呼び出されます。プロキシ オブジェクト自体、プロキシ オブジェクト上で呼び出されるメソッド オブジェクト、メソッドの呼び出し時に渡されるパラメータの 3 つのパラメータを受け取ります。このメソッドは、ターゲット メソッドの呼び出しの前後に追加の処理ロジックを追加できます。単純な動的プロキシの例を次に示します。

public interface IService {
    
    
    void doSomething();
}

public class RealService implements IService {
    
    
    public void doSomething() {
    
    
        System.out.println("Do something in RealService");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    
    
    private Object target;

    public MyInvocationHandler(Object target) {
    
    
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println("Before invoke method...");
        Object result = method.invoke(target, args);
        System.out.println("After invoke method...");
        return result;
    }
}

次に、Proxy クラスの newProxyInstance メソッドを使用してプロキシ オブジェクトを作成できます。

//一个是在代理对象上调用的方法对象
IService realService = new RealService();
//调用处理器
InvocationHandler handler = new MyInvocationHandler(realService);
//创建代理对象
IService proxyService = (IService) Proxy.newProxyInstance(
    IService.class.getClassLoader(),
    new Class<?>[]{
    
    IService.class},
    handler
);
proxyService.doSomething();  // 调用这个方法时,会触发 handler 的 invoke 方法

この例では、RealService のプロキシを作成しました。プロキシ経由で doSomething メソッドを呼び出すと、MyInvocationHandler の invoke メソッドが最初に呼び出されます。Java の動的プロキシは、クラスではなくインターフェイスのプロキシのみを作成できることに注意してください。クラスのプロキシを作成する必要がある場合は、CGLIB などのサードパーティ ライブラリを使用できます。

静的プロキシ 動的プロキシ
コンパイル時間 プロキシ クラスはコンパイル時にすでに存在します。 プロキシ クラスは実行時に動的に生成されます
コードの複雑さ メソッドごとに転送コードを記述する必要があり、メソッドが多いインターフェースの場合、コード量が多くなります。 作成する必要がある呼び出しハンドラーは 1 つだけであり、コードの量は比較的少なくなります。
コードの柔軟性 インターフェースが変わるとプロキシクラスも変更する必要があり、メンテナンスが煩雑になります。 呼び出し側プロセッサは通常、特定の実装ではなくインターフェイスのみに依存するため、インターフェイスが変更された場合でも、通常、呼び出し側プロセッサのコードを変更する必要はありません。
実現方法 プロキシ クラスとターゲット クラスは両方とも同じインターフェイスを実装しており、プロキシ クラスはターゲット クラスのメソッドを直接呼び出します。 Java の Proxy クラスと InvocationHandler インターフェイスを使用すると、プロキシ オブジェクトは実行時に JVM によって動的に生成されます。
アプリケーションシナリオ 1 つまたは複数の特定のクラスまたはインターフェイスのプロキシを作成する必要がある場合、静的プロキシの方がシンプルで簡単です。 動的プロキシは、複数または未定義のクラスまたはインターフェイスのプロキシを作成する必要がある場合、またはより柔軟で強力なプロキシ機能 (インターセプタ、AOP など) が必要な場合に適しています。

3. JDK の動的プロキシ

1 はじめに

package java.lang.reflect;

public interface InvocationHandler {
    
    
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

InvocationHandler インターフェイス (メソッドのインターセプトに使用) はメソッド invoke を定義します。このメソッドは、プロキシ インスタンスでメソッドが呼び出されるときに呼び出されます。invoke メソッドは 3 つのパラメータを受け取ります。

  1. プロキシ: プロキシ インスタンス。これは、invoke メソッドを呼び出すプロキシ クラスのインスタンスです。(newProxyInstanceによるプロキシインスタンスの作成)
  2. Method: プロキシ インスタンスで呼び出されるインターフェイス メソッドを表すメソッド インスタンス。これを使用して、メソッド名、パラメータの型など、呼び出されたメソッドに関する情報を取得できます。(対象メソッドの実行)
  3. args: プロキシ インスタンスのメソッド呼び出しで渡される引数を含むオブジェクトの配列。インターフェイス メソッドがパラメータを取らない場合は、null になります。(対象メソッドのパラメータ)

invokeメソッドの戻り値は、プロキシインスタンスのメソッド呼び出しの戻り値となります。つまり、このメソッドは実際にプロキシ メソッドの動作を決定します。プロキシ インスタンスでのメソッド呼び出し中に例外がスローされた場合、その例外は invoke メソッドによってスローされます。実際の使用では、通常、InvocationHandler インターフェイスを実装したクラスを作成し、メソッド呼び出しの前後に追加の処理を追加するなど、呼び出しメソッド内にカスタム ロジックを記述します。
Java では、InvocationHandler はプロキシ インスタンスでのメソッド呼び出しを処理するために使用されるインターフェイスです。Java ダイナミック プロキシ メカニズムのコア インターフェイスです。

Java の java.lang.reflect.Proxy クラスは、動的プロキシを実装するためのコア クラスです。動的プロキシの作業プロセス中に、Proxy クラスは指定されたインターフェイスのプロキシ クラスとそのオブジェクトを生成し、InvocationHandler インターフェイスを呼び出すことによってメソッドの動的な呼び出しを実装します。

public class Proxy implements java.io.Serializable {
    
    
    private static final long serialVersionUID = -2222568056686623797L;
    
    private InvocationHandler h;
    
    protected Proxy(InvocationHandler h) {
    
    
        Objects.requireNonNull(h);
        this.h = h;
    }

    public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException
    {
    
    
        // ...
    }

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
    
    
        // ...
    }

    public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException
    {
    
    
        // ...
    }
}

Proxy クラスは、次の主なメソッドを提供します。

  1. getProxyClass(ClassLoaderloader, Class<?>…interfaces): このメソッドは、指定されたインターフェイスのプロキシ クラスの Class オブジェクトを生成するために使用されます。パラメーター ローダーは、プロキシ クラスを定義するために使用されるクラス ローダーであり、インターフェイスは一連のインターフェイスであり、プロキシ クラスはこれらのインターフェイスを実装します。
  2. newProxyInstance(ClassLoaderloader,Class<?>[]interfaces,InvocationHandlerh): このメソッドは、指定されたインターフェイスのプロキシ オブジェクトを生成するために使用されます。このメソッドは、プロキシ クラスの Class オブジェクトを生成するだけでなく、このクラスのインスタンスも作成します。パラメーター h は、プロキシ オブジェクトでのメソッド呼び出しの動作を定義する InvocationHandler インターフェイスの実装です。
  3. getInvocationHandler(Object proxy): このメソッドは、指定されたプロキシ オブジェクトに関連付けられた呼び出しハンドラーを返します。パラメータ proxy はプロキシ オブジェクトです。

Proxy クラスを使用してプロキシ オブジェクトを生成する場合、最初に InvocationHandler インターフェイスを実装するクラスを提供する必要があります。このクラスの invoke メソッドは、プロキシ オブジェクトでのメソッド呼び出しの動作を定義します。次に、Proxy.newProxyInstance メソッドを呼び出してプロキシ オブジェクトを生成できます。プロキシ オブジェクトでメソッドが呼び出される場合、その呼び出しは呼び出しハンドラーの呼び出しメソッドに転送されます。

2. テスト

学生クラスを作成する

public class Student {
    
    
    private String name;

    public Student(String name) {
    
    
        this.name = name;
    }
    public Student() {
    
    
    }

    public String getName() {
    
    
        return name;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

サービスインターフェースの作成

public interface StudentService {
    
    
    public void save();
    public Student query();
}

サービスインターフェースの実装(プロキシが必要なクラス - 拡張が必要な​​クラス)

public class IStudentServiceImpl implements StudentService {
    
    
    public void save() {
    
    
        System.out.println("Save student info");
    }

    public Student query() {
    
    
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;
        
    }
}

拡張クラスの実装

public class DaoTrascation {
    
    
    public void before(){
    
    
        System.out.println("开启事务操作");
    }
    public void after(){
    
    
        System.out.println("关闭事务操作");
    }
}

InvocationHandlerインターフェースを実装する

public class TransactionHandler implements InvocationHandler {
    
    
    //增强类对象
    private DaoTrascation daoTrascation;
    //需要代理的目标对象
    private Object object;
    public TransactionHandler(DaoTrascation daoTrascation,Object object){
    
    
        this.daoTrascation=daoTrascation;
        this.object=object;
    }
    
    //代理对象要执行的方法,实现目标方法执行,和功能增强
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        Object ret=null;
        //判断当前方法是否是save方法,是才做事务操作
        if("save".equals(method.getName())){
    
    
            daoTrascation.before();
            ret=method.invoke(object,args);
            daoTrascation.after();
        }else {
    
    
            method.invoke(object,args);
        }
        return ret;
    }
}

テストコード

public class MybeanTest {
    
    
    @Test
    public void testMyBean(){
    
    
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),  //类加载器
                IStudentServiceImpl.class.getInterfaces(),   //目标类所实现的所有接口
                transactionHandler //方法拦截处理器
        );
        studentService1.save();
        studentService1.query();
    }
}

ここに画像の説明を挿入します

3. 原理分析

上記のコードと組み合わせると、逆コンパイルを通じて基礎となる層を表示できます。

テストクラスのコードを変更する

 @Test
    public void testMyBean(){
    
    
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),
                IStudentServiceImpl.class.getInterfaces(),
                transactionHandler
        );
        studentService1.save(new Student());
        Student query = studentService1.query();
        saveProxyClass("/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest/src");
    }
    //使用生成字节码学习使用
    private void  saveProxyClass(String path) {
    
    
        //生成的代理类的字节码文件
        byte[] $proxy1 = ProxyGenerator.generateProxyClass("$Proxy1", IStudentServiceImpl.class.getInterfaces());
        try(FileOutputStream fileOutputStream = new FileOutputStream(new File(path + "$Proxy1.class"));) {
    
    
            fileOutputStream.write($proxy1);
        }  catch (IOException e) {
    
    
            throw new RuntimeException(e);
        }

    }

コードによるバイト ファイルの生成を許可する

//代理对象首先实现了StudentService接口
ublic final class $Proxy1 extends Proxy implements StudentService {
    
    
//生成5个代理方法对象(因为Object对象中有一些方法)
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    //构造函数
    public $Proxy1(InvocationHandler var1) throws  {
    
    
        super(var1);
    }
    //equals方法(Object方法)
    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);
        }
    }
    //StudentService的query方法
    public final Student query() throws  {
    
    
        try {
    
    
            return (Student)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new UndeclaredThrowableException(var3);
        }
    }
    //object的tostring方法
    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);
        }
    }
    StudentService的save方法
    public final void save(Student var1) throws  {
    
    
        try {
    
    
            super.h.invoke(this, m3, new Object[]{
    
    var1});
        } catch (RuntimeException | Error var3) {
    
    
            throw var3;
        } catch (Throwable var4) {
    
    
            throw new UndeclaredThrowableException(var4);
        }
    }
    //object的hashcode方法
    public final int hashCode() throws  {
    
    
        try {
    
    
        //invoke方法,代理对象为当前对象,代理方法为m0,参数为null
        //h是Proxy类的InvocationHandeler类的实例
            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"));
            m4 = Class.forName("Service.StudentService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Service.StudentService").getMethod("save", Class.forName("pojo.Student"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
    
    
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
    
    
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

4. 概略図

ここに画像の説明を挿入します

4. CGLIB の動的プロキシ

1 はじめに

CGLIB は、強力で高性能、高品質のサードパーティ コード生成ライブラリです。このライブラリは、Spring、Mybatis、Hibernate などのサードパーティ フレームワークでメソッド インターセプト操作を提供するために広く使用されています。CGLIB はオープン ソース プロジェクトであり、その CGLIBソース コード アドレスはです。CGLIB エージェントは主に、オブジェクトへのアクセスを制御するバイトコードの操作を通じて、オブジェクトに間接的なレベルを導入します。CGLIB は、Java リフレクションに基づく JDK 動的プロキシよりも強力です。JDK 動的プロキシの欠点は、インターフェイスのみをプロキシでき、個々の通常のクラスをプロキシできないことですが、CGLIB はこの問題を解決します。(実際には、これは JDK 動的プロキシの補足です)

2.ケース

  • 関連パッケージをインポートする
 <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>2.2.2</version>
      </dependency>
  • ターゲットクラスの作成
public class IStudentServiceImpl implements StudentService {
    
    
    public void save(Student student) {
    
    
        System.out.println("Save student info");
    }

    public Student query() {
    
    
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;

    }
}
  • トランザクション作成操作
public class DaoTrascation {
    
    
    public void before(){
    
    
        System.out.println("开启事务操作");
    }
    public void after(){
    
    
        System.out.println("关闭事务操作");
    }
}
  • トランザクションのインターセプトを実行する
public class CglibInterceptor implements MethodInterceptor {
    
    
    DaoTrascation daoTrascation;
    StudentService studentService;

    public CglibInterceptor(DaoTrascation daoTrascation, StudentService studentService) {
    
    
        this.daoTrascation = daoTrascation;
        this.studentService = studentService;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        daoTrascation.before();
        Object ret=methodProxy.invokeSuper(o,objects);
        daoTrascation.after();
        return ret;
    }
}
  • テスト
  @Test
    public void testMyBean(){
    
    
        DaoTrascation trascation = new DaoTrascation();

        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

ここに画像の説明を挿入します

3. 基礎となる原理の分析

  • テストクラスを変更する
public void testMyBean(){
    
    
        //当Spring使用CGLIB创建代理对象时,CGLIB会生成字节码来创建新的类。这个 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY
        // 属性实际上是一个开关,当你设置了这个属性,CGLIB在生成新的类时,会在指定的目录下生成相应的.class文件,这样就可以查看或者进一步
        // 分析这些自动生成的类了。
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest");
        DaoTrascation trascation = new DaoTrascation();
        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

ここに画像の説明を挿入します
プロキシ クラス ファイルは次のとおりです。

public class IStudentServiceImpl$$EnhancerByCGLIB$$8b9b66bd extends IStudentServiceImpl implements Factory {
    
    
 public final void save(Student var1) {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
    
    
            var10000.intercept(this, CGLIB$save$0$Method, new Object[]{
    
    var1}, CGLIB$save$0$Proxy);
        } else {
    
    
            super.save(var1);
        }
    }
    public final Student query() {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (Student)var10000.intercept(this, CGLIB$query$1$Method, CGLIB$emptyArgs, CGLIB$query$1$Proxy) : super.query();
    }
   }
  • プロキシ クラスはプロキシ オブジェクト IStudentServiceImpl を継承し、そのメソッドを実装するため、このパターンは継承を使用して実装されます。
  • 次に、インターセプター内のインターセプター メソッドが保存とクエリの両方で呼び出され、拡張が実現されます。

おすすめ

転載: blog.csdn.net/qq_43456605/article/details/131076909