「デザインパターン」 デザインパターンの基本原理
「デザインパターン」 シングルトンパターン 「
デザインパターン」 ファクトリーパターン
「デザインパターン」 プロトタイプパターン 「デザインパターン」
ビルダーパターン 「デザインパターン
」 アダプターパターン 「デザインパターン」
ブリッジパターン
「デザインパターン」デコレータモード
「デザインモード」 コンポジションモード
「デザインモード」 アピアランスモード 「デザインモード」
フライウェイトモード 「デザインモード」 プロキシモード
「デザイン
モード」 テンプレートメソッドモード
「デザインモード」 コマンドモード
「デザインパターン」プロキシパターン
1. 基本紹介
定義:
- プロキシ モードでは、クライアントとターゲット オブジェクトの間にプロキシ オブジェクトが導入されます。プロキシ オブジェクトはクライアント要求の処理を完了し、要求をターゲット オブジェクトに渡します。これにより、ターゲット オブジェクトの機能を拡張するための追加の機能操作を提供できます。. ターゲット オブジェクト自体を変更せずに。
プロキシモードの役割構成:
- 抽象サブジェクト (Subject) : プロキシ オブジェクトとターゲット オブジェクトのパブリック インターフェイスを定義し、このインターフェイスを実装することでプロキシ オブジェクトがターゲット オブジェクトをプロキシできるようにします。
- リアル サブジェクト (RealSubject) : プロキシ オブジェクトによって表されるターゲット オブジェクトを定義し、プロキシ オブジェクトが表すリアル オブジェクトです。
- プロキシ オブジェクト (Proxy) : 実際のテーマへの参照を保持し、内部に実際のテーマと同じインターフェイス メソッドを実装し、クライアントはプロキシ オブジェクトを介して実際のテーマにアクセスします。また、実際のテーマの機能を拡張することもできます。
プロキシ パターンのクラス図は次のとおりです。
2.静的プロキシ
事例の背景:
休日の前後に、駅は人の流れが最も大きい場所の 1 つに違いありません。現在のオンラインチケット購入チャネルは非常に成熟しているため、携帯電話でいくつかの簡単な操作でチケットを購入できるため、非常に便利です。インターネットでの切符の購入方法が世に出る前から、若い頃、一部の店舗に貼られた電車の切符売り場の言葉をよく目にした記憶がありますが、インターネットがまだ発達していなかった時代には、これは比較的便利でした。切符を購入する方法は、駅に行ってその場で切符を購入するよりもはるかに便利です。この生活シーンを代理店モデルの考え方で分析すると、駅が対象物、販売代理店が代理店とみなすことができ、代理店を通じて切符を購入し、駅もおよび代理店はチケット販売機能を持っており、「当方」がアクセス対象となります。
設計クラス図は次のとおりです。
静的プロキシの実装手順:
- まず、インターフェース(抽象主体)を定義し、インターフェースを実装するクラス(対象オブジェクト)を定義します。
- 次に、インターフェイス (抽象サブジェクト) を実装するプロキシ クラスを作成します。
- 最後に、対象オブジェクトをプロキシクラスに注入し、プロキシクラスのオーバーロードされたメソッドで対象オブジェクトのメソッドを呼び出し、対象オブジェクトのメソッド呼び出しの前後にいくつかの操作を拡張関数として追加します。
BookTicketsService
インターフェース:
public interface BookTicketsService {
void book();
}
BookTicketsServiceImpl
親切:
/**
* 目标对象
*/
public class BookTicketsServiceImpl implements BookTicketsService{
@Override
public void book() {
System.out.println("订票");
}
}
BookTicketsProxy
親切:
public class BookTicketsProxy implements BookTicketsService{
private final BookTicketsService bookTicketsService;
public BookTicketsProxy(BookTicketsService bookTicketsService) {
this.bookTicketsService = bookTicketsService;
}
@Override
public void book() {
System.out.println("订票前do something...");
bookTicketsService.book();
System.out.println("订票后do something...");
}
}
Client
親切:
/**
* 客户端类
*/
public class Client {
public static void main(String[] args) {
BookTicketsService target = new BookTicketsServiceImpl();
BookTicketsProxy bookTicketsProxy = new BookTicketsProxy(target);
bookTicketsProxy.book();
}
}
アクセスClient
オブジェクトBookTicketsProxy
はプロキシ オブジェクトを介して予約され、プロキシ オブジェクトは予約の前後に追加機能を拡張できます。
静的プロキシの概要:
- 対象物の機能を変更しないことを前提に、対象機能の拡張を実現します。
- プロキシ オブジェクトは、ターゲット オブジェクトと同じインターフェイスを実装する必要があります。多くのプロキシ クラスが存在します。インターフェイス メソッドが増加すると、ターゲット オブジェクトとプロキシ オブジェクトの両方を維持する必要があります。
3.JDK 動的プロキシ
JDK ダイナミック プロキシは、Java に付属するダイナミック プロキシ テクノロジーで、実行時にプロキシ オブジェクトを動的に作成し、インターフェースのみをプロキシできます。JDK 動的プロキシを使用する場合は、インターフェイスを定義し、java.lang.reflect.Proxy クラスと java.lang.reflect.InvocationHandler インターフェイスを使用してプロキシ オブジェクトを動的に作成する必要があります。
JDK 動的プロキシの実装手順:
- まず、インターフェース(抽象主体)を定義し、インターフェースを実装するクラス(対象オブジェクト)を定義します。
- 次に、
InvocationHandler
インターフェイスinvoke()
メソッドを書き直し、invoke()
メソッド、ここにいくつかの拡張操作を追加します。 - 最後に、 を介してプロキシ オブジェクト
Proxy.newProxyInstance()
が作成されます。
BookTicketsService
インターフェース:
public interface BookTicketsService {
void book();
}
BookTicketsServiceImpl
親切:
public class BookTicketsServiceImpl implements BookTicketsService {
@Override
public void book() {
System.out.println("订票");
}
}
カスタムMyInvocationHandler
クラス:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("订票前do something...");
// 执行目标对象
Object result = method.invoke(target, args);
System.out.println("订票后do something...");
return result;
}
}
JdkProxyFactory
クラス: プロキシ ファクトリ、プロキシ オブジェクトの作成:
/**
* JdkProxy工厂,创建代理对象
*/
public class JdkProxyFactory {
public static Object getObjectProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 获取目标对象的加载器,加载代理类
target.getClass().getInterfaces(), // 目标对象实现的接口
new MyInvocationHandler(target) // 对应于代理对象自定义的InvocationHandler
);
}
}
Client
親切:
/**
* 客户端类
*/
public class Client {
public static void main(String[] args) {
BookTicketsService bookTicketsService = (BookTicketsService) JdkProxyFactory.getObjectProxy(new BookTicketsServiceImpl());
bookTicketsService.book();
}
}
メモリ内の動的プロキシ クラスの作成プロセスをよりよく観察するには、Java 診断ツール arthas を使用して、プログラムの実行中にプロキシ クラスの構造を出力する必要があります。手順は次のとおりです。
- プログラムの実行後にメモリが解放され、プログラムの実行中に動的プロキシ クラスの構造を観察できないため、クライアント クラスを開始してプログラムの実行を維持し、プロキシ オブジェクトのクラス名を出力します。これは後で arthas ツールを表示するのに便利です。Client クラスのコードは次のとおりです。
public class Client {
public static void main(String[] args) {
BookTicketsService bookTicketsService = (BookTicketsService) JdkProxyFactory.getObjectProxy(new BookTicketsServiceImpl());
bookTicketsService.book();
System.out.println(bookTicketsService .getClass());
while (true) {
}
}
}
- arthas-jar ツールをダウンロードし、クリックしてダウンロードし、アドレスにジャンプします
- コマンド ライン ウィンドウを開き、arthas-boot.jar があるルート ディレクトリを入力します。
- 次のコマンドを入力します。
java -jar arthas-boot.jar
- 以下に示すように、スタートアップ クラス名 Client を検索し、対応するシリアル番号を入力します。
- ロードが完了したら、コマンドを入力し
jad com.sun.proxy.$Proxy0
、以下に示すように、プログラムの実行中にプロキシ クラスの構造が出力されるのを待ちます。
- 完全なコードは次のとおりです。
package com.sun.proxy;
import com.hzz.proxy.dynamicproxy.jdk.BookTicketsService;
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 BookTicketsService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.hzz.proxy.dynamicproxy.jdk.BookTicketsService").getMethod("book", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{
object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void book() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
- プロキシ クラスが
$Proxy0
実際にBookTicketsService
、これはProxy.newProxyInstance()
メソッド、静的プロキシのように明示的に実装する必要はありません。したがって、プロキシ クラスとターゲットの両方がクラスは同じインターフェースを実装する必要があります。 - また、メソッドで作成したクラスに対象オブジェクトが渡され、 の親クラスにソース
Proxy.newProxyInstance()
コード。target
$Proxy0
Proxy
クラスの構造によると、動的プロキシの実行プロセスは大まかに次のようになります。
- まず、
Client
のbook
。 - その後、ポリモーフィズムに従って、プロキシクラス
$Proxy0
のbook()
。 - 次に、プロキシ クラス
$Proxy0
のインターフェイスのサブ実装クラス オブジェクトのメソッドを呼び出しますbook()
。InvocationHandler
invoke()
- 最後に、
invoke()
メソッドBookTicketsServiceImpl
はbook()
リフレクションを通じてターゲット クラスのメソッドを実行します。
JDK 動的プロキシの概要:
- JDK 動的プロキシを使用する場合、 メソッド
private
とstatic
メソッドをプロキシできません。プロキシ クラスとターゲット クラスは、private
同じstatic
。 - JDK 動的プロキシ クラスによって生成されるクラスの親クラスはクラスであり、Java では多重継承がサポートされていない
$Proxy0
ため、JDK 動的プロキシは共通クラスではなくインターフェイスのみをプロキシできます。Proxy
4. CGLIB 動的プロキシ
上記の 2 つのセクションから、静的プロキシであろうと JDK 動的プロキシであろうと、ターゲット クラスはインターフェイスを実装する必要があることがわかりますが、ターゲット オブジェクトはインターフェイスを実装していない単一のオブジェクトである場合もあります。使用するには プロキシ モードでは、CGLIB 動的プロキシを使用できます。
CGLIB (コード生成ライブラリ) 動的プロキシ:
- CGLIB は、プロキシ クラスを継承するサブクラスを動的に生成し、プロキシ クラスの
final
装飾されてサブクラスのメソッド インターセプト テクノロジを使用して、親クラスのすべてのメソッド呼び出しをインターセプトします。 - CGLIB プロキシは、JDK 動的プロキシを適切に補完します. 強力で高性能なコード生成パッケージとして, インターフェイスを実装しないクラスにプロキシを提供できます. メソッドインターセプトを実装するために AOP フレームワークで広く使用されています.
CGLIB の jar 座標は次のとおりです。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
JDK 動的プロキシの実装手順:
- まず、ターゲット オブジェクトを定義します。
- 次に、
MethodInterceptor
インタフェースメソッドを書き換えます。これは、intercept
JDK 動的プロキシのinvoke()
メソッドと同様であり、intercept()
拡張プロキシ クラスのメソッドをインターセプトするためにメソッドが使用されます。 - 最後に
Enhancer
、クラスのメソッドを使用してプロキシ オブジェクトを作成しますcreate()
。
BookTicketsService
クラス: ターゲット オブジェクト
public class BookTicketsService {
public void book() {
System.out.println("订票");
}
}
カスタムMyMethodInterceptor
クラス:
public class MyMethodInterceptor implements MethodInterceptor {
/**
* 重写 intercept 方法,在该方法中调用目标对象的方法
* @param o 代理对象
* @param method 目标对象的方法的 method 实例
* @param objects 方法entry
* @param methodProxy 代理对象中的方法 method 实例
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("订票前do something");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("订票后do something");
return result;
}
}
CglibProxyFactory
親切:
/**
* CglibProxy工厂,创建代理对象
*/
public class CglibProxyFactory {
public static Object getObjectProxy(Class<?> clazz) {
// 创建Enhancer对象,类似于JDK动态代理的Proxy类
Enhancer enhancer = new Enhancer();
// 设置父类的字节码对象
enhancer.setClassLoader(clazz.getClassLoader());
// 设置代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
return enhancer.create();
}
}
Client
親切:
/**
* 客户端类
*/
public class Client {
public static void main(String[] args) {
BookTicketsService bookTicketsService = (BookTicketsService) CglibProxyFactory.getObjectProxy(BookTicketsService.class);
bookTicketsService.book();
}
}
CGLIB プロキシに関する注意:
- CGLIB パッケージの最下層は、バイトコード処理フレームワーク ASM を使用してバイトコードを変換し、新しいクラスを生成することです。
final
継承された親クラスが定数クラスである場合、継承できず、エラーが報告されるため、メモリ内でサブクラスを動的に構築するために、プロキシされるクラスを にすることはできませんjava.lang.IllegalArgumentException
。- サブクラスが
private
親クラスのプライベート メソッドにアクセスできないため、ターゲット オブジェクトのメソッドは存在できません。final
サブクラスは親クラスの不変メソッドをオーバーライドできないため、ターゲット オブジェクトのメソッドは存在できません。ターゲット オブジェクトのメソッドは、static
static メソッドは class に属しているため、オブジェクトの一部ではありません。
5. CGLIB と JDK 動的プロキシの性能比較実験
CGLIB と JDK の 2 つの動的プロキシ メソッドのパフォーマンスを理解するために、次の実験が設計されています。 、および各比較操作の数が 1000w 倍に増加します。実験のJava環境はそれぞれJDK1.6、JDK1.8、JDK11で、cglibのバージョンは2.2.2です。
5.1 実験的なソースコード
抽象的なテーマのインターフェースBookTicketsService
:
public interface BookTicketsService {
void book();
}
対象者BookTicketsServiceImpl
:
public class BookTicketsServiceImpl implements BookTicketsService {
@Override
public void book() {
int arr[] = {
8, 5, 3, 2, 4};
// 冒泡排序
for (int i = 0; i < arr.length; i++) {
// 外层循环,遍历次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 内层循环,升序(如果前一个值比后一个值大,则交换)
// 内层循环一次,获取一个最大值
if (arr[j] > arr[j + 1]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
// 选择排序
for (int i = 0; i < arr.length; i++) {
// 默认第一个是最小的。
int min = arr[i];
// 记录最小的下标
int index = i;
// 通过与后面的数据进行比较得出,最小值和下标
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) {
min = arr[j];
index = j;
}
}
// 然后将最小值与本次循环的,开始值交换
int temp = arr[i];
arr[i] = min;
arr[index] = temp;
}
// 插入排序
for (int i = 1; i < arr.length; i++) {
// 外层循环,从第二个开始比较
for (int j = i; j > 0; j--) {
// 内存循环,与前面排好序的数据比较,如果后面的数据小于前面的则交换
if (arr[j] < arr[j - 1]) {
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
} else {
// 如果不小于,说明插入完毕,退出内层循环
break;
}
}
}
}
}
CGLIB 動的プロキシのカスタムMyMethodInterceptor
クラス。
public class MyMethodInterceptor implements MethodInterceptor {
/**
* @param o 代理对象
* @param method 被拦截的方法
* @param objects 方法entry
* @param methodProxy 调用目标对象方法的代理
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
}
CGLIB 動的プロキシ ファクトリ CglibProxyFactory クラス:
/**
* CglibProxy工厂,创建代理对象
*/
public class CglibProxyFactory {
public static Object getObjectProxy(Class<?> clazz) {
// 创建动态代理增强对象
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
JDK 動的プロキシのカスタムMyInvocationHandler
クラス。
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行目标对象
Object result = method.invoke(target, args);
return result;
}
}
JDK 動的プロキシ ファクトリ JdkProxyFactory クラス:
/**
* JdkProxy工厂,创建代理对象
*/
public class JdkProxyFactory {
public static Object getObjectProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 获取目标类的加载器,加载目标类
target.getClass().getInterfaces(), // 代理需要实现的接口
new MyInvocationHandler(target) // 对应于代理对象自定义的InvocationHandler
);
}
}
テスト比較ComparsionTest
クラス:
public class ComparisonTest {
public static void main(String[] args) {
BookTicketsService bookTicketsService = new BookTicketsServiceImpl();
BookTicketsService jdkProxy = (BookTicketsService) JdkProxyFactory.getObjectProxy(bookTicketsService);
BookTicketsService cglibProxy = (BookTicketsService) CglibProxyFactory.getObjectProxy(BookTicketsServiceImpl.class);
long[] runCounts = new long[]{
10000000, 20000000, 30000000, 40000000, 50000000, 60000000, 70000000, 80000000, 90000000, 100000000};
Map<String, BookTicketsService> proxyMap = new HashMap<>(3);
proxyMap.put("jdkProxy", jdkProxy);
proxyMap.put("cglibProxy", cglibProxy);
runWithoutMonitor(proxyMap);
runWithMonitor(proxyMap, runCounts);
}
// 预热
private static void runWithoutMonitor(Map<String, BookTicketsService> proxyMap) {
for (int i = 0; i < 1; i++) {
for (String key : proxyMap.keySet()) {
for (int j = 0; j < 100000; j++) {
proxyMap.get(key).book();
}
}
}
}
private static void runWithMonitor(Map<String, BookTicketsService> proxyMap, long[] runCounts) {
for (int i = 0; i < runCounts.length; i++) {
System.out.println("------------------[jdk version = 1.8.0_301] [运行"+runCounts[i]+"次]---------------------");
for (String key : proxyMap.keySet()) {
long start = System.currentTimeMillis();
for (int j = 0; j < runCounts[i]; j++) {
proxyMap.get(key).book();
}
long end = System.currentTimeMillis();
System.out.println("["+ key + "] Elapsed Time:" + (end-start) + "ms");
}
}
}
}
5.2 実験結果
5.2.1 jdk 1.6 バージョンでの実験結果
IDE での実行結果のスクリーンショット:
Cglib と jdk の動的プロキシのパフォーマンス比較実験データ:
ラン数 | cglib 時間消費 (ミリ秒) | jdk時間消費 |
---|---|---|
10000000 | 359 | 365 |
20000000 | 610 | 624 |
30000000 | 907 | 929 |
40000000 | 1191 | 1226 |
50000000 | 1492年 | 1589年 |
60000000 | 1807年 | 1853年 |
70000000 | 2095 | 2159 |
80000000 | 2450 | 2515 |
90000000 | 2756 | 2841 |
100000000 | 3052 | 3155 |
cglib と jdk の動的プロキシのパフォーマンス比較のヒストグラム:
5.2.2 jdk 1.8 バージョンでの実験結果
IDE での実行結果のスクリーンショット:
Cglib と jdk の動的プロキシのパフォーマンス比較実験データ:
ラン数 | cglib 時間消費 (ミリ秒) | jdk時間消費 |
---|---|---|
10000000 | 284 | 292 |
20000000 | 502 | 431 |
30000000 | 744 | 668 |
40000000 | 910 | 866 |
50000000 | 1158 | 1115 |
60000000 | 1379年 | 1292 |
70000000 | 1543 | 1512 |
80000000 | 1811年 | 1713年 |
90000000 | 2028年 | 1951年 |
100000000 | 2292 | 2143 |
cglib と jdk の動的プロキシのパフォーマンス比較のヒストグラム:
5.2.3 jdk 11 バージョンでの実験結果
IDE での実行結果のスクリーンショット:
Cglib と jdk の動的プロキシのパフォーマンス比較実験データ:
ラン数 | cglib 時間消費 (ミリ秒) | jdk時間消費 |
---|---|---|
10000000 | 360 | 325 |
20000000 | 494 | 439 |
30000000 | 642 | 631 |
40000000 | 836 | 836 |
50000000 | 1054 | 1060 |
60000000 | 1267 | 1254 |
70000000 | 1487年 | 1454 |
80000000 | 1685年 | 1650年 |
90000000 | 1931年 | 1893年 |
100000000 | 2087年 | 2045 |
cglib と jdk の動的プロキシのパフォーマンス比較のヒストグラム:
5.3 実験結果の分析
- JDK 1.6 環境では、JDK 動的プロキシのパフォーマンスは実際には CGLIB 動的プロキシほど良くなく、JDK 動的プロキシ オブジェクトに必要な時間消費は明らかに CLGIB よりも多いことがわかります。動的プロキシ オブジェクト。
- 当時、JDK 1.8 バージョンでは、JDK 動的プロキシ オブジェクトに必要な時間消費は、CGLIB 動的プロキシの時間パフォーマンスと同等またはそれを上回っていました. 少なくとも、1.8 環境では、JDK 動的プロキシの効率は確実です。 proxy は大幅に改善されました. これは主に JDK のリフレクション テクノロジの最適化とアップグレードによるものです.
- 同样地,在JDK11环境下,JDK动态代理的性能依旧保持较好,不会再像JDK1.6时的动态代理性能那么差了。JDK8 和 JDK11 是目前市面上使用较多的两个版本,甚至以后会升级到 JDK17 版本,因为从 springboot 3.0 开始只支持 JDK 17以上版本。
- 本次实验我使用的 CGLIB 版本为 2.2.2,其实在较新的 3.3.0 版本实验中,CGLIB 动态代理表现出来的性能依然比 JDK 动态代理好的,当时不会好 10 倍以上。
- 目前,JDK动态代理和CGLIB动态代理的性能并不会相差很多,使用哪种动态代理方式往往不是因为性能的差别,而是因为这两种代理方式本身的特点决定的,JDK动态代理只支持接口,而CGLIB动态代理既支持接口又支持类,往往出于场景的考虑决定使用哪种代理方式,性能差距我觉得可以忽略不记
6. 区别比较
静态代理和动态代理的区别:
- 如果接口增加一个方法,静态代理除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
- 动态代理最大的优点就是接口中声明的所有方法都被转移到调用处理器的方法
InvocationHandler.invoke
中处理。 在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
JDK 代理和 CGLIB 代理的区别:
- 如果有接口则使用 JDK 动态代理,如果没有接口则使用 CGLIB 动态代理,因为 CGLIB 不仅可以代理接口还能代理普通类。
- JDK 动态代理使用 Java 反射技术进行操作,在生成类上更高效。CGLIB 使用 ASM 框架直接对字节码进行修改,使用了 FastClass 的特性。在某些情况下,类的方法执行会比较高效。
7. 代理模式在框架中的应用
我们知道,Spring 中有一个重要的概念就是面向切面编程(AOP),它很好地与 OOP 相配合,可以帮助我们做一下日志处理、权限控制以及事务管理的操作。因此,Spring 中对 AOP 的实现机制也有相关说明:
大概意思就是:
- Spring 官方默认使用标准的 JDK 动态代理作为 AOP 的代理方式,可以代理任何接口。
- 如果一个业务对象没有实现任何接口,那么 AOP 就会使用 CGLIB 动态代理。
但是,在 SpringBoot 中我们发现,对于 SpringBoot 2.x 以前的版本,默认使用的是 JDK 动态代理实现 AOP,具体可以见类 AopAutoConfiguration
:
SpringBoot 1.5 版本中 AOP 的代理机制:
@Configuration
@ConditionalOnClass({
EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = true)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = false)
public static class CglibAutoProxyConfiguration {
}
}
在 SpringBoot 1.5 版本中:
- SpringBoot 默认使用 JDK 动态代理作为 AOP 代理的方式。
SpringBoot 2.7 版本中 AOP 的代理机制:
@Configuration
@ConditionalOnClass({
EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(
proxyBeanMethods = false
)
@EnableAspectJAutoProxy(
proxyTargetClass = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {
"proxy-target-class"},
havingValue = "false",
matchIfMissing = false
)
static class JdkDynamicAutoProxyConfiguration {
JdkDynamicAutoProxyConfiguration() {
}
}
@Configuration(
proxyBeanMethods = false
)
@EnableAspectJAutoProxy(
proxyTargetClass = true
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {
"proxy-target-class"},
havingValue = "true",
matchIfMissing = true
)
static class CglibAutoProxyConfiguration {
CglibAutoProxyConfiguration() {
}
}
}
在 SpringBoot 2.7 版本中:
- SpringBoot 默认使用 CGLIB 动态代理作为 AOP 代理的方式。
至于为什么要更改 springboot 中 AOP 默认代理方式,首先看一下之前 Spring 的开发者们在 github 上的讨论:
上の図に示すように、Phil Webb は使用について言及しました@EnableTransactionManagement(proxyTargetClass = true)
。つまり、CGLIB 動的プロキシ メソッドを使用することをお勧めします。誰かがこのインターフェイスを使用しない場合、迷惑な問題を回避できます。ここで厄介な問題は、JDK 動的プロキシがクラスをプロキシできず、インターフェースのみをプロキシできるという制限によって引き起こされます。したがって、springboot 2.x 以降、AOP プロキシのデフォルトの方法は、JDK 動的プロキシではなく CGLIB 動的プロキシです。
もちろん、どうしても JDK 動的プロキシを使用する場合は、構成ファイルで指定することもできます。spring.aop.proxy-target-class = false