Javaにおけるプロキシパターンの実装と活用方法

プロキシパターンは、オブジェクトのアクセスを制御するための代理オブジェクトを提供するデザインパターンです。他のオブジェクトへの参照を保持し、必要に応じてその処理の前後で追加ロジックを実行できます。

基本的な実装

ユーザ登録機能を例に見ていきましょう。まず、インターフェースとその実装クラスを作成します。


// ユーザ管理インターフェース
package jp.tech.example.proxy;

public interface UserManager {
    void registerUser();
}

// 実装クラス
package jp.tech.example.proxy;

public class SimpleUserManager implements UserManager {
    @Override
    public void registerUser() {
        System.out.println("ユーザーをデータベースに登録中...");
    }
}

通常の利用方法は以下の通りです。


// アプリケーションクラス
package jp.tech.example.proxy;

public class Application {
    public static void main(String[] args) {
        UserManager manager = new SimpleUserManager();
        manager.registerUser();
    }
}

実行結果:

ユーザーをデータベースに登録中...

静的プロキシによる機能拡張

登録処理の前後にログを出力する必要が出てきたとします。元のクラスは変更できないため、プロキシクラスでラップします。


// ロギングプロキシ
package jp.tech.example.proxy;

public class UserManagerLogger implements UserManager {
    private UserManager target;
    
    public UserManagerLogger(UserManager manager) {
        this.target = manager;
    }
    
    @Override
    public void registerUser() {
        System.out.println("【処理開始】時刻: " + System.currentTimeMillis());
        target.registerUser();
        System.out.println("【処理終了】時刻: " + System.currentTimeMillis());
    }
}

// クライアントコード
package jp.tech.example.proxy;

public class Application {
    public static void main(String[] args) {
        UserManager loggedManager = new UserManagerLogger(new SimpleUserManager());
        loggedManager.registerUser();
    }
}

実行結果:

【処理開始】時刻: 1672501200000
ユーザーをデータベースに登録中...
【処理終了】時刻: 1672501201000

専用プロキシの制約

より厳格な制御が必要な場合、対象クラス自身が特定のプロキシ経由でのみ実行を許可するように設計できます。


// 認証付きインターフェース
package jp.tech.example.proxy;

public interface SecuredUserManager {
    void registerUser();
    SecuredUserManager createAuthorizedProxy();
}

// 実装クラス
package jp.tech.example.proxy;

public class SecureUserManager implements SecuredUserManager {
    private SecuredUserManager authorizedProxy;
    
    @Override
    public void registerUser() {
        if (!isAccessedViaProxy()) {
            System.out.println("エラー: 認証済みプロキシを使用してください");
            return;
        }
        System.out.println("ユーザーをデータベースに登録中...");
    }
    
    private boolean isAccessedViaProxy() {
        return authorizedProxy != null;
    }
    
    @Override
    public SecuredUserManager createAuthorizedProxy() {
        this.authorizedProxy = new AuthorizedUserProxy(this);
        return this.authorizedProxy;
    }
}

// 認証プロキシ
package jp.tech.example.proxy;

public class AuthorizedUserProxy implements SecuredUserManager {
    private SecuredUserManager origin;
    
    public AuthorizedUserProxy(SecuredUserManager manager) {
        this.origin = manager;
    }
    
    @Override
    public void registerUser() {
        System.out.println("【認証プロキシ】アクセス許可");
        origin.registerUser();
        System.out.println("【認証プロキシ】アクセス終了");
    }
    
    @Override
    public SecuredUserManager createAuthorizedProxy() {
        return this;
    }
}

正しい使用方法:


package jp.tech.example.proxy;

public class Application {
    public static void main(String[] args) {
        SecuredUserManager manager = new SecureUserManager().createAuthorizedProxy();
        manager.registerUser();
    }
}

実行結果:

【認証プロキシ】アクセス許可
ユーザーをデータベースに登録中...
【認証プロキシ】アクセス終了

不正なアクセスの例:


// 直接インスタンス化
new SecureUserManager().registerUser();
// 出力: エラー: 認証済みプロキシを使用してください

// 不正なプロキシ使用
new AuthorizedUserProxy(new SecureUserManager()).registerUser();
// 出力: 【認証プロキシ】アクセス許可 → エラー: 認証済みプロキシを使用してください

動的プロキシによる柔軟な実装

JDKが提供する動的プロキシを使うと、実行時にプロキシクラスを生成できます。InvocationHandlerインターフェースを実装します。


// インターセプタ
package jp.tech.example.proxy;

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

public class LoggingInterceptor implements InvocationHandler {
    private final Object delegate;
    
    public LoggingInterceptor(Object target) {
        this.delegate = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("【AOP】メソッド実行前: " + method.getName());
        Object result = method.invoke(delegate, args);
        System.out.println("【AOP】メソッド実行後: " + method.getName());
        return result;
    }
}

// 実行クラス
package jp.tech.example.proxy;

import java.lang.reflect.Proxy;

public class Application {
    public static void main(String[] args) {
        SimpleUserManager realManager = new SimpleUserManager();
        LoggingInterceptor handler = new LoggingInterceptor(realManager);
        
        UserManager dynamicProxy = (UserManager) Proxy.newProxyInstance(
            UserManager.class.getClassLoader(),
            new Class[]{UserManager.class},
            handler
        );
        
        dynamicProxy.registerUser();
    }
}

実行結果:

【AOP】メソッド実行前: registerUser
ユーザーをデータベースに登録中...
【AOP】メソッド実行後: registerUser

プロキシファクトリの活用

動的プロキシの生成ロジックを共通化すると、使い勝手が向上します。


// プロキシファクトリ
package jp.tech.example.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyFactory {
    @SuppressWarnings("unchecked")
    public static <T> T createLoggingProxy(T instance) {
        ClassLoader loader = instance.getClass().getClassLoader();
        Class[] interfaces = instance.getClass().getInterfaces();
        InvocationHandler handler = new LoggingInterceptor(instance);
        return (T) Proxy.newProxyInstance(loader, interfaces, handler);
    }
}

// 簡潔なクライアントコード
package jp.tech.example.proxy;

public class Application {
    public static void main(String[] args) {
        UserManager manager = ProxyFactory.createLoggingProxy(new SimpleUserManager());
        manager.registerUser();
    }
}

このアプローチにより、横断的関心事(ログ出力、トランザクション管理など)を簡潔に実装できます。

タグ: プロキシパターン 静的プロキシ 動的プロキシ Java InvocationHandler

7月19日 16:08 投稿