asp.net Webアプリケーションにキャッスルダイナミックプロキシを実装するように依頼され、asp.netWebアプリケーションのキャッスルダイナミックプロキシについてCastleProjectとCodeProjectから入手したいくつかの記事を読んでいました。
どちらの記事もインターセプターの作成について詳しく説明していますが、インターセプターがクラスで使用される理由がわかりません。適切に動作しているクラスをインターセプトする必要があるのはなぜですか。
あなたのクラスが特定の操作のために3つのことをする必要があるとしましょう:
さらに、セキュリティ、ロギング、またはキャッシングを構成した特定の方法について、クラスが何も知らないと仮定しましょう。これらのものの抽象化に依存する必要があります。
それについて行くにはいくつかの方法があります。1つの方法は、一連のインターフェイスを設定し、コンストラクターインジェクションを使用することです。
public class OrderService : IOrderService
{
private readonly IAuthorizationService auth;
private readonly ILogger logger;
private readonly ICache cache;
public OrderService(IAuthorizationService auth, ILogger logger,
ICache cache)
{
if (auth == null)
throw new ArgumentNullException("auth");
if (logger == null)
throw new ArgumentNullException("logger");
if (cache == null)
throw new ArgumentNullException("cache");
this.auth = auth;
this.logger = logger;
this.cache = cache;
}
public Order GetOrder(int orderID)
{
auth.AssertPermission("GetOrder");
logger.LogInfo("GetOrder:{0}", orderID);
string cacheKey = string.Format("GetOrder-{0}", orderID);
if (cache.Contains(cacheKey))
return (Order)cache[cacheKey];
Order order = LookupOrderInDatabase(orderID);
cache[cacheKey] = order;
return order;
}
}
これはひどいコードではありませんが、私たちが紹介している問題について考えてみてください。
このOrderService
クラスは、3つの依存関係すべてがないと機能できません。できるようにしたい場合は、どこでもnullチェックを使用してコードをペッパーする必要があります。
比較的単純な操作(注文の検索)を実行するために、大量の追加コードを記述しています。
このボイラープレートコードはすべて、すべてのメソッドで繰り返す必要があり、非常に大きく、醜い、バグが発生しやすい実装になります。
保守がはるかに簡単なクラスは次のとおりです。
public class OrderService : IOrderService
{
[Authorize]
[Log]
[Cache("GetOrder-{0}")]
public virtual Order GetOrder(int orderID)
{
return LookupOrderInDatabase(orderID);
}
}
アスペクト指向プログラミングでは、これらの属性はジョインポイントと呼ばれ、その完全なセットはポイントカットと呼ばれます。
実際に依存関係コードを書く代わりに、何度も何度も、このメソッドに対していくつかの追加の操作が実行されることになっているという「ヒント」を残します。
もちろん、これらの属性はいつかコードに変換する必要がありますが、プロキシを作成することで、メインのアプリケーションコードまで延期することができます(メソッドはオーバーライドする必要があるため、作成されているOrderService
ことに注意してください。サービス)、およびメソッドをインターセプトします。GetOrder
virtual
GetOrder
インターセプターの作成は、次のように簡単です。
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (Attribute.IsDefined(invocation.Method, typeof(LogAttribute))
{
Console.Writeline("Method called: "+ invocation.Method.Name);
}
invocation.Proceed();
}
}
プロキシの作成は次のようになります。
var generator = new ProxyGenerator();
var orderService = (IOrderService)generator.CreateClassProxy(typeof(OrderService),
new LoggingInterceptor());
これは繰り返しの少ないコードであるだけでなく、実際の依存関係を完全に削除します。これは、私たちが行ったことを確認するためです。承認システムやキャッシュシステムもまだありませんが、システムは引き続き実行されます。AuthorizeAttribute
別のインターセプターを登録し、またはをチェックすることで、後で承認とキャッシュのロジックを挿入できCacheAttribute
ます。
うまくいけば、これが「理由」を説明します。
補足: KrzysztofKoźmicがコメントしているように、このような動的インターセプターを使用することは、DPの「ベストプラクティス」ではありません。本番コードでは、不要なメソッドに対してインターセプターを実行したくないため、代わりにIInterceptorSelectorを使用してください。
Castle-DynamicProxyを使用する理由は、アスペクト指向プログラミングと呼ばれるもののためです。これにより、コード自体に依存することなく、コードをコードの標準操作フローに挿入できます。
簡単な例は、いつものようにロギングです。エラーが発生したクラスの周囲にDynamicProxyを作成すると、メソッドに入るデータがログに記録され、例外がキャッチされてから、例外がログに記録されます。
インターセプターを使用すると、現在のコードはそれが存在することを認識しません(ソフトウェアがインターフェイスと分離された方法で正しく構築されていると仮定します)。制御の反転を使用してクラスの登録を変更し、代わりにプロキシされたクラスを使用することができます。コード内の他の場所で1行を変更します。次に、バグを解決するときに、プロキシをオフにすることができます。
プロキシのより高度な使用法は、すべての遅延読み込みがプロキシを介して処理されるNHibernateで見ることができます。