In
AspectWerkz
any Java class can be an aspect (or more conceptually correct, can
be defined to be
cross-cutting), which means that it will become a unit of modularity
for crosscutting concerns.
They do not have to extend any special class or implement a specific interface, but can extend any class they want.
The only requirement is that the aspect needs to have either no constructor at all (meaning implicitly a default one) or one of these two different constructors defined:
org.codehaus.aspectwerkz.CrossCuttingInfo
instance
as its only parameter - this is needed if you want to retrieve information about the runtime
system, access paramaters defined at deployment time, access meta-data etc.
See
Using the CrossCuttingInfo class section
for details.
The aspect can then be defined using XML or annotations, see the XML definition section or the Annotation definition section for more information.
Since the Aspects are pure Java classes, you have the possibility of defining abstract aspects that you can reuse by implementing another aspect that inherits the abstract aspect.
Aspect inheritance is exactly regular class inheritance. An abstract aspect
is defined using the
abstract
keyword as a regular abstract class and is inherited
using the
extends
keyword.
In the aspect class the advice are regular methods. The methods needs to conform to a specfic signature
unless args()
selector is used in the pointcut the advice is bounded to.
Around
advice:
public Object <name of method>(JoinPoint joinPoint) throws Throwable
signature.
Before
and
After
advice:
public void <name of method>(JoinPoint joinPoint) throws Throwable
signature.
Here is a simple example of an
Around
advice. (For more examples see the
Examples section.) The
JoinPoint
object that is passed to
the method contains metadata and RTTI of the current
join point. To invoke the next advice in the chain (or the target method if there are no more advice)
simply call
joinPoint.proceed()
which will return the result from the next advice (or the target method).
public Object myAroundAdvice(JoinPoint joinPoint) throws Throwable { // do some stuff Object result = joinPoint.proceed(); // do some more stuff return result; }
If a pointcut is using the args()
selector to gain direct access to the advised method/constructor/field set
the Pointcut declaration itself will have a signature. The bounded advice(s) is then required to have those
parameters in their signature as well. This allows to avoid using the RTTI interface as well as providing strong typing.
Such an advice can be defined thru Annotations and thru XML. See the XML definition section or the Annotation definition section for more information.
The advice signature is thus depending on the pointcut signature, although the JoinPoint argument is mandatory. A sample could look like the following:
// will match all method named "log" with a "java.lang.String" as sole parameter @Expression execution(* log(..)) AND args(s) Pointcut myPointcut(String s) {return null;} @Before myPointcut(adviceArg) public void myBeforeAdvice(JoinPoint joinPoint, String adviceArg) {...} // it is possible to have the JoinPoint at any index in the advice parameter list // though a best practice is to keep it at the first position @Around myPointcut(adviceArg) public Object myAroundAdvice(String adviceArg, JoinPoint joinPoint) {... joinPoint.proceed(); ...} @After myPointcut(adviceArg) public void myAfterAdvice(JoinPoint joinPoint, String adviceArg) {...}
Each advice is passed a
JoinPoint
instance. The
JoinPoint
class implements
the join point concept, i.e. a well-defined point in the program flow.
The
JoinPoint
instance contains static info and RTTI (runtime type information) about the join point we are
currently executing over.
The static info (the signature) is retrieved through one of the
Signature interfaces by invoking
getSignature()
.
While the RTTI is retrieved through one of the
RTTI interfaces
by invoking
getRtti()
.
The
JoinPoint
clas has a method called
proceed()
, this method is used in
Around
advice to either:
proceed()
method returns the result from the method invocation and in the
Around
advice you have the option of either returning the value returned from the
proceed()
method or faking the value and returning something else.
Warning: if the
proceed()
method is invoked within an
Before
or
After
advice it will currently result in infinite recursion.
The
JoinPoint
class has some other interesting methods as well, see the JavaDoc for details.
The
Signature
interfaces are used for retrieving static
about the join point we are currently executing at. The interfaces forms a hierarchy in which you can
be both fine-grained and course-grained depending on you needs. Just cast to the appropriate interface.
The
Signature
can ber retrieved from the
JoinPoint
by invoking
the
getSignature()
method.
The interfaces are:
org.codehaus.aspectwerkz.joinpoint.Signature
org.codehaus.aspectwerkz.joinpoint.MemberSignature
org.codehaus.aspectwerkz.joinpoint.CodeSignature
org.codehaus.aspectwerkz.joinpoint.MethodSignature
org.codehaus.aspectwerkz.joinpoint.ConstructorSignature
org.codehaus.aspectwerkz.joinpoint.FieldSignature
org.codehaus.aspectwerkz.joinpoint.CatchClauseSignature
The
Rtti
interfaces are used for retrieving RTTI (Runtime Type Information)
about the join point we are currently executing at. The interfaces forms a hierarchy in which you can
be both fine-grained and course-grained depending on you needs. Just cast to the appropriate interface.
The
Signature
can ber retrieved from the
JoinPoint
by invoking
the
getSignature()
method.
The interfaces are:
org.codehaus.aspectwerkz.joinpoint.Rtti
org.codehaus.aspectwerkz.joinpoint.MemberRtti
org.codehaus.aspectwerkz.joinpoint.CodeRtti
org.codehaus.aspectwerkz.joinpoint.MethodRtti
org.codehaus.aspectwerkz.joinpoint.ConstructorRtti
org.codehaus.aspectwerkz.joinpoint.FieldRtti
org.codehaus.aspectwerkz.joinpoint.CatchClauseRtti
The
CrossCuttingInfo
class provides methods for getting handles to various parts
of the system, that can be used for introspection as well as redefinitions of the runtime system.
To access the
CrossCuttingInfo
in a specific aspect or mixin you have define a
constructor that takes one
org.codehaus.aspectwerkz.CrossCuttingInfo
as its only
parameter. The system will then take care of passing in a
CrossCuttingInfo
instance
when the aspect of mixin is instantiated. This instance you can then use as you wish, store it in
a member field etc.
This class has many useful methods. For example:
... RuntimeSystem getSystem() AspectContainer getContainer() void setParameter(String name, String value) String getParameter(String name) void setMetaData(Object key, Object value) Object getMetaData(Object key) int getDeploymentModel() void setDeploymentModel(int deploymentModel) Object getMixinTargetInstance(String mixinName, Object mixinImpl) Class getMixinTargetClass(String mixinName, Object mixinImpl) ...
Introductions makes it possible to extend a class with a new interface and/or a new implementation
(methods and fields).
AspectWerkz
supports two different types of introductions:
Implementation introductions (interface with concrete implementation), in
AspectWerkz
implemented as
Mixins, are defined as public static inner classes of the aspect class or as regular POJOs outside
the aspect class.
It is mandatory for the inner class to be public, apart from this the only requirement is that the aspect needs to have either no constructor at all (meaning implicitly a default one) or one of these two different constructors defined:
org.codehaus.aspectwerkz.CrossCuttingInfo
instance
as its only parameter - this is needed if you want to retrieve information about the runtime
system, access paramaters defined at deployment time, access meta-data etc.
See
Using the CrossCuttingInfo class section
for details.
The inner class implements the interface(s) to introduce on the target class.
Example of an implementation introduction that will introduce the implemetatation of the inner class along with the interfaces the inner class implements to the target classes defined in the definition.
public MyAspect { public static class MyIntroduction extends SuperIntroduction implements ToBeIntroduced { // methods to be introduced ... } }
It is possible to replace the introduction implementation at runtime using a call like
SystemLoader.getSystem("systemId").getAspectManager().getMixin("test.attribdef.aspect.IntroductionTestAspect$MyImpl"). swapImplementation("test.attribdef.aspect.IntroductionTestAspectMyImplReplacement");
The swapped implementation must implement the introduced interfaces, but can be an anonymous class or an aspect inner class. Only the default introduced implementation must be an inner class.
This is a simple example showing how an aspect implementation with a couple of advice and an introduction could look like:
public class MyAspect { public Object myAroundAdvice(JoinPoint joinPoint) throws Throwable { // do some stuff Object result = joinPoint.proceed(); // do some other stuff return result; } public void myBeforeAdvice(JoinPoint joinPoint) throws Throwable { // do some stuff } public static class MyIntroduction extends SuperMixin implements ContractualInterface { ... // introduced methods and fields } }
The XML definition allows to define the pointcut expressions in an external file. It can be used to provide aspects that needs to be tuned without an extra development phase (apart from editing an XML file). The main disadvantage with this approach is that the implementation is separated from the definition which makes the code harder to refactor, maintain and reuse.
The Annotation definition allows you to have truly self-contained aspect components. Those are easier to maintain, refactor and reuse as well as build libraries upon. The drawbacks are that it requires an additional post-compilation step (until Java 1.5) to annotate them with the metadata and introduces tighter coupling, makes the aspect harder to configure etc.
We have exprerienced that a combination of both definition formats can be beneficial. For example define the reusable aspects and advice using annotations but do the pointcut definition in the external XML file. This allows you to make certain decisions later in the development cycle, for example at deployment time instead of at compile time.