フレームワーク内部の設計思想を解読する
数多くのオープンソースプロジェクトは、単なるライブラリではなく、柔軟性と保守性を高めるための優れた設計パターンの実装集と言えます。本記事では、代表的な Java フレームワークにおける主要なデザインパターンの使用例を取り上げ、そのコード構造や変数名を変更しながら再構成することで、それぞれの特性を解説します。
1. ビルダーパターンの多様な実装形式
複雑なオブジェクト構築を段階的に行うビルダーパターンは、フレームワーク設定で頻繁に採用されています。内部クラス利用、外部クラス分離、静的メソッドファクトリなど、状況に応じたバリエーションが存在します。
MyBatis 環境構成の構築
MyBatis の Environment クラスは、静的内部クラスを利用してビルダーを定義しています。以下のコードは元のロジックを維持しつつ、変数名やチェック順序を変更して再構成した例です。
public final class DbEnvironment {
private final String envIdentifier;
private final TransactionFactory txProducer;
private final DataSource dbPool;
public DbEnvironment(String identifier, TransactionFactory factory, DataSource pool) {
if (Objects.isNull(identifier)) {
throw new IllegalStateException("Environment ID cannot be empty");
}
this.envIdentifier = identifier;
if (Objects.nonNull(factory)) {
this.txProducer = factory;
} else {
throw new IllegalArgumentException("Transaction factory is required");
}
if (Objects.isNull(pool)) {
throw new IllegalArgumentException("DataSource instance is missing");
}
this.dbPool = pool;
}
public static class EnvConfigurator {
private String envId;
private TransactionFactory txFactory;
private DataSource connectionPool;
public EnvConfigurator(String id) {
this.envId = id;
}
public EnvConfigurator useTransactionFactory(TransactionFactory factory) {
this.txFactory = factory;
return this;
}
public EnvConfigurator useConnectionPool(DataSource pool) {
this.connectionPool = pool;
return this;
}
public DbEnvironment create() {
return new DbEnvironment(this.envId, this.txFactory, this.connectionPool);
}
}
}
Swagger 文書のメタデータ管理
Swagger では、API 情報の定義を外部ビルダークラスで行っています。各プロパティのセット時にフラuent 設計を採用しており、可読性を高めています。
public class ServiceDocumentation {
private final String docTitle;
private final String apiDesc;
private final ContactPoint owner;
public ServiceDocumentation(String title, String desc, ContactPoint contact) {
this.docTitle = title;
this.apiDesc = desc;
this.owner = contact;
}
// Getters...
}
public class DocBuilder {
private String titleStr;
private String descriptionText;
private ContactPoint teamContact;
public DocBuilder title(String val) {
this.titleStr = val;
return this;
}
public DocBuilder info(String text) {
this.descriptionText = text;
return this;
}
public DocBuilder owner(ContactPoint contact) {
this.teamContact = contact;
return this;
}
public ServiceDocumentation assemble() {
return new ServiceDocumentation(titleStr, descriptionText, teamContact);
}
}
Nacos サービスインスタンスの定義
Nacos のサービスディスカバリ機能では、ノード情報を構築するために独立したビルダーを利用し、null 安全な初期化を行っています。
public class ServiceNode implements Serializable {
private String nodeId;
private String ipAddress;
private int listeningPort;
// Setters and Getters omitted for brevity
public void mergeMetadata(Map<String, String>> extraInfo) {
if (extraInfo != null && !extraInfo.isEmpty()) {
// Logic to merge metadata
}
}
}
public class NodeConstructor {
private String uid;
private String hostIp;
private Integer listenPort;
private NodeConstructor() {}
public static NodeConstructor create() {
return new NodeConstructor();
}
public NodeConstructor identity(String id) {
this.uid = id;
return this;
}
public NodeConstructor address(String ip) {
this.hostIp = ip;
return this;
}
public NodeNode build() {
ServiceNode node = new ServiceNode();
node.setNodeId(uid);
node.setIpAddress(hostIp);
node.setListeningPort(listenPort == null ? 80 : listenPort);
return node;
}
}
2. テンプレートメソッドパターンによる拡張性確保
共通処理を親クラスに定義し、差異となる部分をサブクラスで実装させるテンプレートメソッドパターンは、MyBatis の型変換処理などで見られます。
// 型ハンドラインターフェース
public interface DataMapper<T> {
void mapParameter(PreparedStatement stmt, int index, T value, JdbcType sqlType) throws SQLException;
T fetchResult(ResultSet rs, String colName) throws SQLException;
}
// 抽象基底クラス
public abstract class AbstractDataMapper<T> implements DataMapper<T> {
@Override
public void mapParameter(PreparedStatement stmt, int idx, T data, JdbcType type) throws SQLException {
if (data == null) {
stmt.setNull(idx, Objects.requireNonNull(type).TYPE_CODE);
} else {
try {
processNonNull(stmt, idx, data, type);
} catch (Exception ex) {
throw new TypeConversionException("Failed to set parameter", ex);
}
}
}
protected abstract void processNonNull(PreparedStatement ps, int idx, T obj, JdbcType t) throws SQLException;
}
// 具体クラス
public class IntTypeHandler extends AbstractDataMapper<Integer> {
@Override
protected void processNonNull(PreparedStatement ps, int i, Integer num, JdbcType jtype) throws SQLException {
ps.setInt(i, num);
}
}
3. スレッドコンテキストにおけるシングルトン
標準的なシングルトンに加え、スレッド局所変数(ThreadLocal)を活用して、特定スレッド内で一意なコンテキストを保つパターンです。
public class RuntimeTraceRecord {
private static final ThreadLocal<RuntimeTraceRecord> RECORD_STORE = new ThreadLocal<>();
private String resourceName;
private String operationName;
private String errorMsg;
private RuntimeTraceRecord() {}
public static RuntimeTraceRecord getRecord() {
RuntimeTraceRecord current = RECORD_STORE.get();
if (current == null) {
current = new RuntimeTraceRecord();
RECORD_STORE.set(current);
}
return current;
}
public RuntimeTraceRecord markResource(String res) {
this.resourceName = res;
return this;
}
public RuntimeTraceRecord logError(String msg) {
this.errorMsg = msg;
return this;
}
public void clear() {
RECORD_STORE.remove();
}
}
4. マーカインタフェースによる制御ループ
メソッドを持たない空のインタフェースを実装することで、Spring コンテナが特定のライフサイクルイベントを介入可能にする仕組みです。
public interface AwareComponent {
// 空の実装
}
public interface ContainerAware extends AwareComponent {
void registerContext(ApplicationContext ctx);
}
public interface BeanRegistryAware extends AwareComponent {
void injectBeanFactory(BeanFactory factory);
}
public interface NamingAware extends AwareComponent {
void assignBeanName(String name);
}
5. ファクトリパターンによる接続元抽象化
データベース接続先の具体的な実装を隠蔽し、ファクトリを通じてデータソースを取得するパターンです。
public interface ConnectionSourceFactory {
void configure(Properties settings);
DataSource produceSource();
}
// Spring の組み込みテスト DB 用ファクトリ実装イメージ
public class EmbeddedDBCreator {
private String dbName = "test_db";
private DataSourceFactory sourceMaker = new SimpleDriverSourceFactory();
public EmbeddedDBCreator setName(String name) {
this.dbName = name;
return this;
}
public DataSource getDbConnection() {
sourceMaker.configure(createProperties(dbName));
return sourceMaker.produceSource();
}
}
6. アダプターとストラテジーパターンによるロギング統一
フレームワーク内部で使用されるログ出力を、プロジェクトの依存するログライブラリに合わせて切り替えるアダプターパターンの実装例です。
public interface SystemLogger {
void debug(String msg);
void error(String msg, Throwable err);
boolean isDebugOn();
}
// SLF4J への適合
public class Slf4jWrapper implements SystemLogger {
private org.slf4j.Logger delegate;
public Slf4jWrapper(org.slf4j.Logger logger) {
this.delegate = logger;
}
@Override
public void debug(String message) {
if (delegate.isDebugEnabled()) {
delegate.debug(message);
}
}
// Other methods implemented similarly
}
// 失敗耐性付きアダプター (Dubbo 実装参照)
public class FaultTolerantLogger implements SystemLogger {
private final SystemLogger innerLogger;
private static volatile boolean active = true;
public FaultTolerantLogger(SystemLogger target) {
this.innerLogger = target;
}
@Override
public void error(String msg, Throwable e) {
if (!active) return;
try {
innerLogger.error(msg, e);
} catch (Exception ex) {
// 内部的なログエラー時は無視
}
}
// Remaining delegation methods...
}