Aspect Oriented Programming An Overview of AspectJ What is AOP? • Not strictly OOP • Not strictly POP • Captures “aspects” that “crosscut” both – More on this later A simple image filter (v1) (defun or! (a b) (let ((result (new-image))) (loop for i from 1 to width do (loop for j from 1 to height do (set-pixel result i j (or (get-pixel a i j) (get-pixel b i j))))) result)) • Other logical operations similar • Shift image up, down similar A simple image filter (v2) (defun remove! (a b) (and! a (not! b))) Difference of two images (defun top-edge! (a) (remove! a (down! a))) Pixels at top edge of region (defun bottom-edge! (a) (remove! a (up! a))) Pixels at bottom edge of region (defun horizontal-edge! (a) (or! (top-edge! a) (bottom-edge! a))) Horizontal edge pixels • Easy to understand, but not efficient A simple image filter (v2) (defun horizontal-edge-improved! (a) (let ((result (new-image)) (a-up (up! a)) (a-down (down! a))) (loop for i from 1 to width do (loop for j from 1 to height do (set-pixel result i j (or (and (get-pixel a i j) (not (get-pixel a-up i j))) (and (get-pixel a i j) (not (get-pixel a-down i j))))))) result)) • Fewer image copies • Reasoning less obvious A simple image filter (v2) Lessons learned • Efficiency is good! • Understanding is good! • Can’t we have both? good modularity XML parsing • XML parsing in org.apache.tomcat – red shows relevant lines of code – nicely fits in one box good modularity URL pattern matching • URL pattern matching in org.apache.tomcat – red shows relevant lines of code – nicely fits in two boxes (using inheritance) problems like… logging is not modularized • logging in org.apache.tomcat – red shows lines of code that handle logging – not in just one place – not even in a small number of places AspectJ • Aspect Oriented environment for Java • Developed at Xerox PARC a simple figure editor factory methods Display * FigureElement Figure makePoint(..) makeLine(..) Point getX() getY() setX(int) setY(int) move(int, int) move(int, int) 2 Line getP1() getP2() setP1(Point) setP2(Point) move(int, int) operations that move elements a simple figure editor class Line implements FigureElement{ private Point p1, p2; Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; } void moveBy(int dx, int dy) { ... } } class Point implements FigureElement { private int x = 0, y = 0; int getX() { return x; } int getY() { return y; } void setX(int x) { this.x = x; } void setY(int y) { this.y = y; } void moveBy(int dx, int dy) { ... } } join points key points in dynamic call graph imagine l.move(2, 2) a Line dispatch a Point a method execution returning or throwing a method call returning or throwing a method execution returning or throwing dispatch join point terminology a Line dispatch method execution join points • several kinds of join points – – – – – method & constructor call method & constructor execution field get & set exception handler execution static & dynamic initialization method call join points primitive pointcuts “a means of identifying join points” a pointcut is a kind of predicate on join points that: – can match or not match any given join point and – optionally, can pull out some of the values at that join point call(void Line.setP1(Point)) matches if the join point is a method call with this signature pointcut composition pointcuts compose like predicates, using &&, || and ! a “void Line.setP1(Point)” call or call(void Line.setP1(Point)) || call(void Line.setP2(Point)); a “void Line.setP2(Point)” call whenever a Line receives a “void setP1(Point)” or “void setP2(Point)” method call user-defined pointcuts defined using the pointcut construct user-defined (aka named) pointcuts – can be used in the same way as primitive pointcuts pointcut move(): call(void Line.setP1(Point)) || call(void Line.setP2(Point)); after advice action to take after computation under join points after advice runs “on the way back out” a Line pointcut move(): call(void Line.setP1(Point)) || call(void Line.setP2(Point)); after() returning: move() { <code here runs after each move> } a simple aspect DisplayUpdating v1 an aspect defines a special class that can crosscut other classes aspect DisplayUpdating { pointcut move(): call(void Line.setP1(Point)) || call(void Line.setP2(Point)); after() returning: move() { Display.update(); } } box means complete running code without AspectJ DisplayUpdating v1 class Line { private Point p1, p2; Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; Display.update(); } void setP2(Point p2) { this.p2 = p2; Display.update(); } } • what you would expect – update calls are tangled through the code – “what is going on” is less explicit pointcuts can cut across multiple classes pointcut move(): call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Point.setX(int)) || call(void Point.setY(int)); pointcuts can use interface signatures pointcut move(): call(void FigureElement.moveBy(int, int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Point.setX(int)) || call(void Point.setY(int)); a multi-class aspect DisplayUpdating v2 aspect DisplayUpdating { pointcut move(): call(void FigureElement.moveBy(int, int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Point.setX(int)) || call(void Point.setY(int)); after() returning: move() { Display.update(); } } using values at join points demonstrate first, explain in detail afterwards • pointcut can explicitly expose certain values • advice can use value parameter mechanism being used pointcut move(FigureElement figElt): target(figElt) && (call(void FigureElement.moveBy(int, int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Point.setX(int)) || call(void Point.setY(int))); after(FigureElement fe) returning: move(fe) { <fe is bound to the figure element> } explaining parameters… of user-defined pointcut designator • variable is bound by user-defined pointcut declaration – pointcut supplies value for variable – value is available to all users of user-defined pointcut pointcut parameters pointcut move(Line l): target(l) && (call(void Line.setP1(Point)) || call(void Line.setP2(Point))); typed variable in place of type name after(Line line): move(line) { <line is bound to the line> } explaining parameters… of advice • variable is bound by advice declaration – pointcut supplies value for variable – value is available in advice body pointcut move(Line l): target(l) && (call(void Line.setP1(Point)) || call(void Line.setP2(Point))); advice parameters after(Line line): move(line) { <line is bound to the line> } typed variable in place of type name context & multiple classes DisplayUpdating v3 aspect DisplayUpdating { pointcut move(FigureElement figElt): target(figElt) && (call(void FigureElement.moveBy(int, int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Point.setX(int)) || call(void Point.setY(int))); after(FigureElement fe): move(fe) { Display.update(fe); } } without AspectJ class Line { private Point p1, p2; Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; } } class Point { private int x = 0, y = 0; int getX() { return x; } int getY() { return y; } void setX(int x) { this.x = x; } void setY(int y) { this.y = y; } } without AspectJ DisplayUpdating v1 class Line { private Point p1, p2; Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; Display.update(); } void setP2(Point p2) { this.p2 = p2; Display.update(); } } class Point { private int x = 0, y = 0; int getX() { return x; } int getY() { return y; } void setX(int x) { this.x = x; } void setY(int y) { this.y = y; } } without AspectJ DisplayUpdating v2 class Line { private Point p1, p2; Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; Display.update(); } void setP2(Point p2) { this.p2 = p2; Display.update(); } } class Point { private int x = 0, y = 0; int getX() { return x; } int getY() { return y; } void setX(int x) { this.x = x; Display.update(); } void setY(int y) { this.y = y; Display.update(); } } without AspectJ DisplayUpdating v3 class Line { private Point p1, p2; Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; Display.update(this); } void setP2(Point p2) { this.p2 = p2; Display.update(this); } } class Point { private int x = 0, y = 0; int getX() { return x; } int getY() { return y; } void setX(int x) { this.x = x; Display.update(this); } void setY(int y) { this.y = y; Display.update(this); } } • no locus of “display updating” – evolution is cumbersome – changes in all classes – have to track & change all callers with AspectJ class Line { private Point p1, p2; Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; } } class Point { private int x = 0, y = 0; int getX() { return x; } int getY() { return y; } void setX(int x) { this.x = x; } void setY(int y) { this.y = y; } } with AspectJ DisplayUpdating v1 class Line { aspect DisplayUpdating { private Point p1, p2; pointcut move(): call(void Line.setP1(Point)) || call(void Line.setP2(Point)); Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; } } class Point { private int x = 0, y = 0; int getX() { return x; } int getY() { return y; } void setX(int x) { this.x = x; } void setY(int y) { this.y = y; } } after() returning: move() { Display.update(); } } with AspectJ DisplayUpdating v2 class Line { aspect DisplayUpdating { private Point p1, p2; pointcut move(): call(void FigureElement.moveBy(int, int) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Point.setX(int)) || call(void Point.setY(int)); Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; } } } class Point { private int x = 0, y = 0; int getX() { return x; } int getY() { return y; } void setX(int x) { this.x = x; } void setY(int y) { this.y = y; } } after() returning: move() { Display.update(); } with AspectJ DisplayUpdating v3 class Line { aspect DisplayUpdating { private Point p1, p2; pointcut move(FigureElement figElt): target(figElt) && (call(void FigureElement.moveBy(int, int) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Point.setX(int)) || call(void Point.setY(int))); Point getP1() { return p1; } Point getP2() { return p2; } void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; } after(FigureElement fe) returning: move(fe) { Display.Update(fe); } } class Point { } private int x = 0, y = 0; int getX() { return x; } int getY() { return y; } void setX(int x) { this.x = x; } void setY(int y) { this.y = y; } } • clear display updating module – all changes in single aspect – evolution is modular aspects crosscut classes aspect modularity cuts across class modularity Display * FigureElement Figure makePoint(..) makeLine(..) Point getX() getY() setX(int) setY(int) moveBy(int, int) moveBy(int, int) 2 Line getP1() getP2() setP1(Point) setP2(Point) moveBy(int, int) DisplayUpdating advice is additional action to take at join points • before before proceeding at join point • after returning a value to join point • after throwing a throwable to join point • after returning to join point either way • around on arrival at join point gets explicit control over when&if program proceeds contract checking simple example of before/after/around • pre-conditions – check whether parameter is valid • post-conditions – check whether values were set • condition enforcement – force parameters to be valid pre-condition using before advice aspect PointBoundsPreCondition { before(int newX): call(void Point.setX(int)) && args(newX) { assert(newX >= MIN_X); what follows the ‘:’ is assert(newX <= MAX_X); always a pointcut – } primitive or user-defined before(int newY): call(void Point.setY(int)) && args(newY) { assert(newY >= MIN_Y); assert(newY <= MAX_Y); } private void assert(boolean v) { if ( !v ) throw new RuntimeException(); } } post-condition using after advice aspect PointBoundsPostCondition { after(Point p, int newX): call(void Point.setX(int)) && target(p) && args(newX) { assert(p.getX() == newX); } after(Point p, int newY): call(void Point.setY(int)) && target(p) && args(newY) { assert(p.getY() == newY); } private void assert(boolean v) { if ( !v ) throw new RuntimeException(); } } condition enforcement using around advice aspect PointBoundsEnforcement { void around(Point p, int newX): call(void Point.setX(int)) && target(p) && args(newX) { proceed(p, clip(newX, MIN_X, MAX_X)); } void around(Point p, int newY): call(void Point.setY(int)) && target(p) && args(newY) { proceed(p, clip(newY, MIN_Y, MAX_Y)); } private int clip(int val, int min, int max) { return Math.max(min, Math.min(max, val)); } } wildcarding in pointcuts target(Point) target(graphics.geom.Point) target(graphics.geom.*) target(graphics..*) “*” is wild card “..” is multi-part wild card any type in graphics.geom any type in any sub-package of graphics call(void Point.setX(int)) call(public * Point.*(..)) call(public * *(..)) any public method on Point any public method on any type call(void call(void call(void call(void any getter Point.getX()) Point.getY()) Point.get*()) get*()) call(Point.new(int, int)) call(new(..)) any constructor role types and reusable abstract aspect Observing { protected interface Subject { } protected interface Observer { } public void addObserver(Subject s, Observer o) { ... } public void removeObserver(Subject s, Observer o) { ... } abstract pointcut changes(Subject s); after(Subject s): changes(s) { Iterator iter = getObservers(s).iterator(); while ( iter.hasNext() ) { notifyObserver(s, ((Observer)iter.next())); } } abstract void notifyObserver(Subject s, Observer o); } this is the concrete reuse DisplayUpdating v4 aspect DisplayUpdating extends Observing { declare parents: FigureElement implements Subject; declare parents: Display implements Observer; pointcut changes(Subject s): call(void FigureElement.moveBy(int, int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Point.setX(int)) || call(void Point.setY(int)); void notifyObserver(Subject s, Observer o) { ((Display)o).needsRepaint(); } } other primitive pointcuts cflow(pointcut designator) all join points within the dynamic control flow of any join point in pointcut designator cflowbelow(pointcut designator) all join points within the dynamic control flow below any join point in pointcut designator example 3 counting bytes interface OutputStream { public void write(byte b); public void write(byte[] b); } /** * This SIMPLE aspect keeps a global count of all * the bytes ever written to an OutputStream. */ aspect ByteCounting { int count = 0; int getCount() { return count; } // // // what goes here? // // // } counting bytes v1 a first attempt aspect ByteCounting { int count = 0; int getCount() { return count; } after() returning: call(void OutputStream.write(byte)) { count = count + 1; } after(byte[] bytes) returning: call(void OutputStream.write(bytes)) { count = count + bytes.length; } } counting bytes some stream implementations class SimpleOutputStream implements OutputStream { public void write(byte b) { … } public void write(byte[] b) { for (int i = 0; i < b.length; i++) write(b[i]); } } class OneOutputStream implements OutputStream { public void write(byte b) { … } public void write(byte[] b) { … } } counting bytes another implementation class OtherOutputStream implements OutputStream { public void write(byte b) { byte[] bs = new byte[1] { b }; write(bs); } public void write(byte[] b) { … } } counting bytes v2 using cflowbelow for more robust counting aspect ByteCounting { int count = 0; int getCount() { return count; } pointcut write(): call(void OutputStream.write(byte)) || call(void OutputStream.write(byte[])); pointcut writeCflow(): cflowbelow(write()); after() returning: !writeCflow() && call(void OutputStream .write(byte)) { count++; } after(byte[] bytes) returning: !writeCflow() && call(void OutputStream .write(bytes)) { count = count + bytes.length; } } AspectJ technology • AspectJ is a small extension to Java™ – valid Java programs are also valid AspectJ programs • AspectJ has its own compiler, ajc – ajc runs on Java 2 platform (Java 1.2 – 1.4) – ajc produces Java platform compatible .class files • AspectJ tools support – IDE extensions: Emacs, JBuilder 5, Forte4J – ajdoc to parallel javadoc – debugger: command line, GUI, & IDE • license – compiler, runtime and tools are free for any use – compiler and tools are Open Source when are aspects appropriate? • is there a concern that: – crosscuts the structure of several objects or operations – is beneficial to separate out … crosscutting • a design concern that involves several objects or operations • implemented without AOP would lead to distant places in the code that – do the same thing • e.g. traceEntry(“Point.set”) • try grep to find these [Griswold] – do a coordinated single thing • e.g. timing, observer pattern • harder to find these … beneficial to separate out • does it improve the code in real ways? – separation of concerns • e.g . think about service without timing – clarifies interactions, reduces tangling • e.g. all the traceEntry are really the same – easier to modify / extend • e.g. change the implementation of tracing • e.g. abstract aspect re-use Aspect Oriented Programming Integrating Independent Components with On-Demand Remodularization A generic tree display interface TreeModel { Object getRoot(); Object[] getChildren(Object node); String getStringValue(Object node, boolean selected, boolean expanded, boolean leaf, int row, boolean focus); } } interface TreeGUIControl { display(); } class SimpleTreeDisplay implements TreeGUIControl { TreeModel tm; display() { Object root = tm.getRoot(); ... tm.getChildren(root) ... ... tm.getStringValue(...); ... } } A generic tree display class Expression { Expression[] subExpressions; String description() { ... } Expression[] getSubExpressions() { ... } } class ExpressionDisplay implements TreeModel { ExpressionDisplay(Expression r) { root = r; } Expression root; Object getRoot() { return root; } Object[] getChildren(Object node) { return ((Expression) node).getSubExpressions(); } String getStringValue(Object node, boolean selected, boolean expanded, boolean leaf, int row, boolean focus){ String s = ((Expression) node).description(); if (focus) s ="<"+s+">"; return s; } } Problems with ExpressionDisplay • Use of Object – What can we reason about structure, dataflow? • Lose type safety – Must rely on runtime checks Problems with ExpressionDisplay class Expression { Expression[] subExpressions; String description() { ... } Expression[] getSubExpressions() { ... } } class ExpressionDisplay implements TreeModel { ExpressionDisplay(Expression r) { root = r; } Expression root; Object getRoot() { return root; } Object[] getChildren(Object node) { return ((Expression) node).getSubExpressions(); } String getStringValue(Object node, boolean selected, boolean expanded, boolean leaf, int row, boolean focus){ String s = ((Expression) node).description(); if (focus) s ="<"+s+">"; return s; } } Collaboration Interfaces • Dual contracts – Provided: analogous to normal component exports – Expected: “filled-in” by client for context • Developer of component writes Provided parts – Assumes Expected parts to be filled in later • Client must write Expected parts to complete component A generic tree display (CI) interface TreeDisplay { provided void display(); expected TreeNode getRoot(); interface TreeNode { expected TreeNode[] getChildren(); expected String getStringValue(); provided display(); provided boolean isSelected(), provided boolean isExpanded(); provided boolean isLeaf(); provided int row(); provided boolean hasFocus(); } } A generic tree display (CI) interface TreeDisplay { provided void display(); expected TreeNode getRoot(); interface TreeNode { expected TreeNode[] getChildren(); expected String getStringValue(); provided display(); provided boolean isSelected(), provided boolean isExpanded(); provided boolean isLeaf(); provided int row(); provided boolean hasFocus(); } } Implementer writes these A generic tree display (CI) interface TreeDisplay { provided void display(); expected TreeNode getRoot(); interface TreeNode { expected TreeNode[] getChildren(); expected String getStringValue(); provided display(); provided boolean isSelected(), provided boolean isExpanded(); provided boolean isLeaf(); provided int row(); provided boolean hasFocus(); } } Binder writes these Implementing the interface class SimpleTreeDisplay implements TreeDisplay { void onSelectionChange(TreeNode n, boolean selected) { n.setSelected(true); } void display() { getRoot().display(); } class TreeNode { boolean selected; boolean isSelected() { return selected; } void setSelected(boolean s) { selected =s;} void display() { ... TreeNode c = getChildren()[i]; ... paint(position, c.getStringValue()); ... } } Called, but not defined yet } Binding the interface class ExpressionDisplay binds TreeDisplay { Expression root; public ExpressionDisplay(Expression rootExpr) { root = rootExpr; } TreeNode getRoot() { return ExprTreeNode(root); } class ExprTreeNode binds TreeNode { Expression e; ExprTreeNode(Expression e) { this.e=e;} TreeNode[] getChildren() { return ExprTreeNode[](e.getSubExpressions()); } String getStringValue() { String s = e.description(); if (hasFocus()) s ="<"+s+">"; return s; } } } Instantiating the interface • Neither class is sufficient by itself • Create “compound” class that combines both • Compound class defined using “+” class SimpleExpressionDisplay = SimpleTreeDisplay + ExpressionDisplay; ... class Plus extends Expression { … } class Times extends Expression { … } Expression test = new Plus(new Times(5, 3), 9); TreeDisplay t = new SimpleExpressionDisplay(test); t.display(); Wrapper recycling • When returning nested objects, don’t use new – Avoid multiple “wrappers” for same object • Wrapper provides 1-1 mapping to embedded object • Uses map for lookups – 1st time, create new nested object – Subsequent wrappings of same embedded object return same nested object Wrapper recycling class ExpressionDisplay binds TreeDisplay { Expression root; public ExpressionDisplay(Expression rootExpr) { root = rootExpr; } TreeNode getRoot() { return ExprTreeNode(root); } class ExprTreeNode binds TreeNode { Expression e; ExprTreeNode(Expression e) { this.e=e;} TreeNode[] getChildren() { return ExprTreeNode[](e.getSubExpressions()); } String getStringValue() { String s = e.description(); if (hasFocus()) s ="<"+s+">"; return s; } } } Type safety • Virtual types – All nested interfaces, nested bindings, and nested implementations are virtual – Virtual types defined by enclosing instance – Exposed nested objects are compound types Type safety Expression e = ...; final ExpressionDisplay ed = new SimpleExpressionDisplay(e); ... // let FileSystemDisplay be a binding of // TreeDisplay to the file system structure SimpleFileSystemDisplay = SimpleTreeDisplay + FileSystemDisplay; FileSystem fs = ... ; final FileSystemDisplay fsd = new SimpleFileSystemDisplay(fs); ... ed.TreeNode t = ed.getRoot(); fsd.setRoot(t); // Type error detected by typechecker! // fsd.TreeNode is not subtype of ed.TreeNode Type safety (polymorphism) • We can still have polymorphism • If C = A + B, then – C<A –C<B Type safety (polymorphism) class SucAugSched = SuccessiveAugmentationColoring + SchedulingGraph; class SimAnSched = SimulatedAnnealingColoring + SchedulingGraph; ... final SchedulingGraph sg = wantSucAug ? new SucAugSched() : new SimAnSched(); sg.computeMinimumColoring( sg.CourseVertex[](courses) ); SucAugSched < SchedulingGraph and SimAnSched < SchedulingGraph, so this is okay Type safety (polymorphism) • Multiple bindings of nested classes allowed • Only one implementation of nested classes • Constructors defined at binding site only • Asymmetry Type safety (polymorphism) class StudentKnowsTeacherGraph binds Graph { class StudVertex binds Vertex { final Student s; StudVertex(Student s) {this.s=s;} ... } class TeacherVertex binds Vertex { Teacher t; TeacherVertex(Teacher t) { this.t = t; } ... } ... Vertex v1 = StudVertex(aStudent); ... ... Vertex v2 = TeacherVertex(aTeacher); ... } Conclusions • Is this AOP? – In broad sense, maybe (not exactly OOP or POP) – Not typical crosscutting • Similar to Jiazzi – “mixins” done at finer grain (procedure level) • Could we solve with templates (generics)? Conclusions • Do we buy it? – Adds type safety – Wrapper recycling not terribly exciting