In aspectgeoriënteerd programmeren noemen we de logica waarin een aspect verwerkt zit een advice en de plaats waar deze geïnjecteerd wordt een join-point. Er zijn twee manieren te onderscheiden hoe de code en advices met elkaar worden verweven. Dit gebeurt of tijdens het compilatieproces of tijdens de uitvoering van het programma. Het eerste heeft als belangrijkste voordeel dat het snellere applicatiecode oplevert gezien de advices reeds in code verwerkt zijn voordat het wordt uitgevoerd. Tevens zijn er dan geen complexe runtime-interceptietechnieken nodig. De compilatietijd neemt hierdoor wel lichtjes toe. Het tweede biedt een meer flexibele oplossing, omdat het gedrag tijdens runtime aangepast kan worden.
In de volgende voorbeelden maken we gebruik van Postsharp, een commerciëel A.O.P.-framework, geschikt voor .Net dat de aspecten tijdens compilatietijd verwerkt. Postsharp biedt de mogelijkheid om met zogenoemde pointcuts te werken, hierdoor hoeven we zelf geen attributen in de applicatiecode te zetten, maar kunnen we volstaan met een soort van query die de join-points bepaalt. Met andere woorden; we selecteren een aantal injectiepunten die aan een bepaald criterium voldoen. Daarnaast kunnen advices in Postsharp ook validatielogica bevatten, die tijdens compilatietijd wordt getoetst en die desnoods melding aan de ontwikkelaar teruggeeft.
Selectieve trace
Een typisch voorbeeld van aspectgeoriënteerd programmeren toont het injecteren van traceerlogica aan het begin van een methode. Hiervoor dienen we de gewenste methode te decoreren met een attribuut. Deze opzet is vrij statisch van aard, iets wat niet geheel wenselijk is.
Een betere aanpak is om in dit geval een pointcut te gebruiken, zoals wordt getoond in listing 1.
[Serializable]
public class SelectiveTracePointcut : TypeLevelAspect, IAspectProvider
{
public string ClassNameContains { get; set; }
public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
{
var targetType = (Type)targetElement;
if (!string.IsNullOrEmpty(ClassNameContains)
&& !targetType.Name.Contains(ClassNameContains))
{
return new AspectInstance[0];
}
var methods = targetType.GetMethods();
return methods.Select(m => new AspectInstance(m, new SelectiveTraceAspect()));
}
}
Listing 1: Voorbeeld van een pointcut
De methode ProvideAspects wordt tijdens het compilatieproces voor elke type aangeroepen. De methode bevat filterlogica die alleen aspectinstanties creëert voor passende methodes. Dit wordt via reflection bewerkstelligt.
Om het advice op assembly-niveau toe te passen plaatsen we het volgende attribuut in assembly.cs:
[assembly: SelectiveTracePointcut(ClassNameContains = "Foo")]
Listing 2: introduceer advice op assembly-niveau
De regel introduceert het SelectiveTraceAspect-attribuut bij alle methodes, waarbij de klassenaam “Foo” bevat.
Compile time validator
Je code tijdens het compilatieproces kunnen toetsen op bepaalde voorwaarden kan veel mogelijkheden bieden. We kunnen immers op deze manier opmaak- en architectuurregels vastleggen en die op een geautomatiseerde wijze controleren, voordat de compilatie is afgerond.
Laten we ter illustratie een fictieve regel verzinnen die stelt dat alle properties binnen een business entity-klasse publiekelijk schrijfbaar moeten zijn. Een advice die dit kan toetsen zien we terug in listing 3.
public class EntityPropertiesArePubliclySettable : TypeLevelAspect
{
public override bool CompileTimeValidate(Type type)
{
var properties = type.GetProperties();
foreach (var property in properties)
{
bool hasPublicSetter = property.GetSetMethod(false) != null;
if (!hasPublicSetter)
{
Message.Write(SeverityType.Error, "001",
"Property {0}.{1} is not public settable",
type.Name, property.Name);
}
}
return base.CompileTimeValidate(type);
}
}
Listing 3: Een aspect dat applicatiecode toetst tijdens het compilatieproces
Wanneer we een business entity aanmaken die niet voldoet aan deze regel toont Visual Studio een standard foutmelding:
Figuur 2: Validatiefoutmelding
Change tracker
Stel je voor dat we ineens het wilde plan hebben om voor alle business entities bij te houden of ze inhoudelijk zijn aangepast, bijvoorbeeld om deze vervolgens op een efficiënte manier te synchroniseren met een database. We kunnen dit natuurlijk handmatig doen, maar dit proces is foutgevoelig en arbeidsintensief. Een betere oplossing is middels het introduceren van een advice.
Het Postsharp-framework bevat de mogelijkheid om in te haken op het moment dat een property gezet wordt. Het enige wat we nog moeten doen is bij deze punten een vlaggetje te zetten. Om dit tijdens runtime weer eenvoudig uit te lezen introduceren we de functionaliteit via een interface, zoals in listing 4.
public interface ITrackChanges{
bool IsDirty { get; set; }}
Listing 4: interface voor het bijhouden van wijzigingen
Het advice ziet er dan als volgt uit (listing 5):
[IntroduceInterface(typeof(ITrackChanges))]
public class TrackChangesAttribute : InstanceLevelAspect, ITrackChanges
{
[IntroduceMember]
public bool IsDirty { get; set; }
[OnLocationSetValueAdvice,MulticastPointcut(Targets=
MulticastTargets.Property,Attributes=MulticastAttributes.Instance)]
public void OnPropertySet(LocationInterceptionArgs args)
{
args.ProceedSetValue();
IsDirty = true;
}
}
Listing 5: Het programmatisch toevoegen van een interface en inhaken op wijzigingsmomenten
De IntroduceInterface en IntroduceMember-attributen zorgen voor het toevoegen van de ITrackChanges-interface aan een business entity en de benodige members. OnPropertySet is de interceptiemethode die voor alle properties toegepast zal worden. Hier in markeren we de entity als aangepast. Listing 6 toont hoe we dit vervolgens kunnen bepalen.
if (instance is ITrackChanges)
{
bool isDirty = (instance as ITrackChanges).IsDirty;
Listing 6: Het controleren op wijzigingen tijdens runtime
Conclusie
Hopelijk heeft dit artikel laten zien dat je meer met aspectgeoriënteerd programmeren kunt doen dan de voorbeelden waarmee je normaliter geconfronteerd wordt. Aspectgeoriënteerd programmeren inzetten als codegenerator of voor de validatie tijdens compilatie zijn maar enkele voorbeelden waarbij de inzet snel beloond wordt.
Voetnoot: in dit artikel zijn kleine details uitgelicht uit volledige programma’s. Deze programma’s zijn via de website van SDN verkrijgbaar. Postsharp is als zijnde NuGet-pakket aan de voorbeeldprogramma’s toegevoegd. Het gaat hier om een 45-daagse proefversie. Eventueel kan Postsharp ook gedownload worden via de website van de producent (http://www.sharpcrafters.com/). Hier kan je tevens meer informatie vinden.