Spring AOP

Aspect-orientated programming is used to address cross-cutting concerns that lead to code scattering (boiler plate code by spreading the same concern across your application) and/or code tangling (neglection of single responsibility by coupling different concers). Examples for cross-cutting concerns are generic functionalities, such as logging or security checks before method execution.

Spring offers of AspectJ or Spring AOP. For the latter, normal Java-Code is written AspectJ on the other is more powerful (for example, aspects can only be woven around visible methods of Spring beans with Spring AOP). AspectJ uses byte code modification to weave in the aspects, Spring AOP relies on dynamic proxies.

Because of the proxy usage a method call from within the same class/interface will NOT trigger the advice.

AOP uses the concepts of

  • JoinPoint: What is affected: The place in your program where the concern will be applied to, e.g. the method call
  • Pointcut: Where is it applied: Expression to match application code for JoinPoints
  • Advice: What is the aspect’s concern: Code that is executed at a JoinPoint
  • AdviceType: When is the advice applied, @Before, @AfterThrowing, @Around, @AfterReturning, @After etc.
  • Aspect: Component to encapsulate pointcuts and advice

Implementing an aspect

To use AOP you need to have an aspect and use it as a bean. Here’s an example with a separate configuration:

Example aspect:

@Aspect
@Component
public class GenericLogger {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(GenericLogger.class);
    
    @Before("execution(* add*(..))")
    public void debugAddCall(JoinPoint joinPoint) {
        String joinPointName = joinPoint.getSignature().getName();
        Object joinPointArg0 = joinPoint.getArgs()[0];
        String targetType = joinPoint.getTarget().getClass().getSimpleName();    //not very practiable here, just to show how to access the target
        LOGGER.debug("Method {} about to be called on type {} with argument {}", joinPointName, targetType, joinPointArg0);
    }
}

Aspect configuration:

@ComponentScan("ch.pma.useradmin.logging") //package where to find the aspect-bean(s)
@EnableAspectJAutoProxy
public class LoggingConfiguration {
 
}

You then only need to the configuration into your main-configuration-class:

@Import(LoggingConfiguration.class)

The same in XML:

<aop:aspectj-autoproxy />
<context:component-scan base-package="..."/>

Exceptions

If you use @Before and the advice itself throws an exception, the target will not be called.

@After will be called regardless of whether the target threw an exception or not.

@AfterReturning, @AfterThrowing and @Around

If you use @AfterReturning, you can access the returned value by defining it in the annotation and as an argument:

@AfterReturning(value="execution(...)", returning="returnedObject")
public void debugReturnedObject(ReturnedObjectType returnedObject) {
    ...
}

With @AfterThrowing, the case for a thrown exception is similar:

@AfterThrowing(value="execution(...)", throwing="exception")
public void logException(ExceptionType exception) {
    ...
}

With the @Around advice, you pass a

ProceedingJoinPoint

as an argument to your advice on which you call

.proceed()

to call the method (or decide to skip it by not calling proceed()).

Pointcut expressions

An expression consists of a designator (normally “execution”) and a combination of annotation, return type, package, type, method and params to match.

You can use wildcards such as “*”, “**”, or “..”. Operators such as “||”, “&&” and “!” are supported as well.

Pointcut expressions might also match annotations:

@After("execution(@ch.pma.useradmin.annotation.ServiceMethod * *(..))")
public void debugServiceMethodCall() {
    LOGGER.debug("Service-method was called");
}

matches any method annotated with

@ServiceMethod

Alternatively, there is a designator for annotations:

@Before("execution(...) &amp;&amp; @annotation(serviceMethod)")
public void doSomething(ServiceMethod serviceMethod) {
    ...
}

The expression can also provide typesafe access to target, arguments and/or the proxy-object.

All the types do have to match or the advice will be skipped:

@Before("execution(void *.RepositoryService.*(java.util.Map)) &amp;&amp; target(instance) &amp;&amp; args(map) &amp;&amp; this(proxy)")
public void doSomething(RepositoryService instance, Map map, RepositoryService proxy)(
    ...
}

Remember that for the proxy, an interface will be implemented or a class extended, therefore it is possible to narrow the execution via definition of the type to be proxied.

@Pointcut

With the

@Pointcut

annotation you can break complex expressions into separate ones and reference them in the advices’ expression:

@Pointcut("execution(* package1.*.*(..))")
public void pointcut1() {}
 
@Pointcut("execution(* package2.*.*(..))")
public void pointcut2() {}
 
@Before("pointcut1() || pointcut2()")    //you could even fully-qualify your pointcut(s) here
public void doSomething(JoinPoint joinPoint) {
        LOGGER.debug("Method to be called on {}", joinPoint.getSignature().getName());
}

The expression can also provide typesafe access to target and arguments. If the types do not match, the advice will be skipped:

@Before("execution(void *.RepositoryService.*(java.util.Map)) &amp;&amp; target(instance) &amp;&amp; args(map) &amp;&amp; this(proxy)")
    public void doSomething(RepositoryService instance, Map map, RepositoryService proxy)(
        ...
    }

Bing Bong

Guest Author - Knows Java and Spring very well.

Latest posts by Bing Bong (see all)

Leave a Reply

Your email address will not be published. Required fields are marked *

*