710 likes | 830 Views
Untangle Your Code with Aspect-Oriented Programming (AOP): Part One. Frank Sauer Associate, TRC. Agenda. Limitations of object-oriented programming What is Aspect-Oriented Programming? AspectJ Patterns as reusable components Transactional Java Objects. Clay or Gold?.
E N D
Untangle Your Code with Aspect-Oriented Programming (AOP): Part One Frank Sauer Associate, TRC
Agenda • Limitations of object-oriented programming • What is Aspect-Oriented Programming? • AspectJ • Patterns as reusable components • Transactional Java Objects
Clay or Gold? “At present software is like Clay: it is soft and malleable early in its lifetime, but eventually it hardens and becomes brittle. At that point, it is possible to add new bumps to it, but its fundamental shape is fixed, and it can no longer adapt adequately to the constant evolutionary pressures of our ever- changing world.”
Clay or Gold? “… produce software more like Gold – malleable and flexible for life…”(morphogenic software)Harold Ossher and Perit Tarr, “Using multidimensional separation of concerns to (re)shape evolving software”, Communications of the ACM, October 2001
Limitations of OOP • Object-oriented programming has matured to the point we are now beginning to see its limitations. • Many requirements do not neatly decompose into behaviors centered on a single locus. • The tyranny of the dominant decomposition: Every OO decomposition results in cross-cutting concerns. (Tarr 99)
Cross-Cutting Concerns Visualized: Logging in Tomcat (From AspectJ tutorial)
Example: Tracing class TraceSupport { static int TRACELEVEL = 0; static protected PrintStream stream = null; static protected int callDepth = -1; static void init(PrintStream _s) {stream=_s;} static void traceEntry(String str) { if (TRACELEVEL == 0) return; callDepth++; printEntering(str); } static void traceExit(String str) { if (TRACELEVEL == 0) return; callDepth--; printExiting(str); } } TraceSupport class Point { void set(int x, int y) { TraceSupport.traceEntry(“Point.set”); _x = x; _y = y; TraceSupport.traceExit(“Point.set”); } } Consistent trace form but using it is cross-cutting...
Other Cross-Cutting Concerns • Systemic • security (authorization and auditing) • logging and debugging • synchronization and transactions • persistence and many more • Functional • business rules and constraints • traversal of complex object graphs • accounting mechanisms (timing and billing)
The Cost of Tangled Code • Redundant code • same or similar fragment of code in many places • Difficult to reason about • non-explicit structure • the big picture of the tangling isn’t clear • Difficult to change • have to find all the code involved • and be sure to change it consistently • and be sure not to break it by accident
Aspect-Oriented Programming • AOP attempts to realize cross-cutting concerns as first-class elements and eject them from the object structure into a new, previously inaccessible, dimension of software development. • AOP encapsulates behavior that OOP would scatter throughout the code by extracting cross-cutting concerns into a single textual structure called an aspect.
AOP: Opening a New Dimension in Software Development Object decomposition is flatland A cross-cutting concern is scattered because it is realized in the wrong dimension! concerns
AOP: Opening a New Dimension in Software Development Object decomposition is flatland A cross-cutting concern is scattered because it is realized in the wrong dimension! concerns
AOP: Opening a New Dimension in Software Development Object decomposition is flatland Aspects are orthogonal to the primary decomposition aspect concerns
Expected Benefits of AOP • Good modularity, even for crosscutting concerns • less tangled code • more natural code • shorter code • easier maintenance and evolution • easier to reason about, debug, change • more reusable • library aspects • plug and play aspects when appropriate
Impact of AOP • AOP done right will not only clean up code, but will impact the entire software development process as well. In fact, MIT Technology Review lists AOP as one of the top 10 emerging technologies that will change the world and names it in one breath with brain-machine interfaces, flexible transistors, and microphotonics. –(MIT Technology Review, January 2001)
The Four Ingredients of AOP 1. Joinpoint model: common frame of reference to define the structure of cross-cutting concerns 2. Means to identify joinpoints 3. Means to influence the behavior at joinpoints 4. Means to weave everything together into a functional system
AspectJ • Java language extension for AOP developed at Xerox PARC out of work on reflection and metaobject protocols • ajc compiler compiles Java and weaves aspects into classes (ingredient 4) • open source! • http://www.aspectj.org
AspectJ Terminology • Joinpoint (Ingredient 1):Any well-defined point of execution in a Java program such as method calls, field accesses, and object construction An Object method call join point Field access join point int foo dispatch get set method execution join point
AspectJ Terminology • Pointcut (Ingredient 2):Predicate on joinpoints selecting a collection of joinpoints. Example:call(public * com.trcinc..*.*.do*(..));This pointcut matches any method call to public methods in any class in any com.trcinc. subpackage whose name starts with ‘do’ with any number or type of arguments and any return type
More on Pointcuts • Pointcuts can be composed as boolean expressions with &&, || and ! • Pointcuts can be named • Pointcuts can have arguments (next slide) Example named pointcut:pointcut move():call(Point.setX(..)) ||call(Point.setY(..)) ||call(Line.setP1(..)) ||call(Line.setP2(..));
Pointcut Arguments Example:pointcut move(Object mover, Shape shape): this(mover) && target(shape) && (call(Point.setX(..)) ||call(Point.setY(..)) ||call(Line.setP1(..)) ||call(Line.setP2(..)) );this(mover) binds mover to the object making the call. target(shape) binds shape to the object being moved.
Pointcuts Using Other Pointcuts Dynamic pointcuts can further constrain other pointcuts:pointcut toplevelMove(): move() && !cflowbelow(move())Read as: A toplevel move is any move unless we are already in the process of moving.
AspectJ Terminology • Advice :Piece of code that attaches to a pointcut and thus injects behavior at all joinpoints selected by that pointcut (Ingredient 3). This code executes before, after, or around a pointcut.
shape Example After Advice • Update a display after any move of a shape:after(): move() { display.update();} • or with parameters: • after(Object mover, Shape shape): move(mover, shape){ display.update(mover, shape);} after advice runs“on the way back out” move() mover
After Advice Variations • “after returning” only runs when joinpoint returned “normally” • “after throwing” runs when joinpoint threw an exception • You have access to the return value:pointcut factoryMethod(Client c): this( c ) && call(Shape Shape.make*(..));after(Client c) returning(Shape s): factoryMethod(c) { s.setColor(c.getColorFor(s));}
Example Before Advice • Keep track of changes in transactional objects:pointcut dirty(Persistent o): this(o) && set(* *.*);before(Persistent o): dirty(o) { String name = thisJoinPoint.getSignature().getName(); Transaction.getCurrent().setModified(o,name);} New pseudo variable (like this)that always has information aboutthe current joinpoint
Example Around Advice • An around advice executes instead of the joinpoint and has control over the continuation of the original code. • For example, enforce constraints on an argument:void around(Point p, int newX):call(void Point.setX(int)) && target(p) && args(newX) {proceed(p, clip(newX, MIN_X, MAX_X));}
AspectJ Terminology • aspect :A modular unit of cross-cutting behavior. Very much like a class, can have methods, fields, initializers, pointcuts and advices. They can be abstract, inherit from classes and abstract aspects and implement interfaces.
The Hello-World of Aspects public aspect SimpleTracing { // trace all public method calls in com.trcinc.* private pointcut trace(): call(* com.trcinc..*.*.*(..));before(): trace() { String name = thisJoinPoint.getSignature().getName(); TraceSupport.traceEntry(name); }after(): returning: trace() { String name = thisJoinPoint.getSignature().getName(); TraceSupport.traceExit(name); }after(): throwing(Throwable t): trace() { TraceSupport.logException(t); } }
Abstract Aspects for Reusability public abstract aspect Tracing { protected abstract pointcut trace(); // advice same as before}public aspect MyTracing extends Tracing { // just define which calls to trace protected pointcut trace(): call(* com.trcinc..*.*.*(..));} Plug’n Debug...
Example: Optimization public class Fibonacci { // naive implementation of fibonacci, resulting in a lot // of redundant calculations of the same values. public static int fib(int n) { if ( n < 2) { System.err.println(n + "."); return 1; } else { System.err.print(n + ","); return fib(n-1) + fib(n-2); } } public static void main(String[] args) { int f = fib(10); System.err.println("Fib(10) = " + f); } } public aspect Memoization { // NOT a typo... private HashMap cache = new HashMap(); private pointcut fibs(int in): execution(int Fibonacci.fib(int)) && args(in); // calculate only if not already in cache! int around(int in): fibs(in) { Integer result = (Integer)cache.get(new Integer(in)); if (result == null) { int f = proceed(in); // not found, calculate! cache.put(new Integer(in), new Integer(f)); return f; } else return result.intValue(); // found, done! } }
caller1 Service caller2 worker2 worker1 worker3 Example: Context Passing • workers need to know the caller: • transactions and/or security • charge backs • to customize result • example taken from AspectJ • tutorial
caller1 Service caller2 worker2 worker1 worker3 Example: Context Passing Define these pointcuts
Example: Context Passing pointcut invocations(Caller c): this(c) && call(void Service.doService(String));
Example: Context Passing pointcut invocations(Caller c): this(c) && call(void Service.doService(String)); pointcut workPoints(Worker w): target(w) && call(void Worker.doTask(Task));
Example: Context Passing pointcut invocations(Caller c): this(c) && call(void Service.doService(String)); pointcut workPoints(Worker w): target(w) && call(void Worker.doTask(Task)); pointcut perCallerWork(Caller c, Worker w): cflow(invocations(c)) && workPoints(w);
Example: Context Passing pointcut invocations(Caller c): this(c) && call(void Service.doService(String)); pointcut workPoints(Worker w): target(w) && call(void Worker.doTask(Task)); pointcut perCallerWork(Caller c, Worker w): cflow(invocations(c)) && workPoints(w); before (Caller c, Worker w): perCallerWork(c, w) { w.checkAllowed(c); } after (Caller c, Worker w) returning: perCallerWork(c, w) { c.chargeForWork(w); }
JoinPoint Example: Context Passing We’ve created a wormhole !
ptc1 cflow = cflow(ptc1)&& ptc2 ptc2 AspectJ Wormhole Theory
Summary of Dynamic Features of AspectJ • Pointcuts (composable with && , || , ! ): • call(signature), execution(signature) • get(fields), set(fields) • handler(exception) • initialization, staticinitialization • cflow(ptc), cflowbelow(ptc) • this(typePattern or id), target(typePattern or Id) • within(TypePattern), withincode(Signature) • advice: • before, after around • Some powerful Idioms: • wormhole: cflow(ptc1) && ptc2 • non-reentrant: ptc && !cflowbelow(ptc)
Untangle Your Code with Aspect-Oriented Programming (AOP): Part Two More AspectJ, Design Patterns and Transactional Objects
AspectJ Terminology • Introduction (aka “Open Classes”):An aspect can introduce new structure and behavior on existing classes and interfaces. These are called introductions. • Simple example:declare parents: domain.* implements Serializable;write this in an aspect and all classes in the domain package automagically become serializable objects...
Example Introduction aspect CloneablePoint { declare parents: Point implements Cloneable; public Object Point.clone() throws CloneNotSupportedException { return super.clone(); } } By introducing the clone method in the Point class itself , we can now clone Point objects. ClonablePoint is not a class. It cannot be instantiated.
More Introductions • AspectJ lets you introduce fields and concrete methods (with bodies!) on interfaces! Example: aspect TransactionalObjects { interface Persistent {} private String Persistent.uuid = UUID.getUUID(); public String Persistent.getUUID() { return uuid; }}
Aspect Instances • By default aspects are singletons • Can be modified using keywords: • perthis(pointcut)create an instance per object that can ever be “this” at a joinpoint • pertarget(pointcut)create an instance per object that can ever be the target of a joinpoint • percflow(pointcut)create one instance for each flow of control of the join points picked out by pointcut
Example perthis(pointcut) Public aspect ObserverPattern perthis(observed()) { // objects being observed abstract pointcut observed(); // this aspect state is the reason each observed // object must have its own aspect instance. Vector observers = new Vector(); public static void addObserver(Observed m, Observer v) { ObserverPattern.aspectOf(m).observers.add(v); } } Generated by ajc for concrete perthis aspects
Design Patterns as Reusable Components Using Aspects • Abstract aspects can be used to encapsulate the inter-object protocols of behavioral patterns in a single place. • The abstract pattern works with interfaces and/or abstract pointcuts • To re-use a pattern, subclass it and introduce the interfaces on your classes or make the pointcuts concrete.
Example: JavaBean Observer • Goals: • encapsulate relationships between observer and observed in a single place • automatically inject these relationships into existing classes • classes are unaware of the pattern • pattern is unaware of the classes • use the Javabean conventions, for example, getProp and setProp methods and PropertyChangeEvents
Example: JavaBean Observer Pattern works with any implementation of Observer public abstract aspect ObserverPattern { public interface Observer extends PropertyChangeListener { } public interface Observed { public void addPropertyChangeListener(PropertyChangeListener l); public void removePropertyChangeListener(PropertyChangeListener l); }; PropertyChangeSupport Observed.pss; public void Observed.addPropertyChangeListener(PropertyChangeListener l) { if (pss == null) pss = new PropertyChangeSupport(this); pss.addPropertyChangeListener(l); } public void Observed.removePropertyChangeListener(PropertyChangeListener l) { if (pss == null) return; else pss.remove (l); } Pattern works with any implementation of Observed Every Observed needs a PropertyChangeSupport object. Code to add property change listeners is the same for every Observed, so it can be implemented here. Code to remove property change listeners is the same for every Observed, so it can be implemented here.
Example: JavaBean Observer private Object getOldValue(Object o, String property) { try { Method m = o.getClass().getMethod("get" + property, new Class[]{}); return m.invoke(o, new Object[]{}); } catch (Exception x) { return null; } } void around(Observed o, Object val): target(o) && call(* *.set*(..)) && args(val) { Object old = null; String name = thisJoinPoint.getSignature().getName().substring(3); if (o.pss != null) old = getOldValue(o, name); proceed(o, val); if (o.pss != null) { String prop = name.substring(0,1).toLowerCase() + name.substring(1); o.pss.firePropertyChange(prop, old, val); } } } When calling any set method on an Observed with argument ‘val’, get the old value, then call the method, then fire an event. Helper method to access the old value of a property to set in the PropertyChangeEvent