Pro platformu .NET již v současné době existuje velká skupina open source projektů. V tomto článku si jeden z této velké skupiny představíme a naučíme se, jak jej použít pro realizaci tvorby proxy objektů, což pomocí standardních možností .NET frameworku není zrovna jednoduché.
reklama
Návrhový vzor Proxy
Znalost návrhových vzorů by měla patřit do výbavy každého kvalitního vývojáře. Díky návrhovým vzorům totiž můžeme efektivně implementovat řešení netriviálních problémů různých typů. To vše se zachováním velmi důležitého, vývojářům společného slovníku, který může výrazně usnadnit jejich komunikaci. Užití některých návrhových vzorů je více časté než je tomu u vzorů jiných a je to do značné míry ovlivněno řešenou problémovou doménou.
Jeden z velmi zajímavých návrhových vzorů je návrhový vzor Proxy, nebo chceme-li česky zástupce. Návrhový vzor Proxy umožňuje řídit přístup k určitému objektu, který je takzvaně zastupován – od toho název vzoru. Princip realizace tohoto vzoru spočívá v tom, že proxy objekt i reálný objekt (objekt, který je zastupován proxy objektem) mají společné rozhraní a díky tomuto společnému rozhraní může proxy objekt kdekoli zastupovat reálný objekt, se kterým je asociován. Na obrázku níže můžeme vidět diagram tříd a sekvenční diagram v jazyce UML, zobrazující obecný princip návrhového vzoru Proxy.
Proxy design pattern – UML diagram tříd
Proxy design pattern – UML sekvenční diagram
Návrhový vzor Proxy je používán v několika různých situacích, jako například :
- Přístup ke vzdálenému objektu ( Remote proxy ) – web services, .NET remoting, Java RMI atd.
- Kontrola přístupu k objektu ( Protective proxy ) – např. kontrola oprávnění
- Zajištění dodatečných operací před/po volání skutečného objektu ( Smart proxy )
- „Lazy“ vytváření reálného objektu, neboli vytváření skutečného objektu až v době, kdy je to skutečně potřebné ( Virtual proxy )
- Cachování dat, které je nelehké získat ( Cache proxy )
Dynamic proxy
Třídy proxy lze, a často se tomu tak děje, psát „ručně“ a po té je obsazovat do klientských referencí a tak libovolně upravovat použití metod zastupovaného objektu. Ovšem v případě, že tříd pro jejichž instance budeme chtít vytvářet existuje mnoho, nebo chceme využívat proxy i pro instance klientem vytvořených tříd, je obtížné respektive nemožné psát pro každou třídu jejího proxy. V takovýchto případech je vhodné využít principu označovaného jako dynamický zástupce – dynamic proxy. Při použití dynamic proxy je proxy objekt vygenerován automaticky na základě reálného objektu nebo jeho typu. Takto vytvořený proxy objekt je asociován s nějakou námi poskytovanou instancí třídy, která zařídí potřebné služby spojené s kontrolou přístupu k objektu.
Moderní objektově orientované technologie, za které v současné době můžeme považovat Javu a .NET, disponují různými prostředky pro tvorbu těchto dynamických proxy objektů.
Dynamic proxy a .NET framework
V technologii Java je použití dynamických proxy objektů velmi snadné a jediné co potřebujete je použití služeb třídy java.lang.reflect.Proxy a pro implementaci dodatečných operací k volání reálného objektu implementovat rozhraní java.lang.reflect.InvocationHandler. Vskutku elegantně řešeno. Ne tak jednoduše a elegantně je použití dynamických proxy možné řešit na platformě .NET. Proto, aby bylo možné vytvářet proxy objekty pomocí standardních služeb .NET frameworku je nutné použít možností spojených s .NET remoting. Abych byl přesnější, tak pokud budete chtít používat proxy, budete se muset seznámit s použitím atributu ProxyAttribute a vaše třídy budou muset být odvozeny ze třídy ContextBoundObject, což není vždy žádoucí.
Naštěstí open source komunita vyvíjející pro platformu .NET je poměrně početná i přesto, že za touto platformou stojí firma Microsoft, která jak jistě víme není vždy open-source komunitou milována. Jedním z početných open source projektů, díky kterým není nutné využívat výše zmíněného přístupu pomocí ProxyAttribute a ContextBoundObject, je projekt Castle DynamicProxy, který je součástí velmi zajímavého projektu Castle, jenž se snaží zjednodušit vývoj enterprise aplikací.
Použití Castle DynamicProxy
Castle DynamicProxy umožňuje jednoduché použití dynamických proxy objektů v aplikacích na platformě .NET. Koncept použití je takřka identický se zmiňovaným způsobem použití v Javě. Vytvoření proxy objektu tedy ponecháme na službách Castle DynamicProxy a pro vlastní řízení přístupu k zastupovanému objektu a implementaci dodatečných operací musíme implementovat určité rozhraní třídou, jejíž instanci předáme při vytváření vlastního proxy objektu.
Po výše omílané teorii tedy konečně přistupme k praxi. Pro účel seznámení se s Castle DynamicProxy si realizujeme příklad, ve kterém chceme, aby místo skutečných objektů byly klientovi vraceny instance na proxy objekty, které před zavoláním metody objektu a po zavolání metody objektu zalogují informaci o tom, která metoda na kterém typu je/byla volána. Musíme tedy napsat takzvaný interceptor, což je třída, která implementuje rozhraní Castle.DynamicProxy.IInterceptor.
/// <summary>
/// Interceptor zarizujici logovani pomoci System.Diagnostics.Trace
/// </summary>
public class TracingInterceptor : IInterceptor
{
public object Intercept(IInvocation invocation, params object[] args)
{
//zapsat info o zacatku metody
Trace.WriteLine(string.Format("Begin of method {0} of type {1}",invocation.Method.Name ,invocation.Method.DeclaringType.FullName));
//provest metodu
object methodResult = invocation.Proceed(args);
//zapsat info o konci metody
Trace.WriteLine(string.Format("End of method {0} of type {1}", invocation.Method.Name, invocation.Method.DeclaringType.FullName));
//navraceni navratove hodnoty
return methodResult;
}
}
Rozhraní IInterceptor obsahuje pouze jednu metodu, kterou je metoda Intercept. Tato metoda přijímá formou vstupních parametrů objekt typu IInvocation a volitelně pole objektů, které představuje hodnoty vstupních parametrů metody reálného objektu. V našem interceptoru, který provádí logování potřebujeme zjistit název metody, kterou klient na zastupovaném objektu volá. Toho dosáhneme velice jednoduše, protože na objektu typu IInvocation existuje kromě jiných vlastnost Method, která je typu MethodInfo z reflexe a tudíž můžeme o metodě, zjistit veškeré potřebné údaje. Po té co provedeme potřebné operace před vlastním zavoláním metody, zajistíme zavolání metody na reálném objektu použitím metody Proceed na objektu typu IInvocation.
Tolik k jednoduché implementaci třídy interceptoru pro logování. Do skládanky nám ještě chybí způsob jak dynamicky vytvořit vlastní objekty proxy pomocí služeb Castle DynamicProxy. Pro příklad použijeme jednoduchou třídu, jejíž instance budou představovat business objekty naší aplikace.
/// <summary>
/// Ukazkova business trida.
/// </summary>
public class SomeBusinessObject
{
public virtual string SomeMethod(int argument)
{
return argument.ToString();
}
}
Jistě jste si povšimli, že metoda třídy je označena jako virtuální. To je velmi důležité, protože pokud vytváříme proxy objekty na základě třídy, tak všechny metody, ke kterým budeme chtít řídit přístup musí být virtuální. Důvod si vysvětlíme v pozdější části článku.
Vlastní vytvoření dynamického proxy objektu zařídíme pomocí instance třídy Castle.DynamicProxy.ProxyGenerator. Pokud vytváříme proxy objekt na základě třídy použijeme metodu CreateClassProxy na objektu generátoru proxy. Této metodě předáme typ třídy, pro níž chceme vytvořit proxy objekt a také instanci třídy, která implementuje rozhraní IInterceptor. Vytvořený proxy objekt bude před zavoláním jakékoli metody na reálném objektu delegovat volání na námi specifikovaný objekt interceptoru.
//vytvoreni proxy generatoru
ProxyGenerator proxyGen = new ProxyGenerator();
//vytvoreni proxy tridy pomoci proxy generatoru
SomeBusinessObject busObj = (SomeBusinessObject)proxyGen.CreateClassProxy(
typeof(SomeBusinessObject), new TracingInterceptor());
//metoda je volana na proxy tride
string result = busObj.SomeMethod(123);
Nyní jsme si ukázali způsob, jak vytvořit objekt proxy zastupující třídu. Avšak obvyklejší a dle mého názoru také lepší způsob je vytvářet objekty proxy, které zastupují nikoliv přímo třídu, ale nějaké rozhraní. Takovýmto přístupem nejenže vytváříme nízkou provázanost mezi typy, což je obecně výhodné, ale také není nutné deklarovat metody a vlastnosti třídy jako virtuální, což v předchozím případě nutné bylo. Necháme tedy naši business třídu implementovat rozhraní, které předepisuje její metodu.
/// <summary>
/// Pokusne rozhrani business objektu.
/// </summary>
public interface ISomeBusinessObject
{
string SomeMethod(int argument);
}
public class SomeBusinessObject : ISomeBusinessObject
{
…
}
K tomu, abycho vytvořili proxy objekt, který zastupuje dané rozhraní použijeme opět objekt typu ProxyGenerator ,avšak nyní použijeme metodu CreateProxy. Této metodě nejenže předáme typ rozhraní, které má daný proxy objekt implementovat a objekt interceptoru, ale také instanci třídy, představující zastupovaný objekt. Na instanci, která představuje zastupovaný objekt, budou po provedení dodatečných operací implementovaných v interceptoru, volány metody zastupovaného rozhraní. Rozhraní, které je zastupováno, nemusí být jediné. Díky přetížené verzi metody CreateProxy můžeme vytvořit proxy objekt, jenž implementuje rozhraní hned několik.
private static ISomeBusinessObject CreateProxy(SomeBusinessObject obj)
{
//vytvoreni proxy generatoru
ProxyGenerator proxyGen = new ProxyGenerator();
//vytovreni proxy, ktera simuluje rozhrani na zaklade instance objektu
ISomeBusinessObject busObj = (ISomeBusinessObject)proxyGen.CreateProxy(
typeof(ISomeBusinessObject), new TracingInterceptor(), obj);
return busObj;
}
Jak to funguje?
Castle DynamicProxy používá služeb Reflection.Emit k dynamickému generování IL kódu. Emitovaný IL kód je odlišný v závislosti na tom, jaký typ proxy objektu je vytvářen. V případě vytváření proxy objektu, zastupujícího instanci třídy je dynamicky vytvořen typ, který je z dané třídy odvozen. Tento odvozený typ překryje všechny metody zastupovaného typu a při volání těchto metod dané volání deleguje na objekt interceptoru a následně (po zavolání invocation.Proceed) na bázovou, tedy původní, implementaci metody. Z tohoto důvodu musí být všechny metody a vlastnosti typu, který bude zastupován, označeny jako virtuální, protože jinak nebudou překryté metody na dynamicky vytvořeném odvozeném typu polymorfně volány.
V případě zastupování rozhraní je emitován IL kód, který představuje typ úplně nový. Tento generovaný typ implementuje všechna potřebná rozhraní, takže na ně může být proxy objekt bezpečně přetypován. Při každém použití metody či vlastnosti na proxy objektu dojde k delegaci volání na objekt interceptoru a po zavolání metody Proceed na objektu IInvocation je volání delegováno na instanci, která byla předána jako cíl volání.
Dobrou zprávou také je, že pro vlastní delegaci volání v emitovaných typech je použito standardních delegátů, tudíž je dosaženo dobrého výkonu, který je vyšší, než kdyby se pro volání metod použila reflexe.
Na závěr se podívejme na UML sekvenční diagram, který zobrazuje postup volání v případě použití dynamické proxy pomocí Castle DynamicProxy.
Závěr
Castle DynamicProxy je velmi zajímavý projekt, který umožňuje implementovat tvorbu proxy objektů jednodušší a elegantnější cestou než je tomu pomocí standardních možností .NET frameworku. Díky tomu, že umožňuje vytvořit proxy objekt jak na základě třídy, tak na základě jednoho či více rozhraní a delegace volání metod je implementována velmi efektivně, je tento open source projekt použitelný takřka ve všech situacích, které potřebují použití proxy objektů.