設計パターンは、よく使われる開発技法を体系化したものです。これにより、エンジニア同士の技術的コミュニケーションがより効率的になります。例えば、I/Oモジュールにおけるデコレーターパターンの採用は、コード構造を理解する上で大きなヒントになります。
多くの設計パターンはパフォーマンス向上に直接的な影響を与えるわけではなく、コード構成の方法論としての側面が強いです。本記事では、パフォーマンスに影響を与える設計パターンについて具体例を交えて解説します。代理パターン、シングルトンパターン、フライウェイトパターン、プロトタイプパターンなど、それぞれの特徴と適用ケースを確認します。
ダイナミックプロキシの遅延原因特定手法
SpringフレームワークではCGLIBを用いたプロキシ生成が広く採用されています。複雑なプロジェクトでは多くのAOP処理が存在し、権限チェックやログ記録などの切片処理が行われます。以下に、Arthasツールを用いた性能ボトルネックの特定方法を紹介します。
@Component
public class SampleBean {
public void execute() {
System.out.println("#######################");
}
}
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.demo.SampleBean.*(..))")
public void definePointcut() {
}
@Before("definePointcut()")
public void beforeAdvice() {
System.out.println("Logging started");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}
}
@Controller
public class AopTestController {
@Autowired
private SampleBean sampleBean;
@ResponseBody
@GetMapping("/aop")
public String testAop() {
long startTime = System.currentTimeMillis();
sampleBean.execute();
long duration = System.currentTimeMillis() - startTime;
String className = sampleBean.getClass().toString();
return className + " | " + duration;
}
}
実行結果は以下の通りで、CGLIBによるプロキシ生成が確認できます。
class com.example.demo.SampleBean$$EnhancerBySpringCGLIB$$a5d91535 | 1023
Arthasを使用した解析手順は以下の通りです:
- アプリケーションを選択して分析画面へ移動
trace com.example.demo.SampleBean executeコマンドを実行/aopエンドポイントを呼び出して処理トレースを取得
代理パターンの比較
Javaにおける動的プロキシ実装にはJDKとCGLIBの2種類があります。
| 実装方法 | 対象 | 主要クラス |
|---|---|---|
| JDK | インターフェース | InvocationHandler, Proxy |
| CGLIB | クラス | MethodInterceptor, Enhancer |
JMHベンチマーク結果(JDK1.8環境):
Benchmark Mode Cnt Score Error Units
ProxyBenchmark.cglib thrpt 10 78499.580 ± 1771.148 ops/ms
ProxyBenchmark.jdk thrpt 10 88948.858 ± 814.360 ops/ms
プロキシ作成処理のベンチマーク:
Benchmark Mode Cnt Score Error Units
ProxyCreateBenchmark.cglib thrpt 10 7281.487 ± 1339.779 ops/ms
ProxyCreateBenchmark.jdk thrpt 10 15612.467 ± 268.362 ops/ms
SpringがCGLIBを選択した主な理由は、インターフェース不要なクラスレベルのプロキシ生成が可能であるためです。
シングルトンパターン
Springコンテナでは@Scopeアノテーションでスコープを指定できます。シングルトンの実装方法として、ダブルチェックロックとenumを用いた手法があります。
public class SingletonManager {
private static volatile SingletonManager instance;
public static SingletonManager getInstance() {
if (instance == null) {
synchronized (SingletonManager.class) {
if (instance == null) {
instance = new SingletonManager();
}
}
}
return instance;
}
}
このダブルチェック方式は線程安全性を保証しますが、volatileキーワードの使用や同期の必要性など、注意点が多くなります。現在ではenumを用いた実装が推奨されています。
public class EnumSingleton {
private EnumSingleton() {
}
public static EnumSingleton getInstance() {
return Holder.HOLDER.instance;
}
private enum Holder {
HOLDER;
private final EnumSingleton instance;
Holder() {
instance = new EnumSingleton();
}
}
}
フライウェイトパターン
オブジェクトの共有を目的とした設計パターンで、インスタンスの再利用を最大化します。以下は戦略パターンの例ですが、オブジェクト共有の観点からはフライウェイトパターンとみなせます。
Map<String, Strategy> strategyMap = new HashMap<>();
strategyMap.put("alpha", new AlphaStrategy());
strategyMap.put("beta", new BetaStrategy());
プロトタイプパターン
複製作成を基本とする設計パターンで、Javaのclone()メソッドが代表的です。ただし浅いコピーしか行えず、深さのコピーにはシリアライズなど他の手段が必要です。