AOP
Content:
- Introduction
- What is AOP?
- AOP concepts
- Usage
- Static weavers
- Tools
- AspectJ
- AspectDNG
- Eos
- YAAOPF
- Examples
- Base code
- Trace
- Exception handling
- Special checks
- More advanced examples
- AOP with reflection and annotations
- AOP introductions
- Dynamic weavers
- Tools
- Spring
- Castle Windsor
- Examples
- Main code
- Trace
- Exception handling
- Special checks
- Is it good?
Introduction:
This article will provide a brief introduction to AOP (Aspect Oriented Programming),
explain some basic concepts and illustrate with some simple examples.
The approach will be more practical than theoretical.
What is AOP?
AOP was invented by Gregor Kiczales at Xerox Parc around 1997. The first popular
AOP tool AspectJ became available in 2001/2002.
Let us start by noting that AOP is not a replacement for OOP - AOP is a supplement
for OOP.
As most other concepts in software it is about reducing duplication of code and
make code more readable.
OOP is great for business logic code. But OOP is not very
good for IT functionality (exception/error handling, transaction handling,
logging, input checking etc.) code unrelated to the business logic.
In most OO code the code handling the IT functionality end up being more lines
than the code handling the business logic. And adding all the code and mixing it in with
the business logic code tend to make it much harder to read the business logic code.
And it is difficult to avoid:
- Using inheritance to include such IT functionality is a big no in modern OOP. It
violates "is a" principle and conflict with popular OO languages that does not
allow multiple inheritance.
- The traditional OO mechanisms inheritance, method override, polymorphism is
usually not suited for handling this IT functionality in a way that avoids code
bloat.
AOP attempts to separate OO code with business logic from AO code with IT functionality
in a way that does not duplicate the IT functionality code.
Two types of AOP tools exists:
- static weavers
- dynamic weavers
Static weavers combine OO code and AO code to executable code at build time.
Dynamic code combines OO code and AO code at runtime when objects are created.
AOP was very hot around 2005-2010, but is a niche technology today.
AOP concepts:
The following are key concepts in AOP:
- concern
- functionality
- cross-cutting concern
- functionality across classes
- advice
- functionality to add
- join point
- place to add functionality
- pointcut
- criteria to find join points
- aspect
- combination of pointcut and advice
The classic types of advice are:
- before call
- advice is executed before call
- after call
- advice is executed after call returned
- at exception in call
- advice is executed after call threw exception
- around call
- advice is executed instead of call and advice may explicit make (proceed) with call
Usage:
Typical usages of AOP are for:
- Logging
- Exception handling
- Transactions
- Security
- Caching
Static weavers:
Static weavers does typical not require anything in the OO code
so less intrusive which is a huge advantage
Some static weavers are:
- AspectJ for Java
- AspectDNG for C#/.NET
- Eos for C#/.NET
- YAAOPF for C#/.NET
In theory the .NET weavers can also be used for VB.NET code, but in practice .NET AOP
developers are always using C#.
AspectJ
AspectJ is the de facto standard for static weavers. It is an open source project
under the Eclipse organization. It is widely used in a large number of Java
frameworks. Most other static weavers are heavily inspired by AspectJ.
AspectJ can be downloaded from Eclipse.
AspectJ supports both before/after advice and around advice.
AspectJ can be used with both a special AspectJ syntax and as valid Java code
with annotations.
I find the special AspectJ syntax to be a lot clearer and more readable than the
Java annotation style. But it is my impression that the Java annotation style is
more widely used today.
To use AspectJ one must:
- Compile Java code and aspects with AspectJ compiler (ajc command)
- Run with AscpectJ runtime (aspectjrt.jar) in classpath
Use of AspectJ is also supported in ant.
build.xml fragment:
<taskdef resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties">
<classpath>
<pathelement location="/somewhere/aspectj/lib/aspectjtools.jar"/>
</classpath>
</taskdef>
...
<iajc ... />
The iajc task works similar to the javac task.
AspectDNG
AspectDNG is an older static weaver for C#/.NET. It is an open source project that
are now abandoned. It is inspired by AspectJ.
AspectDNG can be downloaded from Tigris.
AspectDNG does not work properly on newer OS/.NET versions. And since it is not being
maintained then that will not change. The issue is that strings get corrupted.
AspectDNG supports around advice.
AspectDNG work based on attributes (.NET term for annotations).
To use AspectDNG one must:
- Build normal C# code and aspect C# code to an assembly (need reference to aspectdng.exe)
- Modify assembly by running aspectdng.exe on it
- Have aspectdng.exe present at runtime
Eos
Eos is a static weaver for C#/.NET. It is an academic project where the researchers
has moved on to other projects by now. It is inspired by AspectJ.
Eos can be downloaded from Iowa State University.
Eos supports both before/after advice and around advice.
Eos work via special aspect syntax.
To use Eos one must:
- Build normal C# code and aspect C# code with Eos compiler (eos command)
- Have Eos runtime (Eos.Runetime.dll) present at runtime
YAAOPF
YAAOPF (Yet Another AOP Framework) is a toy static weaver developed by me, because I
needed a static weaver for .NET that worked with later .NET versions. It is also
open source. And it is also inspired by AspectJ.
YAAOPF can be downloaded from my web site.
YAAOPF supports before/after advice.
YAAOPF work based on attributes (.NET term for annotations).
To use YAAOPF one must:
- Build normal C# code to assembly
- Build aspect C# code to assembly (need reference to StaticWeaver.dll)
- Modify normal assembly by running YAAOPF tool on it (yaaopf1 command)
- Run modified assembly with both aspect assembly and a generated glue assembly present
Examples:
Base code
We will demonstrate using the following very simple program:
IntMath.java:
package traditional;
public class IntMath {
public static int add(int a, int b) {
return (a + b);
}
public static int subtract(int a, int b) {
return (a - b);
}
public static int multiply(int a, int b) {
return (a * b);
}
public static int divide(int a, int b) {
return (a / b);
}
}
DoubleMath.java:
package traditional;
public class DoubleMath {
public static double add(double a, double b) {
return (a + b);
}
public static double subtract(double a, double b) {
return (a - b);
}
public static double multiply(double a, double b) {
return (a * b);
}
public static double divide(double a, double b) {
return (a / b);
}
}
Test.java:
package traditional;
public class Test {
public static void main(String[] args) {
System.out.println("Start");
try {
System.out.println("2+3=" + IntMath.add(2, 3));
System.out.println("2.0*3.0=" + DoubleMath.multiply(2.0, 3.0));
System.out.println("10/0=" + IntMath.divide(10, 0));
} catch (Exception ex) {
System.out.println("Exception: " + ex.getMessage());
}
System.out.println("End");
}
}
Run:
C:\Work\AOP\traditional>javac *.java
C:\Work\AOP\traditional>java -cp .. traditional.Test
Start
2+3=5
2.0*3.0=6.0
Exception: / by zero
End
IntMath.cs:
namespace OldStuff
{
public class IntMath
{
public static int Add(int a, int b)
{
return (a + b);
}
public static int Subtract(int a, int b)
{
return (a - b);
}
public static int Multiply(int a, int b)
{
return (a * b);
}
public static int Divide(int a, int b)
{
return (a / b);
}
}
}
DoubleMath.cs:
namespace OldStuff
{
public class DoubleMath
{
public static double Add(double a, double b)
{
return (a + b);
}
public static double Subtract(double a, double b)
{
return (a - b);
}
public static double Multiply(double a, double b)
{
return (a * b);
}
public static double Divide(double a, double b)
{
return (a / b);
}
}
}
Test.cs:
using System;
namespace OldStuff
{
public class Test
{
public static void Main(string[] args)
{
Console.WriteLine("Start");
try
{
Console.WriteLine("2+3=" + IntMath.Add(2, 3));
Console.WriteLine("2.0*3.0=" + DoubleMath.Multiply(2.0, 3.0));
Console.WriteLine("10/0=" + IntMath.Divide(10, 0));
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.Message);
}
Console.WriteLine("End");
}
}
}
Run:
C:\Work\AOP\aspectdng>csc Test.cs IntMath.cs DoubleMath.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.5483
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
C:\Work\AOP\aspectdng>Test
Start
2+3=5
2.0*3.0=6
Exception: Attempted to divide by zero.
End
This code is rather trvial, but it is OK for demo purpose.
Trace:
Trace1.aj:
package traditional;
// aspect
aspect Trace1 {
// pointcut:
// public visibility, any return type, class traditional.IntMath, any method name, any arguments
// public visibility, any return type, class traditional.DoubleMath, any method name, any arguments
pointcut mathtrace() : call(public * traditional.IntMath.*(..)) || call(public * traditional.DoubleMath.*(..));
// advice:
// before
before() : mathtrace() {
System.out.println("AOP> Enter " + thisJoinPoint.getSignature());
}
// advice:
// after returning
after() returning : mathtrace() {
System.out.println("AOP> Exit " + thisJoinPoint.getSignature());
}
}
Use:
C:\Work\AOP\traditional>ajc -source 1.7 *.java Trace1.aj
C:\Work\AOP\traditional>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar traditional.Test
Start
AOP> Enter int traditional.IntMath.add(int, int)
AOP> Exit int traditional.IntMath.add(int, int)
2+3=5
AOP> Enter double traditional.DoubleMath.multiply(double, double)
AOP> Exit double traditional.DoubleMath.multiply(double, double)
2.0*3.0=6.0
AOP> Enter int traditional.IntMath.divide(int, int)
Exception: / by zero
End
Trace2.aj:
package traditional;
import org.aspectj.lang.ProceedingJoinPoint;
// aspect
aspect Trace2 {
// pointcut:
// public visibility, any return type, class traditional.IntMath, any method name, any arguments
// public visibility, any return type, class traditional.DoubleMath, any method name, any arguments
pointcut mathtrace() : call(public * traditional.IntMath.*(..)) || call(public * traditional.DoubleMath.*(..));
// advice:
// around
Object around() : mathtrace() {
System.out.println("AOP> Enter " + thisJoinPoint.getSignature());
Object res = null;
try {
res = proceed();
} catch(Throwable e) {
throw (RuntimeException)e;
}
System.out.println("AOP> Exit " + thisJoinPoint.getSignature());
return res;
}
}
Use:
C:\Work\AOP\traditional>ajc -source 1.7 *.java Trace2.aj
C:\Work\AOP\traditional>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar traditional.Test
Start
AOP> Enter int traditional.IntMath.add(int, int)
AOP> Exit int traditional.IntMath.add(int, int)
2+3=5
AOP> Enter double traditional.DoubleMath.multiply(double, double)
AOP> Exit double traditional.DoubleMath.multiply(double, double)
2.0*3.0=6.0
AOP> Enter int traditional.IntMath.divide(int, int)
Exception: / by zero
End
Trace1.aj:
package annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.AfterReturning;
// aspect
@Aspect
public class Trace1 {
// pointcut:
// public visibility, any return type, class annotation.IntMath, any method name, any arguments
// public visibility, any return type, class annotation.DoubleMath, any method name, any arguments
@Pointcut("call(public * annotation.IntMath.*(..)) || call(public * annotation.DoubleMath.*(..))")
public void mathtrace() { };
// advice:
// before
@Before("mathtrace()")
public void enter(JoinPoint thisJoinPoint) {
System.out.println("AOP> Enter " + thisJoinPoint.getSignature());
}
// advice:
// after returning
@AfterReturning("mathtrace()")
public void leave(JoinPoint thisJoinPoint) {
System.out.println("AOP> Exit " + thisJoinPoint.getSignature());
}
}
Use:
C:\Work\AOP\annotation>ajc -source 1.7 *.java Trace1.aj
C:\Work\AOP\annotation>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar annotation.Test
Start
AOP> Enter int annotation.IntMath.add(int, int)
AOP> Exit int annotation.IntMath.add(int, int)
2+3=5
AOP> Enter double annotation.DoubleMath.multiply(double, double)
AOP> Exit double annotation.DoubleMath.multiply(double, double)
2.0*3.0=6.0
AOP> Enter int annotation.IntMath.divide(int, int)
Exception: / by zero
End
Trace2.aj:
package annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;
// aspect
@Aspect
public class Trace2 {
// pointcut:
// public visibility, any return type, class annotation.IntMath, any method name, any arguments
// public visibility, any return type, class annotation.DoubleMath, any method name, any arguments
@Pointcut("call(public * annotation.IntMath.*(..)) || call(public * annotation.DoubleMath.*(..))")
public void mathtrace() { };
// advice:
// around
@Around("mathtrace()")
public Object trace(ProceedingJoinPoint pjp) {
System.out.println("AOP> Enter " + pjp.getSignature());
Object res = null;
try {
res = pjp.proceed();
} catch(Throwable e) {
throw (RuntimeException)e;
}
System.out.println("AOP> Exit " + pjp.getSignature());
return res;
}
}
Use:
C:\Work\AOP\annotation>ajc -source 1.7 *.java Trace2.aj
C:\Work\AOP\annotation>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar annotation.Test
Start
AOP> Enter int annotation.IntMath.add(int, int)
AOP> Exit int annotation.IntMath.add(int, int)
2+3=5
AOP> Enter double annotation.DoubleMath.multiply(double, double)
AOP> Exit double annotation.DoubleMath.multiply(double, double)
2.0*3.0=6.0
AOP> Enter int annotation.IntMath.divide(int, int)
Exception: / by zero
End
Trace.cs:
using System;
using DotNetGuru.AspectDNG.Joinpoints;
namespace OldStuff
{
// aspect
public class Trace
{
// advice:
// around
// pointcut:
// any return type, class OldStuff.IntMath, any method name, any arguments
// any return type, class OldStuff.DoubleMath, any method name, any arguments
[AroundCall("* OldStuff.IntMath::*(*)")]
[AroundCall("* OldStuff.DoubleMath::*(*)")]
public static object MathTrace(MethodJoinPoint mjp)
{
Console.WriteLine("AOP> Enter " + mjp);
object res = mjp.Proceed();
Console.WriteLine("AOP> Exit " + mjp);
return res;
}
}
}
Use:
C:\Work\AOP\aspectdng>csc /r:aspectdng.exe Test.cs IntMath.cs DoubleMath.cs Trace.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.5483
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
C:\Work\AOP\aspectdng>aspectdng Test.exe
C:\Work\AOP\aspectdng>Test
Start?
AOP> Enter ?static method OldStuff.IntMath::Add(2,3)
AOP> Exit ?static method OldStuff.IntMath::Add(2,3)
2+3=?5
AOP> Enter ?static method OldStuff.DoubleMath::Multiply(2,3)
AOP> Exit ?static method OldStuff.DoubleMath::Multiply(2,3)
2.0*3.0=?6
AOP> Enter ?static method OldStuff.IntMath::Divide(10,0)
Exception: ?Attempted to divide by zero.
End?
Note that extra characters show up in some of the strings.
Trace1.cs:
using System;
namespace NewStuff
{
// aspect
public aspect Trace1
{
// pointcut:
// any return type, class NewStuff.IntMath, any method name, any arguments
// any return type, class NewStuff.DoubleMath, any method name, any arguments
pointcut MathTrace() : execution(public static any NewStuff.IntMath.any(..)) || execution(public static any NewStuff.DoubleMath.any(..));
// advice:
// before
before() : MathTrace()
{
Console.WriteLine("AOP> Enter " + thisJoinPoint.Signature);
}
// advice:
// after
after() : MathTrace()
{
Console.WriteLine("AOP> Enter " + thisJoinPoint.Signature);
}
}
}
Use:
C:\Work\AOP\eos>eos Test.cs IntMath.cs DoubleMath.cs Trace1.cs
Eos Compiler Version 0.3
Copyright (c) 2005, Iowa State University of Science and Technology.
Copyright (C) 2004, The Rector and Visitors of the University of Virginia.
All rights reserved.
C:\Work\AOP\eos>Test
Start
AOP> Enter 262148 System.Int32 NewStuff.IntMath.Add( System.Int32a System.Int32b )
AOP> Enter 262148 System.Int32 NewStuff.IntMath.Add( System.Int32a System.Int32b )
2+3=5
AOP> Enter 262148 System.Double NewStuff.DoubleMath.Multiply( System.Doublea System.Doubleb )
AOP> Enter 262148 System.Double NewStuff.DoubleMath.Multiply( System.Doublea System.Doubleb )
2.0*3.0=6
AOP> Enter 262148 System.Int32 NewStuff.IntMath.Divide( System.Int32a System.Int32b )
AOP> Enter 262148 System.Int32 NewStuff.IntMath.Divide( System.Int32a System.Int32b )
Exception: Attempted to divide by zero.
End
Trace2.cs:
using System;
namespace NewStuff
{
// aspect
public aspect Trace2
{
// pointcut:
// any return type, class NewStuff.IntMath, any method name, any arguments
// any return type, class NewStuff.DoubleMath, any method name, any arguments
pointcut MathTrace() : execution(public static any NewStuff.IntMath.any(..)) || execution(public static any NewStuff.DoubleMath.any(..));
// advice:
// around
object around() : MathTrace() && aroundptr(adp)
{
Console.WriteLine("AOP> Enter " + thisJoinPoint.Signature);
object res = adp.InnerInvoke();
Console.WriteLine("AOP> Enter " + thisJoinPoint.Signature);
return res;
}
}
}
Use:
C:\Work\AOP\eos>eos Test.cs IntMath.cs DoubleMath.cs Trace2.cs
Eos Compiler Version 0.3
Copyright (c) 2005, Iowa State University of Science and Technology.
Copyright (C) 2004, The Rector and Visitors of the University of Virginia.
All rights reserved.
C:\Work\AOP\eos>Test
Start
AOP> Enter 262148 System.Int32 NewStuff.IntMath.Add( System.Int32a System.Int32b )
AOP> Enter 262148 System.Int32 NewStuff.IntMath.Add( System.Int32a System.Int32b )
2+3=5
AOP> Enter 262148 System.Double NewStuff.DoubleMath.Multiply( System.Doublea System.Doubleb )
AOP> Enter 262148 System.Double NewStuff.DoubleMath.Multiply( System.Doublea System.Doubleb )
2.0*3.0=6
AOP> Enter 262148 System.Int32 NewStuff.IntMath.Divide( System.Int32a System.Int32b )
Exception: Exception has been thrown by the target of an invocation.
End
Trace.cs:
using System;
using Vajhoej.StaticWeaver;
namespace MyStuff
{
// aspect
[Aspect]
public class Trace
{
// pointcut:
// any return type, class MyStuff.IntMath, any method name, any arguments
// any return type, class MyStuff.DoubleMath, any method name, any arguments
[Pointcut(Signature="* MyStuff.IntMath::*(*) || * MyStuff.DoubleMath::*(*)")]
public static void MathTrace() { }
// advice:
// before
[BeforeCallAdvice(Pointcut="MathTrace")]
public static void Enter(MethodJoinpoint mjp)
{
Console.WriteLine("AOP> Enter " + mjp);
}
// advice:
// after
[AfterCallAdvice(Pointcut="MathTrace")]
public static void Leave(MethodJoinpoint mjp)
{
Console.WriteLine("AOP> Exit " + mjp);
}
}
}
Use:
C:\Work\AOP\yaaopf>csc Test.cs IntMath.cs DoubleMath.cs
Microsoft (R) Visual C# Compiler version 2.0.0.61501
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Work\AOP\yaaopf>csc /r:StaticWeaver.dll /t:library Trace.cs
Microsoft (R) Visual C# Compiler version 2.0.0.61501
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Work\AOP\yaaopf>yaaopf1 Test.exe Trace.dll TestTraceGlue.dll
C:\Work\AOP\yaaopf>Test
Start
AOP> Enter MyStuff.IntMath.Add(2,3)
AOP> Exit MyStuff.IntMath.Add(2,3)
2+3=5
AOP> Enter MyStuff.DoubleMath.Multiply(2,3)
AOP> Exit MyStuff.DoubleMath.Multiply(2,3)
2.0*3.0=6
AOP> Enter MyStuff.IntMath.Divide(10,0)
Exception: Attempted to divide by zero.
End
Exception handling:
Error1.aj:
package traditional;
// aspect
aspect Error1 {
// pointcut:
// public visibility, any return type, class traditional.IntMath, any method name, any arguments
// public visibility, any return type, class traditional.DoubleMath, any method name, any arguments
pointcut matherror() : call(public * traditional.IntMath.*(..)) || call(public * traditional.DoubleMath.*(..));
// advice:
// after throwing
after() throwing (Throwable e) : matherror() {
System.out.println("AOP> Exception in math calculation: " + e.getMessage());
System.exit(0);
}
}
Use:
C:\Work\AOP\traditional>ajc -source 1.7 *.java Error1.aj
C:\Work\AOP\traditional>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar traditional.Test
Start
2+3=5
2.0*3.0=6.0
AOP> Exception in math calculation: / by zero
Error2.aj:
package traditional;
// aspect
aspect Error2 {
// pointcut:
// public visibility, any return type, class traditional.IntMath, any method name, any arguments
// public visibility, any return type, class traditional.DoubleMath, any method name, any arguments
pointcut matherror() : call(public * traditional.IntMath.*(..)) || call(public * traditional.DoubleMath.*(..));
// advice:
// around
Object around() : matherror() {
Object res = null;
try {
res = proceed();
} catch(Throwable e) {
System.out.println("AOP> Exception in math calculation: " + e.getMessage());
System.exit(0);
}
return res;
}
}
Use:
C:\Work\AOP\traditional>ajc -source 1.7 *.java Error2.aj
C:\Work\AOP\traditional>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar traditional.Test
Start
2+3=5
2.0*3.0=6.0
AOP> Exception in math calculation: / by zero
Error1.aj:
package annotation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.AfterThrowing;
// aspect
@Aspect
public class Error1 {
// pointcut:
// public visibility, any return type, class annotation.IntMath, any method name, any arguments
// public visibility, any return type, class annotation.DoubleMath, any method name, any arguments
@Pointcut("call(public * annotation.IntMath.*(..)) || call(public * annotation.DoubleMath.*(..))")
void matherror() { };
// advice:
// after throwing
@AfterThrowing(pointcut="matherror()", throwing="e")
public void oops(Throwable e) {
System.out.println("AOP> Exception in math calculation: " + e.getMessage());
System.exit(0);
}
}
Use:
C:\Work\AOP\annotation>ajc -source 1.7 *.java Error1.aj
C:\Work\AOP\annotation>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar annotation.Test
Start
2+3=5
2.0*3.0=6.0
AOP> Exception in math calculation: / by zero
Error2.aj:
package annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;
// aspect
@Aspect
public class Error2 {
// pointcut:
// public visibility, any return type, class annotation.IntMath, any method name, any arguments
// public visibility, any return type, class annotation.DoubleMath, any method name, any arguments
@Pointcut("call(public * annotation.IntMath.*(..)) || call(public * annotation.DoubleMath.*(..))")
void matherror() { };
// advice:
// around
@Around("matherror()")
public Object oops(ProceedingJoinPoint pjp) {
Object res = null;
try {
res = pjp.proceed();
} catch(Throwable e) {
System.out.println("AOP> Exception in math calculation: " + e.getMessage());
System.exit(0);
}
return res;
}
}
Use:
C:\Work\AOP\annotation>ajc -source 1.7 *.java Error2.aj
C:\Work\AOP\annotation>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar annotation.Test
Start
2+3=5
2.0*3.0=6.0
AOP> Exception in math calculation: / by zero
Error.cs:
using System;
using DotNetGuru.AspectDNG.Joinpoints;
namespace OldStuff
{
// aspect
public class Error
{
// advice:
// around
// pointcut:
// any return type, class OldStuff.IntMath, any method name, any arguments
// any return type, class OldStuff.DoubleMath, any method name, any arguments
[AroundCall("* OldStuff.IntMath::*(*)")]
[AroundCall("* OldStuff.DoubleMath::*(*)")]
public static object MathError(MethodJoinPoint mjp)
{
try
{
return mjp.Proceed();
}
catch(Exception e)
{
Console.WriteLine("AOP> Exception in math calculation: " + e.Message);
Environment.Exit(0);
return null;
}
}
}
}
Use:
C:\Work\AOP\aspectdng>csc /r:aspectdng.exe Test.cs IntMath.cs DoubleMath.cs Error.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.5483
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
C:\Work\AOP\aspectdng>aspectdng Test.exe
C:\Work\AOP\aspectdng>Test
Start?
2+3=?5
2.0*3.0=?6
AOP> Exception in math calculation: ?Attempted to divide by zero.
Note that extra characters show up in some of the strings.
Error1.cs:
using System;
namespace NewStuff
{
// aspect
public aspect Error1
{
// pointcut:
// any Execption, within NewStuff.any
pointcut MathError() : handler(Exception) && within(NewStuff.any);
// advice:
// before
before() : MathError()
{
Console.WriteLine("AOP> Exception in math calculation: " + ((Exception)thisJoinPoint.Args[0]).Message);
Environment.Exit(0);
}
}
}
Use:
C:\Work\AOP\eos>eos Test.cs IntMath.cs DoubleMath.cs Error1.cs
Eos Compiler Version 0.3
Copyright (c) 2005, Iowa State University of Science and Technology.
Copyright (C) 2004, The Rector and Visitors of the University of Virginia.
All rights reserved.
C:\Work\AOP\eos>Test
Start
2+3=5
2.0*3.0=6
AOP> Exception in math calculation: Attempted to divide by zero.
Error2.cs:
using System;
namespace NewStuff
{
// aspect
public aspect Error2
{
// pointcut:
// any return type, class NewStuff.IntMath, any method name, any arguments
// any return type, class NewStuff.DoubleMath, any method name, any arguments
pointcut MathError() : execution(public static any NewStuff.IntMath.any(..)) || execution(public static any NewStuff.DoubleMath.any(..));
// advice:
// around
object around() : MathError() && aroundptr(adp)
{
try
{
return adp.InnerInvoke();
}
catch(Exception e)
{
Console.WriteLine("AOP> Exception in math calculation: " + ((Exception)thisJoinPoint.Args[0]).Message);
Environment.Exit(0);
return null;
}
}
}
}
Use:
C:\Work\AOP\eos>eos Test.cs IntMath.cs DoubleMath.cs Error2.cs
Eos Compiler Version 0.3
Copyright (c) 2005, Iowa State University of Science and Technology.
Copyright (C) 2004, The Rector and Visitors of the University of Virginia.
All rights reserved.
C:\Work\AOP\eos>Test
Start
2+3=5
2.0*3.0=6
Exception: Exception has been thrown by the target of an invocation.
End
This example does not work as intended. Most likely because I do not fully understand Eos.
Error.cs:
using System;
using Vajhoej.StaticWeaver;
namespace MyStuff
{
// aspect
[Aspect]
public class Error
{
// pointcut:
// any return type, class MyStuff.IntMath, any method name, any arguments
// any return type, class MyStuff.DoubleMath, any method name, any arguments
[Pointcut(Signature="* MyStuff.IntMath::*(*) || * MyStuff.DoubleMath::*(*)")]
public static void MathError() { }
// advice:
// at exception
[AtExceptionAdvice(Pointcut="MathError")]
public static void Enter(MethodJoinpoint mjp)
{
Console.WriteLine("AOP> Exception in math calculation: " + mjp.CaugthException.Message);
Environment.Exit(0);
}
}
}
Use:
C:\Work\AOP\yaaopf>csc Test.cs IntMath.cs DoubleMath.cs
Microsoft (R) Visual C# Compiler version 2.0.0.61501
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Work\AOP\yaaopf>csc /r:StaticWeaver.dll /t:library Error.cs
Microsoft (R) Visual C# Compiler version 2.0.0.61501
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Work\AOP\yaaopf>yaaopf1 Test.exe Error.dll TestErrorGlue.dll
C:\Work\AOP\yaaopf>Test
Start
2+3=5
2.0*3.0=6
AOP> Exception in math calculation: Attempted to divide by zero.
Special checks:
Check1.aj:
package traditional;
// aspect
aspect Check1 {
// pointcut:
// public visibility, any return type, any class, divide method, two arguments any type
pointcut mathcheck() : call(public * *.*.divide(*, *));
// advice:
// before
before() : mathcheck() {
Object o = thisJoinPoint.getArgs()[1];
if(o instanceof Integer) {
Integer oi = (Integer)o;
if(oi.intValue() == 0) {
System.out.println("AOP> About to divide by integer zero");
}
} else if(o instanceof Double) {
Double od = (Double)o;
if(od.doubleValue() == 0.0) {
System.out.println("AOP> About to divide by double zero");
}
} else {
throw new RuntimeException("Unsupported data type in divide: " + o.getClass().getName());
}
}
}
Use:
C:\Work\AOP\traditional>ajc -source 1.7 *.java Check1.aj
C:\Work\AOP\traditional>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar traditional.Test
Start
2+3=5
2.0*3.0=6.0
AOP> About to divide by integer zero
Exception: / by zero
End
package traditional;
// aspect
aspect Check2 {
// pointcut:
// public visibility, any return type, any class, divide method, two arguments any type
pointcut mathcheck() : call(public * *.*.divide(*, *));
// advice:
// around
Object around() : mathcheck() {
Object o = thisJoinPoint.getArgs()[1];
if(o instanceof Integer) {
Integer oi = (Integer)o;
if(oi.intValue() == 0) {
System.out.println("AOP> About to divide by integer zero");
}
} else if(o instanceof Double) {
Double od = (Double)o;
if(od.doubleValue() == 0.0) {
System.out.println("AOP> About to divide by double zero");
}
} else {
throw new RuntimeException("Unsupported data type in divide: " + o.getClass().getName());
}
Object res = null;
try {
res = proceed();
} catch(Throwable e) {
throw (RuntimeException)e;
}
return res;
}
}
Check2.aj:
C:\Work\AOP\traditional>ajc -source 1.7 *.java Check2.aj
C:\Work\AOP\traditional>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar trad
itional.Test
Start
2+3=5
2.0*3.0=6.0
AOP> About to divide by integer zero
Exception: / by zero
End
Use:
Check1.aj:
package annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
// aspect
@Aspect
public class Check1 {
// pointcut:
// public visibility, any return type, any class, divide method, two arguments any type
@Pointcut("call(public * *.divide(*, *))")
public void mathcheck() { };
// advice:
// before
@Before("mathcheck()")
public void check(JoinPoint thisJoinPoint) {
Object o = thisJoinPoint.getArgs()[1];
if(o instanceof Integer) {
Integer oi = (Integer)o;
if(oi.intValue() == 0) {
System.out.println("AOP> About to divide by integer zero");
}
} else if(o instanceof Double) {
Double od = (Double)o;
if(od.doubleValue() == 0.0) {
System.out.println("AOP> About to divide by double zero");
}
} else {
throw new RuntimeException("Unsupported data type in divide: " + o.getClass().getName());
}
}
}
Use:
C:\Work\AOP\annotation>ajc -source 1.7 *.java Check1.aj
C:\Work\AOP\annotation>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar annotation.Test
Start
2+3=5
2.0*3.0=6.0
AOP> About to divide by integer zero
Exception: / by zero
End
Check2.aj:
package annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;
// aspect
@Aspect
public class Check2 {
// pointcut:
// public visibility, any return type, any class, divide method, two arguments any type
@Pointcut("call(public * *.divide(*, *))")
public void mathcheck() { };
// advice:
// around
@Around("mathcheck()")
public Object trace(ProceedingJoinPoint pjp) {
Object o = pjp.getArgs()[1];
if(o instanceof Integer) {
Integer oi = (Integer)o;
if(oi.intValue() == 0) {
System.out.println("AOP> About to divide by integer zero");
}
} else if(o instanceof Double) {
Double od = (Double)o;
if(od.doubleValue() == 0.0) {
System.out.println("AOP> About to divide by double zero");
}
} else {
throw new RuntimeException("Unsupported data type in divide: " + o.getClass().getName());
}
Object res = null;
try {
res = pjp.proceed();
} catch(Throwable e) {
throw (RuntimeException)e;
}
return res;
}
}
Use:
C:\Work\AOP\annotation>ajc -source 1.7 *.java Check2.aj
C:\Work\AOP\annotation>java -cp ..;C:\Eclipse\aspectj1.8\lib\aspectjrt.jar annotation.Test
Start
2+3=5
2.0*3.0=6.0
AOP> About to divide by integer zero
Exception: / by zero
End
Check.cs:
using System;
using DotNetGuru.AspectDNG.Joinpoints;
namespace OldStuff
{
// aspect
public class Check
{
// advice:
// around
// pointcut:
// any return type, any class, method name Divide, any arguments
[AroundCall("* OldStuff.*::Divide(*)")]
public static object MathCheck(MethodJoinPoint mjp)
{
object o = mjp[1];
if(o is int) {
if((int)o == 0)
{
Console.WriteLine("AOP> About to divide by integer zero");
}
} else if(o is double) {
if((double)o == 0)
{
Console.WriteLine("AOP> About to divide by double zero");
}
} else {
throw new Exception("Unsupported data type in divide: " + o.GetType().FullName);
}
return mjp.Proceed();
}
}
}
Use:
C:\Work\AOP\aspectdng>csc /r:aspectdng.exe Test.cs IntMath.cs DoubleMath.cs Check.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.5483
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
C:\Work\AOP\aspectdng>aspectdng Test.exe
C:\Work\AOP\aspectdng>Test
Start?
2+3=?5
2.0*3.0=?6
AOP> About to divide by integer zero?
Exception: ?Attempted to divide by zero.
End?
Note that extra characters show up in some of the strings.
Check1.cs:
using System;
namespace NewStuff
{
// aspect
public aspect Check1
{
// pointcut:
// any return type, any class MyStuff.*, Divide method, any arguments
pointcut MathCheck() : execution(public static any any.Divide(..));
// advice:
// before
before() : MathCheck()
{
object o = thisJoinPoint.Args[1];
if(o is int) {
if((int)o == 0)
{
Console.WriteLine("AOP> About to divide by integer zero");
}
} else if(o is double) {
if((double)o == 0)
{
Console.WriteLine("AOP> About to divide by double zero");
}
} else {
throw new Exception("Unsupported data type in divide: " + o.GetType().FullName);
}
}
}
}
Use:
C:\Work\AOP\eos>eos Test.cs IntMath.cs DoubleMath.cs Check1.cs
Eos Compiler Version 0.3
Copyright (c) 2005, Iowa State University of Science and Technology.
Copyright (C) 2004, The Rector and Visitors of the University of Virginia.
All rights reserved.
C:\Work\AOP\eos>Test
Start
2+3=5
2.0*3.0=6
AOP> About to divide by integer zero
Exception: Attempted to divide by zero.
End
Check2.cs:
using System;
namespace NewStuff
{
// aspect
public aspect Check2
{
// pointcut:
// any return type, any class MyStuff.*, Divide method, any arguments
pointcut MathCheck() : execution(public static any any.Divide(..));
// advice:
// around
object around() : MathCheck() && aroundptr(adp)
{
object o = thisJoinPoint.Args[1];
if(o is int) {
if((int)o == 0)
{
Console.WriteLine("AOP> About to divide by integer zero");
}
} else if(o is double) {
if((double)o == 0)
{
Console.WriteLine("AOP> About to divide by double zero");
}
} else {
throw new Exception("Unsupported data type in divide: " + o.GetType().FullName);
}
return adp.InnerInvoke();
}
}
}
Use:
C:\Work\AOP\eos>eos Test.cs IntMath.cs DoubleMath.cs Check2.cs
Eos Compiler Version 0.3
Copyright (c) 2005, Iowa State University of Science and Technology.
Copyright (C) 2004, The Rector and Visitors of the University of Virginia.
All rights reserved.
C:\Work\AOP\eos>Test
Start
2+3=5
2.0*3.0=6
AOP> About to divide by integer zero
Exception: Exception has been thrown by the target of an invocation.
End
This example does not work as intended. Most likely because I do not fully understand Eos.
Check.cs:
using System;
using Vajhoej.StaticWeaver;
namespace MyStuff
{
// aspect
[Aspect]
public class Check
{
// pointcut:
// any return type, any class MyStuff.*, Divide method, any arguments
[Pointcut(Signature="* MyStuff.*::Divide(*)")]
public static void MathCheck() { }
// advice:
// before
[BeforeCallAdvice(Pointcut="MathCheck")]
public static void Enter(MethodJoinpoint mjp)
{
object o = mjp.Arguments[1];
if(o is int) {
if((int)o == 0)
{
Console.WriteLine("AOP> About to divide by integer zero");
}
} else if(o is double) {
if((double)o == 0)
{
Console.WriteLine("AOP> About to divide by double zero");
}
} else {
throw new Exception("Unsupported data type in divide: " + o.GetType().FullName);
}
}
}
}
Use:
C:\Work\AOP\yaaopf>csc Test.cs IntMath.cs DoubleMath.cs
Microsoft (R) Visual C# Compiler version 2.0.0.61501
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Work\AOP\yaaopf>csc /r:StaticWeaver.dll /t:library Check.cs
Microsoft (R) Visual C# Compiler version 2.0.0.61501
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Work\AOP\yaaopf>yaaopf1 Test.exe Check.dll TestCheckGlue.dll
C:\Work\AOP\yaaopf>Test
Start
2+3=5
2.0*3.0=6
AOP> About to divide by integer zero
Exception: Attempted to divide by zero.
End
More advanced examples:
Note that I will only show these examples with AspectJ. AspectJ is by far the
most mature and feature rich AOP tool.
AOP with reflection and annotations:
The combination of:
- define annotations to declarative mark custom behavior in code
- use an AOP static weaver to inject code that use reflection to test for and
act on those annotations
can be a powerful tool.
Let me show an example how to weave in open and close of database connections.
This is clearly more IT functionality than business logic.
ManagedConnection.java:
package adv1;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ManagedConnection {
public String constr() default "";
public String un() default "";
public String pw() default "";
}
DatabaseMethod.java:
package adv1;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface DatabaseMethod {
}
DB.java:
package adv1;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DB {
@ManagedConnection(constr="jdbc:mysql://localhost/Test",un="root",pw="")
private Connection con;
@DatabaseMethod
public void q1() throws SQLException {
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT f2 FROM t1 WHERE f1=1");
if(rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
stmt.close();
}
@DatabaseMethod
public void q2() throws SQLException {
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT f2 FROM t1 WHERE f1=2");
if(rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
stmt.close();
}
@DatabaseMethod
public void q3() throws SQLException {
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT f2 FROM t1 WHERE f1=3");
if(rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
stmt.close();
}
}
Test.java:
package adv1;
public class Test {
public static void main(String[] args) throws Exception {
DB db = new DB();
db.q1();
db.q2();
db.q3();
}
}
DbTrick.aj:
package adv1;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
aspect DbTrick {
pointcut dbm() : call(@DatabaseMethod public * *.*(..));
Object around() throws SQLException : dbm() {
try {
Field f = thisJoinPoint.getTarget().getClass().getDeclaredField("con");
ManagedConnection mc = f.getAnnotation(ManagedConnection.class);
Connection con = null;
Object res;
try {
con = DriverManager.getConnection(mc.constr(), mc.un(), mc.pw());
f.setAccessible(true);
f.set(thisJoinPoint.getTarget(), con);
res = proceed();
} finally {
if(con != null) con.close();
}
return res;
} catch(NoSuchFieldException | IllegalAccessException ex) {
throw new RuntimeException(ex.getMessage());
}
}
}
AOP introductions:
AOP introductions is the ability to weave in "implements interface" / "extends class"
and new methods into an existing class. The new methods may or may not use reflection.
Let me show an example how to weave in reset and toString methods in bean classes.
This is clearly more IT functionality than business logic.
ABean.java:
package adv2;
public class ABean {
private int iv;
private double xv;
private String sv;
public int getIv() {
return iv;
}
public void setIv(int iv) {
this.iv = iv;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public String getSv() {
return sv;
}
public void setSv(String sv) {
this.sv = sv;
}
}
BBean.java:
package adv2;
public class BBean {
private int v;
public int getV() {
return v;
}
public void setV(int v) {
this.v = v;
}
}
MyBean.java:
package adv2;
public interface MyBean {
public void reset();
@Override
public String toString();
}
Test.java:
package adv2;
public class Test {
public static void main(String[] args) {
ABean a = new ABean();
BBean b = new BBean();
System.out.println(a + " " + b);
a.setIv(123);
a.setXv(123.456);
a.setSv("ABC");
b.setV(123456789);
System.out.println(a + " " + b);
a.reset();
b.reset();
System.out.println(a + " " + b);
}
}
BeanFix.aj:
package adv2;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
aspect BeanFix {
declare parents : adv2.*Bean implements MyBean;
public void MyBean.reset() {
try {
for(PropertyDescriptor pd : Introspector.getBeanInfo(getClass(), Object.class).getPropertyDescriptors()) {
Object dv;
if(pd.getPropertyType() == int.class) {
dv = 0;
} else if(pd.getPropertyType() == double.class) {
dv = 0.0;
} else {
dv = null;
}
pd.getWriteMethod().invoke(this, dv);
}
} catch(Exception ex) {
}
}
@Override
public String MyBean.toString() {
try {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append(getClass().getName());
for(PropertyDescriptor pd : Introspector.getBeanInfo(getClass(), Object.class).getPropertyDescriptors()) {
sb.append(",");
sb.append(pd.getName());
sb.append("=");
sb.append(pd.getReadMethod().invoke(this));
}
sb.append("}");
return sb.toString();
} catch(Exception ex) {
return ex.getMessage();
}
}
}
Dynamic weavers:
A big problem with dynamic weavers is how to make the weaving happen in a
non-intrusive way.
Replacing all occurrences of:
o = new X();
with:
o = GenerateProxyWithAspectsWeavedIn(new X());
is not acceptable.
But smart people has figured out that if the code is already using a Dependency Injection
framework to do instantiation, then the DI framework can also do the proxy generation.
Some DI framworks with support for AOP are:
- Spring for Java
- Castle Windsor for C#/.NET
In theory the .NET weavers can also be used for VB.NET code, but in practice .NET AOP developers are always using C#.
Spring:
Spring is a widely used DI framework for Java.
Spring supports AOP via embedded AspectJ.
Spring is used for DI as usual and AspectJ is used the same way as for static weaver (see
above) - aspects just need to add an annotation for Spring usage:
@Component("somename")
@Aspect
public class SomeAspect {
...
}
For a general intro to Spring DI see here.
Castle Windsor:
Castle is an advanced DI framework for .NET.
It has support for AOP via its dynamic proxy generation capabilities.
An aspect is defined as an interceptor:
public class SomeAspect : IInterceptor
{
public void Intercept(IInvocation inv)
{
...
}
}
The aspects need to be defined as other DI components:
cont.Register(Component.For<SomeAspect>());
And the interceptor must be attached to components it is to be applied to:
cont.Register(Component.For<SomeInterface>().ImplementedBy<SomeClass>().Interceptors(InterceptorReference.ForType<SomeAspect>()).Anywhere);
For a general intro to Castle Windsor DI see here.
Examples:
Main code:
We will use almost the same code as for static weaver - just with interfaces
and implementation classes to work with DI.
IntMath.java:
package dynamic;
public interface IntMath {
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);
}
IntMathImpl.java:
package dynamic;
import org.springframework.stereotype.Component;
@Component("intmath")
public class IntMathImpl implements IntMath {
@Override
public int add(int a, int b) {
return (a + b);
}
@Override
public int subtract(int a, int b) {
return (a - b);
}
@Override
public int multiply(int a, int b) {
return (a * b);
}
@Override
public int divide(int a, int b) {
return (a / b);
}
}
DoubleMath.java:
package dynamic;
public interface DoubleMath {
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
}
DoubleMathImpl.java:
package dynamic;
import org.springframework.stereotype.Component;
@Component("doublemath")
public class DoubleMathImpl implements DoubleMath {
@Override
public double add(double a, double b) {
return (a + b);
}
@Override
public double subtract(double a, double b) {
return (a - b);
}
@Override
public double multiply(double a, double b) {
return (a * b);
}
@Override
public double divide(double a, double b) {
return (a / b);
}
}
Test.java:
package dynamic;
public interface Test {
public void test();
}
TestImpl.java:
package dynamic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("test")
public class TestImpl implements Test {
@Autowired
private IntMath im;
@Autowired
private DoubleMath dm;
public void test() {
System.out.println("Start");
try {
System.out.println("2+3=" + im.add(2, 3));
System.out.println("2.0*3.0=" + dm.multiply(2.0, 3.0));
System.out.println("10/0=" + im.divide(10, 0));
} catch (Exception ex) {
System.out.println("Exception: " + ex.getMessage());
}
System.out.println("End");
}
}
EnableAOP.java:
package dynamic;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class EnableAOP {
}
Main.java:
package dynamic;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
@SuppressWarnings("resource")
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("dynamic");
ctx.refresh();
Test tst = ctx.getBean(Test.class);
tst.test();
}
}
Output:
Start
2+3=5
2.0*3.0=6.0
Exception: / by zero
End
IntMath.cs:
namespace Dynamic
{
public interface IIntMath
{
int Add(int a, int b);
int Subtract(int a, int b);
int Multiply(int a, int b);
int Divide(int a, int b);
}
public class IntMathImpl : IIntMath
{
public int Add(int a, int b)
{
return (a + b);
}
public int Subtract(int a, int b)
{
return (a - b);
}
public int Multiply(int a, int b)
{
return (a * b);
}
public int Divide(int a, int b)
{
return (a / b);
}
}
}
DoubleMath.cs:
namespace Dynamic
{
public interface IDoubleMath
{
double Add(double a, double b);
double Subtract(double a, double b);
double Multiply(double a, double b);
double Divide(double a, double b);
}
public class DoubleMathImpl : IDoubleMath
{
public double Add(double a, double b)
{
return (a + b);
}
public double Subtract(double a, double b)
{
return (a - b);
}
public double Multiply(double a, double b)
{
return (a * b);
}
public double Divide(double a, double b)
{
return (a / b);
}
}
}
Test.cs:
using System;
namespace Dynamic
{
public interface ITest
{
void Test();
}
public class TestImpl : ITest
{
private IIntMath im;
private IDoubleMath dm;
public TestImpl(IIntMath im, IDoubleMath dm)
{
this.im = im;
this.dm = dm;
}
public void Test()
{
Console.WriteLine("Start");
try
{
Console.WriteLine("2+3=" + im.Add(2, 3));
Console.WriteLine("2.0*3.0=" + dm.Multiply(2.0, 3.0));
Console.WriteLine("10/0=" + im.Divide(10, 0));
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.Message);
}
Console.WriteLine("End");
}
}
}
Main.cs:
using System;
using Castle.Core;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
namespace Dynamic
{
public class Program
{
public static void Main(string[] args)
{
WindsorContainer cont = new WindsorContainer();
// only enable aspects wanted
cont.Register(Component.For<Trace>());
cont.Register(Component.For<Error>());
cont.Register(Component.For<Check>());
cont.Register(Component.For<ITest>().ImplementedBy<TestImpl>());
cont.Register(Component.For<IIntMath>().ImplementedBy<IntMathImpl>().Interceptors(InterceptorReference.ForType<Trace>(),
InterceptorReference.ForType<Error>(),
InterceptorReference.ForType<Check>()).Anywhere);
cont.Register(Component.For<IDoubleMath>().ImplementedBy<DoubleMathImpl>().Interceptors(InterceptorReference.ForType<Trace>(),
InterceptorReference.ForType<Error>(),
InterceptorReference.ForType<Check>()).Anywhere);
ITest tst = cont.Resolve<ITest>();
tst.Test();
}
}
}
Output:
Start
2+3=5
2.0*3.0=6
Exception: Attempted to divide by zero.
End
Trace:
Trace1.java:
package dynamic;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.AfterReturning;
// work with Spring DI
@Component("trace1")
// aspect
@Aspect
public class Trace1 {
// pointcut:
// public visibility, any return type, class dynamic.IntMath, any method name, any arguments
// public visibility, any return type, class dynamic.DoubleMath, any method name, any arguments
@Pointcut("execution(public * dynamic.IntMath.*(..)) || execution(public * dynamic.DoubleMath.*(..))")
public void mathtrace() { };
// advice:
// before
@Before("mathtrace()")
public void enter(JoinPoint thisJoinPoint) {
System.out.println("AOP> Enter " + thisJoinPoint.getSignature());
}
// advice:
// after returning
@AfterReturning("mathtrace()")
public void leave(JoinPoint thisJoinPoint) {
System.out.println("AOP> Exit " + thisJoinPoint.getSignature());
}
}
Output:
Start
AOP> Enter int dynamic.IntMath.add(int,int)
AOP> Exit int dynamic.IntMath.add(int,int)
2+3=5
AOP> Enter double dynamic.DoubleMath.multiply(double,double)
AOP> Exit double dynamic.DoubleMath.multiply(double,double)
2.0*3.0=6.0
AOP> Enter int dynamic.IntMath.divide(int,int)
Exception: / by zero
End
Trace2.java:
package dynamic;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Around;
//work with Spring DI
@Component("trace2")
// aspect
@Aspect
public class Trace2 {
// pointcut:
// public visibility, any return type, class dynamic.IntMath, any method name, any arguments
// public visibility, any return type, class dynamic.DoubleMath, any method name, any arguments
@Pointcut("execution(public * dynamic.IntMath.*(..)) || execution(public * dynamic.DoubleMath.*(..))")
public void mathtrace() { };
// advice:
// around
@Around("mathtrace()")
public Object trace(ProceedingJoinPoint pjp) {
System.out.println("AOP> Enter " + pjp.getSignature());
Object res = null;
try {
res = pjp.proceed();
} catch(Throwable e) {
throw (RuntimeException)e;
}
System.out.println("AOP> Exit " + pjp.getSignature());
return res;
}
}
Output:
Start
AOP> Enter int dynamic.IntMath.add(int,int)
AOP> Exit int dynamic.IntMath.add(int,int)
2+3=5
AOP> Enter double dynamic.DoubleMath.multiply(double,double)
AOP> Exit double dynamic.DoubleMath.multiply(double,double)
2.0*3.0=6.0
AOP> Enter int dynamic.IntMath.divide(int,int)
Exception: / by zero
End
Trace.cs:
using System;
using Castle.DynamicProxy;
namespace Dynamic
{
// aspect
public class Trace : IInterceptor
{
// advice and pointcut defined in interceptor setup
public void Intercept(IInvocation inv)
{
Console.WriteLine("AOP> Enter " + inv.Method);
inv.Proceed();
Console.WriteLine("AOP> Exit " + inv.Method);
}
}
}
Output:
Start
AOP> Enter Int32 Add(Int32, Int32)
AOP> Exit Int32 Add(Int32, Int32)
2+3=5
AOP> Enter Double Multiply(Double, Double)
AOP> Exit Double Multiply(Double, Double)
2.0*3.0=6
AOP> Enter Int32 Divide(Int32, Int32)
Exception: Attempted to divide by zero.
End
Exception handling:
Error1.java:
package dynamic;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.AfterThrowing;
//work with Spring DI
@Component("error1")
// aspect
@Aspect
public class Error1 {
// pointcut:
// public visibility, any return type, class dynamic.IntMath, any method name, any arguments
// public visibility, any return type, class dynamic.DoubleMath, any method name, any arguments
@Pointcut("execution(public * dynamic.IntMath.*(..)) || execution(public * dynamic.DoubleMath.*(..))")
void matherror() { };
// advice:
// after throwing
@AfterThrowing(pointcut="matherror()", throwing="e")
public void oops(Throwable e) {
System.out.println("AOP> Exception in math calculation: " + e.getMessage());
System.exit(0);
}
}
Output:
Start
2+3=5
2.0*3.0=6.0
AOP> Exception in math calculation: / by zero
Error2.java:
package dynamic;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Around;
//work with Spring DI
@Component("error2")
// aspect
@Aspect
public class Error2 {
// pointcut:
// public visibility, any return type, class dynamic.IntMath, any method name, any arguments
// public visibility, any return type, class dynamic.DoubleMath, any method name, any arguments
@Pointcut("execution(public * dynamic.IntMath.*(..)) || execution(public * dynamic.DoubleMath.*(..))")
void matherror() { };
// advice:
// around
@Around("matherror()")
public Object oops(ProceedingJoinPoint pjp) {
Object res = null;
try {
res = pjp.proceed();
} catch(Throwable e) {
System.out.println("AOP> Exception in math calculation: " + e.getMessage());
System.exit(0);
}
return res;
}
}
Output:
Start
2+3=5
2.0*3.0=6.0
AOP> Exception in math calculation: / by zero
Error.cs:
using System;
using Castle.DynamicProxy;
namespace Dynamic
{
// aspect
public class Error : IInterceptor
{
// advice and pointcut defined in interceptor setup
public void Intercept(IInvocation inv)
{
try
{
inv.Proceed();
}
catch (Exception e)
{
Console.WriteLine("AOP> Exception in math calculation: " + e.Message);
Environment.Exit(0);
}
}
}
}
Output:
Start
2+3=5
2.0*3.0=6
AOP> Exception in math calculation: Attempted to divide by zero.
Special checks:
Check1.java:
package dynamic;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Before;
//work with Spring DI
@Component("check1")
// aspect
@Aspect
public class Check1 {
// pointcut:
// public visibility, any return type, any class, divide method, two arguments any type
@Pointcut("execution(public * *.divide(*, *))")
public void mathcheck() { };
// advice:
// before
@Before("mathcheck()")
public void check(JoinPoint thisJoinPoint) {
Object o = thisJoinPoint.getArgs()[1];
if(o instanceof Integer) {
Integer oi = (Integer)o;
if(oi.intValue() == 0) {
System.out.println("AOP> About to divide by integer zero");
}
} else if(o instanceof Double) {
Double od = (Double)o;
if(od.doubleValue() == 0.0) {
System.out.println("AOP> About to divide by double zero");
}
} else {
throw new RuntimeException("Unsupported data type in divide: " + o.getClass().getName());
}
}
}
Output:
Start
2+3=5
2.0*3.0=6.0
AOP> About to divide by integer zero
Exception: / by zero
End
Check2.java:
package dynamic;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Around;
//work with Spring DI
@Component("check2")
// aspect
@Aspect
public class Check2 {
// pointcut:
// public visibility, any return type, any class, divide method, two arguments any type
@Pointcut("execution(public * *.divide(*, *))")
public void mathcheck() { };
// advice:
// around
@Around("mathcheck()")
public Object trace(ProceedingJoinPoint pjp) {
Object o = pjp.getArgs()[1];
if(o instanceof Integer) {
Integer oi = (Integer)o;
if(oi.intValue() == 0) {
System.out.println("AOP> About to divide by integer zero");
}
} else if(o instanceof Double) {
Double od = (Double)o;
if(od.doubleValue() == 0.0) {
System.out.println("AOP> About to divide by double zero");
}
} else {
throw new RuntimeException("Unsupported data type in divide: " + o.getClass().getName());
}
Object res = null;
try {
res = pjp.proceed();
} catch(Throwable e) {
throw (RuntimeException)e;
}
return res;
}
}
Output:
Start
2+3=5
2.0*3.0=6.0
AOP> About to divide by integer zero
Exception: / by zero
End
Check.cs:
using System;
using Castle.DynamicProxy;
namespace Dynamic
{
// aspect
public class Check : IInterceptor
{
// advice and pointcut defined in interceptor setup
public void Intercept(IInvocation inv)
{
if(inv.Method.Name == "Divide")
{
object o = inv.GetArgumentValue(1);
if (o is int)
{
if ((int)o == 0)
{
Console.WriteLine("AOP> About to divide by integer zero");
}
}
else if (o is double)
{
if ((double)o == 0)
{
Console.WriteLine("AOP> About to divide by double zero");
}
}
else
{
throw new Exception("Unsupported data type in divide: " + o.GetType().FullName);
}
}
inv.Proceed();
}
}
}
Output:
Start
2+3=5
2.0*3.0=6
AOP> About to divide by integer zero
Exception: Attempted to divide by zero.
End
Is it any good?
Article history:
Version |
Date |
Description |
1.0 |
November 7th 2017 |
Initial version based on old articles (in Danish) on Eksperten.dk |
1.1 |
June 5th 2022 |
Add section on dynamic weavers |
Other articles:
See list of all articles here
Comments:
Please send comments to Arne Vajhøj
I believe that AOP is a very powerful and effetive tool for modern software development.
And personally I really like AspectJ to solve some problems in Java.
But there is also some arguments against AOP. It is a complex concept and understanding real world OO code with multiple aspects weaved in can be be difficult.
As a result then I will only recommend AOP for the most experienced developers working on core frameworks.
But even developers of normal business code should have a basic understanding of AOP, because the frameworks they use may use AOP.