JoyAop Tutorial
Shen Li
JoyAop is a dynamic AOP framework based on the
CGLIB proxies.
JoyAop supports 3 types of Aspect currently:
1)
Interceptor. The interceptors could intercept any non-final
methods which could be overridden by CGLIB (Of course non-static,
public…).
2)
Interface. Actually, it’s a kind of Introduction here,
represents the introductions which only have interface.
3)
Mixin. Another kind of Introduction includes
both interface and implementation.
JoyAop support 3
types of Pointcut currently:
1)
Regular expression: Picks out the classes or methods by
matching their names.
2)
Interface: Picks out the classes by matching their interfaces.
3)
Annotation: Picks out the methods by matching their annotations.
Note
This tutorial mainly
focuses on the usage of JoyAop, and rarely explain
the concepts.
JoyAop supports 4 types of Interceptor currently:
Just like the Interceptor of the other dynamic AOP frameworks, must implement a callback interface, and it is the most efficient type.
package net.sf.joyaop.demo.simple;
import net.sf.joyaop.Interceptor;
import net.sf.joyaop.Invocation;
/**
* @author Shen Li
*/
public class FooInterceptor implements
Interceptor {
public Object
intercept(Invocation invocation) throws Throwable {
System.out.println();
System.out.println("invoke
FooInterceptor...");
System.out.println("method
name: " + invocation.getMethod().getName());
System.out.println("argument
array length: " + invocation.getArguments().length);
System.out.println("paramter value: " +
invocation.getParameter("paramName"));
System.out.println("proxy
class name: " + invocation.getProxy().getClass().getName());
return invocation.proceed();
}
}
Note the invocation.getParameter(..) method, with it the interceptors could obtain the
parameters defined in the current Jointpoint(see
below).
Using the AOP
package net.sf.joyaop.demo.simple;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* @author Shen Li
*/
public class
AopAllianceFooInterceptor
implements MethodInterceptor {
public Object
invoke(MethodInvocation
invocation) throws
Throwable {
System.out.println();
System.out.println("invoke
AopAllianceFooInterceptor...");
System.out.println("method
name: " + invocation.getMethod().getName());
System.out.println("argument
array length: " + invocation.getArguments().length);
System.out.println("proxy
class name: " + invocation.getThis().getClass().getName());
return invocation.proceed();
}
}
Like the first one, but doesn’t directly use the Invocation object.
package net.sf.joyaop.demo.simple;
import net.sf.joyaop.AbstractInterceptor;
/**
* @author Shen Li
*/
public abstract class AbstractFooInterceptor implements AbstractInterceptor {
public Object
execute() throws
Throwable {
System.out.println();
System.out.println("invoke
AbstractFooInterceptor...");
System.out.println("method
name: " + getMethod().getName());
System.out.println("argument
array length: " + getArguments().length);
System.out.println("parameter
value: " + getParameter("paramName"));
System.out.println("proxy
class name: " + getProxy().getClass().getName());
return proceed();
}
}
The AbstractInterceptor interface extends Invocation interface, so it has included all the useful methods, such as proceed (), getMethod(), etc. Just declare our interceptors as abstract class, then directly invoke those methods, the framework will do the rest job. This type is slightly slower than the above ones.
It mainly comes from the concept of abstract schema. Like the Decorator
pattern of OO, the interceptors (decorators) and their target objects are
required to implement the same interfaces.
Target interface::
package net.sf.joyaop.demo.simple;
/**
* @author Shen Li
*/
public interface Foo {
void foo();
}
Target implementation:
package net.sf.joyaop.demo.simple;
/**
* @author Shen Li
*/
public abstract class FooImpl implements
Foo {
public void
foo() {
System.out.println();
System.out.println("invoking
Foo.foo()...");
System.out.println("parameter
value: " + getParamName());
}
public abstract
String getParamName();
}
Decorator:
public abstract class FooDecorator implements
Foo {
public void
foo() {
System.out.println();
System.out.println("invoke
FooDecorator...");
System.out.println("method
name: foo");
System.out.println("argument
array length: 0");
System.out.println("paramter value: " +
getParamName());
System.out.println("proxy class name: "
+ getFoo().getClass().getName());
getFoo().foo();
}
protected abstract
Foo getFoo();
protected abstract
String getParamName();
}
The target object
has an interface and the decorator also implements that interface. In the above
example we want the decorator to intercept the foo() method of the target
object, we just implement that method, and add the extra code. Then if it needs
to proceed the invocation, we should declare an abstract getter method getFoo()
(must start with “get” and the return type should be the target
interface) , which will be implemented by the framework at runtime and returns
the target object. When using getFoo().foo() in the foo() method of the decorator, the framework will proceed the current method invocation.
Besides, we can also access the other methods of the target object by using getFoo().someOtherMethod(). (Not for proceeding invocation now) But
here we have another more simple way, just directly use someOtherMethod() in the foo() method of the decorator, and the framework will
automatically route the invocation to the target object for us. Note if the someOtherMethod()
method has been declared in the decorator itself (Maybe we want to intercept it
too), which means that method has been overridden, we can’t use the
latter simple way now. (Maybe the rule is like the Java inheritance rule: if
the subclass has overridden the super class’s methods, we should use super.someMethod()
instead of someMethod().)
Then how does the
decorator obtain the Joinpoint’s parameters?
Because the decorator doesn’t implements any framework specific interface
as the preceding generic ones, we should declare another abstract getter
method, which follows the JavaBean standard:
“get” + “name of the parameter”. Then just like the
getter method of target object, we simply use getParamName() to obtain the
parameter named paramName.
At last, Interceptor
of this type has the worst performance.
Here we focus on
using the concept of abstract schema to implement Mixin.
The target object could also implement the interfaces of the mixins which will be weaved at runtime.
Target object
package net.sf.joyaop.demo.simple;
/**
* @author Shen Li
*/
public abstract class Target implements Foo {
public void
target() {
foo();
System.out.println();
System.out.println("Target.target() invoked.");
}
}
The mixin’s code is in
the above Decorator section.
The target() method of the Target class could directly invoke
the foo() method of the mixin,
just like it’s own method. And at the client side, we could directly use targetInstance.foo() too.
Not used yet.
JoyAop supports pointcut
composition, by using the operators “AND”, “OR”,
“!” and parentheses.
JoyAop use the regex
library of JDK. To pick out classes use target(regular expression of the full class name), to pick
methods use execution(regular
expression of method name). It’s the same as the AspectJ.
(But the current definition model is somewhat limited.)
target(net.sf.joyaop.demo.simple.Target) AND execution(foo)
To pick a class use
interface(full
class name of the interface)
interface(net.sf.joyaop.demo.simple.Foo)
To pick out a
method use annotation(full
class name of the annotation)
annotation(net.sf.joyaop.demo.rbac.annotation.Permission)
All the aspects and
pointcuts should be declared in a configuration file,
currently an XML file.
Element: interceptor
Attributes:
Name |
Description |
required |
Class |
The class name |
true |
Scope |
The deployment
scope (jvm, class, instance, thread), default is
JVM |
false |
<interceptor class="net.sf.joyaop.demo.simple.FooInterceptor"/>
Element: mixin
Attributes:
Name |
Description |
required |
Class |
The
implementation class name |
true |
Scope |
The deployment
scope (jvm, class, instance, thread), default is
instance |
False |
Interface |
The interface
class name |
true |
<mixin interface="net.sf.joyaop.demo.simple.Foo"
class="net.sf.joyaop.demo.simple.FooImpl"/>
Element: interface
Attributes:
Name |
Description |
required |
class |
The interface
class name |
true |
<interface class="java.io.Serializable"/>
The
expression of Pointcut. If the same expression is used by many pointcuts, you can declare it in this element, and then
refer to it by the name.
Element: expr
Attributes:
name |
Description |
required |
Name |
The name of the expression,
as the reference of the element |
true |
<expr name="fooIntf">
interface(net.sf.joyaop.demo.simple.Foo)
</expr>
Bind Interceptor, Mixin, and Interface to the Pointcut.
Element: pointcut
Attributes:
Name |
Description |
required |
expr |
Expression of the
pointcut, you can refer to the other named
expressions here. |
True |
Sub elements:
l
Interceptor:
It’s the same as the preceding interceptor element. If there is already
an interceptor element has the same class attribute be defined outside the pintcut element, the aspect runtime will bind that
interceptor definition to the Pointcut, and this
element is just as a reference, only the class attribute required. Otherwise,
you must provide the full config info here, it will
be automatically bound to the Pointcut, and only
visible in current Pointcut..
l
Mixin: The same
l
Interface:
The same
l
Parameter:
Parameter of Joinpoint. It may be useful for
declaring transaction attribute, permission, etc.
Attributes:
Name |
Description |
required |
Name |
The name of
parameter. |
true |
Value |
The value of
parameter |
true |
<pointcut expr="fooIntf">
<interceptor class="net.sf.joyaop.demo.simple.FooDecorator"/>
<param name="paramName">decoratorValue</param>
</pointcut>
<pointcut expr="fooIntf">
<mixin interface="net.sf.joyaop.demo.simple.Foo"
class="net.sf.joyaop.demo.simple.FooImpl"/>
<param name="paramName">mixinValue</param>
</pointcut>
The precedence of the interceptors, the top is higher.
Element: interceptor-precedence
<interceptor-precedence>
<interceptor class="net.sf.joyaop.demo.simple.FooInterceptor"/>
<interceptor class="net.sf.joyaop.demo.simple.AopAllianceFooInterceptor"/>
<interceptor class="net.sf.joyaop.demo.simple.AbstractFooInterceptor"/>
<interceptor class="net.sf.joyaop.demo.simple.FooDecorator"/>
</interceptor-precedence>
<runtime>
<expr name="fooIntf">
interface(net.sf.joyaop.demo.simple.Foo)
</expr>
<interceptor-precedence>
<interceptor class="net.sf.joyaop.demo.simple.FooInterceptor"/>
<interceptor class="net.sf.joyaop.demo.simple.AopAllianceFooInterceptor"/>
<interceptor class="net.sf.joyaop.demo.simple.AbstractFooInterceptor"/>
<interceptor class="net.sf.joyaop.demo.simple.FooDecorator"/>
</interceptor-precedence>
<pointcut expr="target(net.sf.joyaop.demo.simple.Target) AND execution(foo)">
<interceptor class="net.sf.joyaop.demo.simple.FooInterceptor"/>
<interceptor class="net.sf.joyaop.demo.simple.AopAllianceFooInterceptor"/>
<interceptor class="net.sf.joyaop.demo.simple.AbstractFooInterceptor"/>
<param name="paramName">interceptorValue</param>
</pointcut>
<pointcut expr="fooIntf">
<interceptor class="net.sf.joyaop.demo.simple.FooDecorator"/>
<param name="paramName">decoratorValue</param>
</pointcut>
<pointcut expr="fooIntf">
<mixin interface="net.sf.joyaop.demo.simple.Foo"
class="net.sf.joyaop.demo.simple.FooImpl"/>
<param name="paramName">mixinValue</param>
</pointcut>
</runtime>
Put the contents into a file named aop.xml.
When we have
written the aspects and configuration file, how to use them with our
applications? Since JoyAop is based on the proxy
solution, the target objects that should be weaved must be instantiated by the ObjectFactory. To create the ObjectFactory
follow the code below:
package net.sf.joyaop.demo.simple;
import net.sf.joyaop.ObjectFactory;
import net.sf.joyaop.config.XmlConfiguration;
import net.sf.joyaop.impl.AspectRuntimeImpl;
/**
* @author Shen Li
*/
public class
public
static void
main(String[] args) {
ObjectFactory objectFactory = new AspectRuntimeImpl(new XmlConfiguration("net/sf/joyaop/demo/simple/aop.xml"));
Target target
= (Target) objectFactory.newInstance(Target.class);
System.out.println("invoking Target.target()...");
target.target();
}
}
The above code is
located in the demos directory of the distribution. To
test it, type “ant run:demo:simple” in
the console, the output should be like this:
run:demo:simple:
[java]
invoking Target.target()...
[java]
invoke FooInterceptor...
[java]
method name: foo
[java]
argument array length: 0
[java]
paramter value: interceptorValue
[java]
proxy class name: net.sf.joyaop.demo.simple.Target$$EnhancerByCGLIB$$e85b3db7
[java]
invoke AopAllianceFooInterceptor...
[java]
method name: foo
[java]
argument array length: 0
[java]
proxy class name: net.sf.joyaop.demo.simple.Target$$EnhancerByCGLIB$$e85b3db7
[java]
invoke AbstractFooInterceptor...
[java]
method name: foo
[java]
argument array length: 0
[java]
parameter value: interceptorValue
[java]
proxy class name: net.sf.joyaop.demo.simple.Target$$EnhancerByCGLIB$$e85b3db7
[java]
invoke FooDecorator...
[java]
method name: foo
[java]
argument array length: 0
[java]
paramter value: decoratorValue
[java]
proxy class name: net.sf.joyaop.demo.simple.Target$$EnhancerByCGLIB$$e85b3db7
[java]
invoking Foo.foo()...
[java]
parameter value: mixinValue
[java]
Target.target() invoked.
This tutorial has
simply introduced the basic usage of JoyAop, for more
information about (dynamic) AOP, refer to the links below:
http://jroller.com/page/rickard
https://dynaop.dev.java.net/release/1.0-beta/manual/
http://aspectwerkz.codehaus.org/