AOP

Content:

  1. Introduction
  2. What is AOP?
  3. AOP concepts
  4. Usage
  5. Tools
    1. AspectJ
    2. AspectDNG
    3. Eos
    4. YAAOPF
  6. Examples
    1. Base code
    2. Trace
    3. Exception handling
    4. Special checks
  7. More advanced examples
    1. AOP with reflection and annotations
    2. AOP introductions
  8. 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:

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 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.

I will focus on static weavers because they are:

AOP was very hot around 2005-2010, but is a niche technology today.

But in my opinion a niche that any serious developer should know about.

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:

Tools:

Some static weavers are:

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:

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:

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:

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:

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:

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();
        }
    }
}

Is it any good?

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.

Article history:

Version Date Description
1.0 November 7th 2017 Initial version based on old articles (in Danish) on Eksperten.dk

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj