460 likes | 576 Views
Untangling Crosscutting Concerns with CAESAR. Mira Mezini Klaus Ostermann. Aspect-Oriented Software Development, 2004. Outline. 1. Requirements on Language Support. 3. The Caesar Model The observer pattern in Caesar. Related work. 2. JPI: Joint Point Interception approach
E N D
Untangling Crosscutting Concerns with CAESAR Mira Mezini Klaus Ostermann Aspect-Oriented Software Development, 2004
Outline 1. Requirements on Language Support 3. The CaesarModel • The observer pattern in Caesar. • Related work. • 2. JPI: Joint Point Interception approach • The “observer design pattern”. • JPI deficiencies.
Relevant Concepts: • “Tyranny of the dominant decomposition”. • Cross-cutting models/concerns. • Code scattering and tangling. • JPI: the Joint-Point Interception approach.
Requirements on Language Support • Multi-Abstraction Aspect Modules • Aspect Composition • Reuse of Aspect Implementation and Bindings • Aspectual Polymorphism
Joint Point Interception • JPI approach has disadvantages. • JPIs are still the main primitives of AOP. • Higher level constructs are to be added on top of JPIs and advices. The problems with JPI are presented through the observer pattern in AspectJ.
Abstract Observer Color Observer Subject Subject Observer Observer A Graphics Software System … Shape Screen Point Line … … The observer pattern 1
Declaring an abstract aspect Marker Interfaces public abstract aspect ObserverProtocol { protected interface Subject { } protected interface Observer { } private WeakHashMap perSubjectObservers; protected List getObservers(Subject s) { if (perSubjectObservers ==null) perSubjectObservers =new WeakHashMap(); List observers = (List) perSubjectObservers.get(s); if ( observers == null ) { observers = new LinkedList(); perSubjectObservers.put(s, observers); } return observers; } … public abstract aspect ObserverProtocol { protected interface Subject { } protected interface Observer { } private WeakHashMap perSubjectObservers; protected List getObservers(Subject s) { if (perSubjectObservers ==null) perSubjectObservers =new WeakHashMap(); List observers = (List) perSubjectObservers.get(s); if ( observers == null ) { observers = new LinkedList(); perSubjectObservers.put(s, observers); } return observers; } … public abstract aspect ObserverProtocol { protected interface Subject { } protected interface Observer { } private WeakHashMap perSubjectObservers; protected List getObservers(Subject s) { if (perSubjectObservers ==null) perSubjectObservers =new WeakHashMap(); List observers = (List) perSubjectObservers.get(s); if ( observers == null ) { observers = new LinkedList(); perSubjectObservers.put(s, observers); } return observers; } … public abstract aspect ObserverProtocol { protected interface Subject { } protected interface Observer { } private WeakHashMap perSubjectObservers; protected List getObservers(Subject s) { if (perSubjectObservers ==null) perSubjectObservers =new WeakHashMap(); List observers = (List) perSubjectObservers.get(s); if ( observers == null ) { observers = new LinkedList(); perSubjectObservers.put(s, observers); } return observers; } … Hash Field Allocating a List of Observers for any Subject The observer pattern 2 public abstract aspect ObserverProtocol { protected interface Subject { } protected interface Observer { } private WeakHashMap perSubjectObservers; protected List getObservers(Subject s) { if (perSubjectObservers ==null) perSubjectObservers =new WeakHashMap(); List observers = (List) perSubjectObservers.get(s); if ( observers == null ) { observers = new LinkedList(); perSubjectObservers.put(s, observers); } return observers; } …
… public void addObserver(Subject s,Observer o){ getObservers(s).add(o); } public void removeObserver(Subject s,Observer o){ getObservers(s).remove(o); } abstract protected void updateObserver(Subject s, Observer o); abstract protected pointcut subjectChange(Subject s); after(Subject s) : subjectChange(s) { Iterator iter = getObservers(s).iterator(); while ( iter.hasNext() ) updateObserver(s, ((Observer)iter.next())); } } … public void addObserver(Subject s,Observer o){ getObservers(s).add(o); } public void removeObserver(Subject s,Observer o){ getObservers(s).remove(o); } abstract protected void updateObserver(Subject s, Observer o); abstract protected pointcut subjectChange(Subject s); after(Subject s) : subjectChange(s) { Iterator iter = getObservers(s).iterator(); while ( iter.hasNext() ) updateObserver(s, ((Observer)iter.next())); } } … public void addObserver(Subject s,Observer o){ getObservers(s).add(o); } public void removeObserver(Subject s,Observer o){ getObservers(s).remove(o); } abstract protected void updateObserver(Subject s, Observer o); abstract protected pointcut subjectChange(Subject s); after(Subject s) : subjectChange(s) { Iterator iter = getObservers(s).iterator(); while ( iter.hasNext() ) updateObserver(s, ((Observer)iter.next())); } } Adding and removing observers Abstract members Advice The observer pattern 3 … public void addObserver(Subject s,Observer o){ getObservers(s).add(o); } public void removeObserver(Subject s,Observer o){ getObservers(s).remove(o); } abstract protected void updateObserver(Subject s, Observer o); abstract protected pointcut subjectChange(Subject s); after(Subject s) : subjectChange(s) { Iterator iter = getObservers(s).iterator(); while ( iter.hasNext() ) updateObserver(s, ((Observer)iter.next())); } }
public aspect ColorObserver extends ObserverProtocol{ declare parents: Point implements Subject; declare parents: Line implements Subject; declare parents: Screen implements Observer; protected pointcut subjectChange(Subject s) : ( call ( void Point.setColor(Color)) || call ( void Line.setColor(Color)) ) && target(s); protectedvoid updateObserver(Subject s, Observer o) { ((Screen)o).display("Color change."); } } public aspect ColorObserver extends ObserverProtocol{ declare parents: Point implements Subject; declare parents: Line implements Subject; declare parents: Screen implements Observer; protected pointcut subjectChange(Subject s) : ( call ( void Point.setColor(Color)) || call ( void Line.setColor(Color)) ) && target(s); protectedvoid updateObserver(Subject s, Observer o) { ((Screen)o).display("Color change."); } } public aspect ColorObserver extends ObserverProtocol{ declare parents: Point implements Subject; declare parents: Line implements Subject; declare parents: Screen implements Observer; protected pointcut subjectChange(Subject s) : ( call ( void Point.setColor(Color)) || call ( void Line.setColor(Color)) ) && target(s); protectedvoid updateObserver(Subject s, Observer o) { ((Screen)o).display("Color change."); } } public aspect ColorObserver extends ObserverProtocol{ declare parents: Point implements Subject; declare parents: Line implements Subject; declare parents: Screen implements Observer; protected pointcut subjectChange(Subject s) : ( call ( void Point.setColor(Color)) || call ( void Line.setColor(Color)) ) && target(s); protectedvoid updateObserver(Subject s, Observer o) { ((Screen)o).display("Color change."); } } Aspect Impl. Mark Base Classes Pointcuts Impl. Update Impl. The observer pattern 4 public aspect ColorObserver extends ObserverProtocol{ declare parents: Point implements Subject; declare parents: Line implements Subject; declare parents: Screen implements Observer; protected pointcut subjectChange(Subject s) : ( call ( void Point.setColor(Color)) || call ( void Line.setColor(Color)) ) && target(s); protectedvoid updateObserver(Subject s, Observer o) { ((Screen)o).display("Color change."); } }
The observer pattern 5 Two primary advantages: • Reusability • Implementation is separated from binding • Implementation is reusable. • Independent Extensibility • Subject can be mapped to many classes. • A class can be assigned many roles. • The same role can be assigned to the same class in different bindings.
Lack of Support for:Multiabstraction Aspects • No separation of concerns in the aspect: • Contains all methods of all abstractions defined in it. Contradicts OOP and AOP. • Fields and inheritance for the abstractions? • AspectJ’s introduction mechanism leads to losing independent extensibility. • No dynamic dispatch.
Lack of support for:Sophisticated Mapping • Each abstraction must be mapped directly to some base class. • This is not always the case: • Consider a graph aspect, defined in terms of Node and Edge. • An application in which every Point has a collection of adjacent points. • Edge cannot be mapped to any base class.
ObserverProtocol ObserverProtocol ColorObserver 1 ColorObserver ColorObserver 2 Software Software LocationObserver Lack of support for:Reusable Aspect Bindings • Every aspect binding is coupled to one particular aspect implementation.
Lack of support for:Aspectual Polymorphism • Once the aspect is compiled together with the target package, the changes in the execution are determined. • It is not possible to determine at runtime which implementation of the aspect to apply.
The Caesar Model 1 • ACI - Aspect Collaboration Interface: An interface definition for aspects with multiple mutually recursive nested types. • The purpose of ACI: decoupling aspect implementations and aspect bindings. • The modules are independently defined, but are indirectly connected.
Observer ACI Color Observer Subject Subject Observer Observer A Software System … The Caesar Model 2
interface ObserverProtocol { interface Subject { providedvoid addObserver(Observer o); providedvoid removeObserver(Observer o); providedvoid changed(); expected String getState(); } interface Observer { expectedvoid notify(Subject s); } } interface ObserverProtocol { interface Subject { providedvoid addObserver(Observer o); providedvoid removeObserver(Observer o); providedvoid changed(); expected String getState(); } interface Observer { expectedvoid notify(Subject s); } } interface ObserverProtocol { interface Subject { providedvoid addObserver(Observer o); providedvoid removeObserver(Observer o); providedvoid changed(); expected String getState(); } interface Observer { expectedvoid notify(Subject s); } } interface ObserverProtocol { interface Subject { providedvoid addObserver(Observer o); providedvoid removeObserver(Observer o); providedvoid changed(); expected String getState(); } interface Observer { expectedvoid notify(Subject s); } } interface ObserverProtocol { interface Subject { providedvoid addObserver(Observer o); providedvoid removeObserver(Observer o); providedvoid changed(); expected String getState(); } interface Observer { expectedvoid notify(Subject s); } } Abstract Observer Aspect Nested Subject Interface Nested Observer Interface ProvidedMethods ExpectedMethod Aspect Collaboration Interfaces 1 interface ObserverProtocol { interface Subject { providedvoid addObserver(Observer o); providedvoid removeObserver(Observer o); providedvoid changed(); expected String getState(); } interface Observer { expectedvoid notify(Subject s); } }
Aspect Collaboration Interfaces 2 • The ObserverProtocol ACI has 2 nested, mutually recursive, ACIs. • Determines impl./bindings relations: • provided: what the aspect should provide. • expected: required from the binding. • The provided & expected facets are implemented in different modules. • However, They are indirectly connected through the ACI.
Aspect Collaboration Interfaces 3 What do we gain? • An abstract aspect. • Aspect-related Interfaces that are “aware” of each other and can collaborate. • A modularization of the problem. • No binding-specific members.
Aspect Implementations 1 • An aspect implementation must implement all the provided methods. • The implementation class structure is the same as of the ACI structure. • The implementation of the provided methods can call the expected methods.
class ObserverProtocolImpl implements ObserverProtocol { class Subject { List observers = new LinkedList(); void addObserver(Observer o) { observers.add(o);} void removeObserver(Observer o) { observers.remove(o); } void changed() { Iterator iter = observers.iterator(); while ( iter.hasNext() ) ((Observer)iter.next()).notify(this); } } } class ObserverProtocolImpl implements ObserverProtocol { class Subject { List observers = new LinkedList(); void addObserver(Observer o) { observers.add(o);} void removeObserver(Observer o) { observers.remove(o); } void changed() { Iterator iter = observers.iterator(); while ( iter.hasNext() ) ((Observer)iter.next()).notify(this); } } } class ObserverProtocolImpl implements ObserverProtocol { class Subject{ List observers = new LinkedList(); void addObserver(Observer o) { observers.add(o);} void removeObserver(Observer o) { observers.remove(o); } void changed() { Iterator iter = observers.iterator(); while ( iter.hasNext() ) ((Observer)iter.next()).notify(this); } } } Provided Methods Observer Aspect A Nested Class Aspect Implementations 2 class ObserverProtocolImpl implements ObserverProtocol { class Subject { List observers = new LinkedList(); void addObserver(Observer o) { observers.add(o);} void removeObserver(Observer o) { observers.remove(o); } void changed() { Iterator iter = observers.iterator(); while ( iter.hasNext() ) ((Observer)iter.next()).notify(this); } } }
Aspect Implementations 3 What do we gain? • Many kinds of Observeraspects, with a common interface. • A modularization of the aspect. • No need for “global” members. • Still no binding-specific members.
Aspect Bindings 1 • An aspect binding implements all the expected methods in the ACI. • For each nested ACI there may be zero,one, or more nested bindings. • Base objects are accessed through wrappers.
A “Binding” class ColorObserver binds ObserverProtocol { class PointSubject binds Subject wraps Point { String getState() { return "Point colored "+wrappee.getColor()}} } class LineSubject binds Subject wraps Line { String getState() { return "Line colored "+wrappee.getColor(); } } class ScreenObserver binds Observer wraps Screen { void notify(Subject s) {wrappee.display("Color changed: "+s.getState());} } after(Point p): (call(void p.setColor(Color))){ PointSubject(p).changed(); } after(Line l): (call(void l.setColor(Color))){ LineSubject(l).changed(); } } class ColorObserver binds ObserverProtocol { class PointSubject binds Subject wraps Point { String getState() { return "Point colored "+wrappee.getColor()}} } class LineSubject binds Subject wraps Line { String getState() { return "Line colored "+wrappee.getColor(); } } class ScreenObserver binds Observer wraps Screen { void notify(Subject s) {wrappee.display("Color changed: "+s.getState());} } after(Point p): (call(void p.setColor(Color))){ PointSubject(p).changed(); } after(Line l): (call(void l.setColor(Color))){ LineSubject(l).changed(); } } class ColorObserver binds ObserverProtocol { class PointSubject binds Subject wraps Point { String getState() { return "Point colored "+wrappee.getColor()}} } class LineSubject binds Subject wraps Line { String getState() { return "Line colored "+wrappee.getColor(); } } class ScreenObserver binds Observer wraps Screen { void notify(Subject s) {wrappee.display("Color changed: "+s.getState());} } after(Point p): (call(void p.setColor(Color))){ PointSubject(p).changed(); } after(Line l): (call(void l.setColor(Color))){ LineSubject(l).changed(); } } class ColorObserver binds ObserverProtocol { class PointSubject binds Subject wraps Point { String getState() { return "Point colored "+wrappee.getColor()}} } class LineSubject binds Subject wraps Line { String getState() { return "Line colored "+wrappee.getColor(); } } class ScreenObserver binds Observer wraps Screen { void notify(Subject s) {wrappee.display("Color changed: "+s.getState());} } after(Point p): (call(void p.setColor(Color))){ PointSubject(p).changed(); } after(Line l): (call(void l.setColor(Color))){ LineSubject(l).changed(); } } Binding of Subject Binding of Observer Advices Aspect Bindings 2 class ColorObserver binds ObserverProtocol { class PointSubject binds Subject wraps Point { String getState() { return "Point colored "+wrappee.getColor()}} } class LineSubject binds Subject wraps Line { String getState() { return "Line colored "+wrappee.getColor(); } } class ScreenObserver binds Observer wraps Screen { void notify(Subject s) {wrappee.display("Color changed: "+s.getState());} } after(Point p): (call(void p.setColor(Color))){ PointSubject(p).changed(); } after(Line l): (call(void l.setColor(Color))){ LineSubject(l).changed(); } }
Aspect Bindings 3 What do we gain? • Interface can be bound in many ways. • A “Binding” is a module. • No need for “global” members. • No aspect-implementation specific details.
Wrapper Recycling • Avoiding multiple wrappers for the same base object. • Access to a base object is done through the wrapper: outerClassInstance.Wrapper(constructor_args) • A wrapper may wrap a set of objects.
Most Specific Wrappers 1 A mechanism that determines the most specific wrapper for an object based on the object’s runtime type, when multiple nested binding classes with the same name are available.
Most Specific Wrappers 2 class MovableFigures { class MovableFigure implements Movable wraps Figure { void moveBy(int x, int y) {}; } class MovableFigure implements Movable wraps Point { void moveBy(int x, int y) { wrappee.setX(wrappee.getX()+x); wrappee.setY(wrappee.getY()+y); } } class MovableFigure implements Movable wraps Line { void moveBy(int x, int y) { MovableFigure(wrappee.getP1()).moveBy(x,y); MovableFigure(wrappee.getP2()).moveBy(x,y); } } }
Most Specific Wrappers 3 class Test { MovableFigures mv = new MovableFigures(); void move(Figure f) { mv.MovableFigure(f).moveBy(5,7); } } On a constructor/wrapper call, the dynamic type of the argument determines the actual nested binding to instantiate/recycle.
Pointcuts and Advices • Supported similarly to AspectJ • Part of the modular ACIs. • A binding must be explicitly deployed. The difference: Compilation with pointcuts and advices does not change the base class semantics.
Weavelets and Deployment • Weavelet: a composition of an implementation and a binding. • A weavelet has to be deployed in order to activate its pointcuts and advices. class CO extends ObserverProtocol<ColorObserver,ObserverProtocolImpl>{};
Weavelets 2 What do we gain? • Reusable aspect bindings. • Reusable aspect implementations. class CO1extends ObserverProtocol<ColorObserver,ObserverProtocolImpl1>{}; class CO2extends ObserverProtocol<ColorObserver,ObserverProtocolImpl2>{}; class CO3extends ObserverProtocol<ColorObserver,ObserverProtocolImpl3>{}; class Obs1extends ObserverProtocol<Observer1,ObserverProtocolImpl>{}; class Obs2extends ObserverProtocol<Observer2,ObserverProtocolImpl>{}; class Obs3extends ObserverProtocol<Observer3,ObserverProtocolImpl>{};
Static Deployment 1 class Test ... { deploypublicstaticfinal CO co = new CO(); ... } • Pointcuts and advices of co weavelet are engaged only if it is deployed. • Deployment takes place at loading time of Test.
Static Deployment 2 class Test{ deploypublicstaticfinal CO co = new CO(); void register(Point p, Screen s){ co.PointSubject(p).addObserver( co.ScreenObserver(s) ); } • Weavelet access. • Objects are accessed through the wrappers mechanism.
Dynamic Deployment 1 It is possible to decide dynamically which aspect implementation to deploy. class Logging { after(): (call(void Point.setX(int)) || call(void Point.setY(int)) ) { System.out.println("Coordinates changed"); } } class VerboseLogging extends Logging { after(): (call(void Point.setColor(Color)) { System.out.println("Color changed"); } }
class Main { publicstaticvoid main(String args[]) { Logging log = null; Point p[] = createSamplePoints(); if (args[0].equals("-log")) log = new Logging(); else if (args[0].equals("-verbose")) log = new VerboseLogging(); deploy (l) { modify(p); } } publicstaticvoid modify(Point p[]) { p[3].setX(5); p[2].setColor(Color.RED); } } What is the type of log ? Dynamic Deployment 2 class Main { publicstaticvoid main(String args[]) { Logging log = null; Point p[] = createSamplePoints(); if (args[0].equals("-log")) log = new Logging(); else if (args[0].equals("-verbose")) log = new VerboseLogging(); deploy (l) { modify(p); } } publicstaticvoid modify(Point p[]) { p[3].setX(5); p[2].setColor(Color.RED); } }
Virtual Classes and Static Typing All nested interfaces of a CI and all classes that implement or bind such interfaces, are virtual types/classes Compatible with Java’s approach.
Caesar vs. Hyper/J • Hyper/J: Independent hyperslices, integrated by composition rules (hypermodule). • Class-based. Can’t change individual objects. • Lacks CI’s and reusable bindings: either the modules are independent, or composition becomes very complex. • Composition language is not OO enough.
Caesar vs. Composition Filters • CFs: filters for object’s in/out messages. Integration through join points. • CF have no mechanism for separating aspect implementation from aspect bindings. • No aspectual polymorphism. • CFs are more declarative: good for some kinds of concerns.
Current Status • in caesarj.org • “Very active on-going research”. • The AORTA project. • Industry: in www.topprax.de • Applying aspect-oriented programming in commercial software development
Conclusions • JPI alone does not suffice for a modular structuring of aspects, resulting in tangled aspect code. • Caesar enables: • Componentization of aspects. • Reusability of aspects bindings & implementations. • Polymorphic aspects usage. • Dynamic aspect deployment. • Caesar is based on the notion of an ACI • ACIs can be applied to support a more modular structuring of aspect code and better aspect reuse.