CodeGym /Java Course /Module 5. Spring /Support @AspectJ

Support @AspectJ

Module 5. Spring
Level 5 , Lesson 1
Available

@AspectJ assumes a style of declaring aspects as regular Java classes annotated with annotations. The @AspectJ style was introduced by the AspectJ project as part of the AspectJ 5 release. Spring interprets the same annotations as AspectJ 5, using the library provided by AspectJ for parsing and matching slices. However, the AOP runtime is still pure Spring AOP, and there is no dependency on the AspectJ compiler or binding tool.

AspectJ compiler and binding tool , which allow you to use the AspectJ language fully.

Enabling @AspectJ support

To use @AspectJ aspects in a Spring configuration, you must enable Spring support to configure Spring AOP based on @AspectJ aspects and auto-proxy beans depending on whether the their advice on these aspects or not. By auto-proxying, we mean that if Spring detects that a bean has advice on one or more aspects, it automatically creates a proxy for that bean to intercept method calls and ensure that the advice is executed as needed.

Support @AspectJ can be activated using XML or Java style configuration. In any case, you need to make sure that the aspectjweaver.jar library from AspectJ is in your application's classpath (version 1.8 or later). This library is available in the lib directory of the AspectJ distribution or from the Maven Central repository.

Enabling @AspectJ support using the Java configuration

To enable @AspectJ support using @Configurationfrom Java, add the @EnableAspectJAutoProxy annotation as shown in the following example:

Java

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
Kotlin

@Configuration
@EnableAspectJAutoProxy
class AppConfig

Enabling @AspectJ support using XML configuration

To enable @AspectJ support using XML-based configuration, use the aop:aspectj element-autoproxy, as shown in the following example:

<aop:aspectj-autoproxy/>

Aspect declaration

When With @AspectJ support enabled, any bean defined in your application context with a class that is an @AspectJ aspect (has the @Aspect annotation) is automatically defined by Spring and used to configure Spring AOP. The following two examples demonstrate the simple definition needed for a not-so-useful aspect.

The first of the two examples shows a normal bean definition in application context, which points to a bean class that has the annotation @Aspect:


<bean id ="myAspect" class="org.xyz.NotVeryUsefulAspect">
        <!-- set up aspect properties here -->
</bean>

The second of two examples shows the definition of the class NotVeryUsefulAspect, which is annotated with the org.aspectj.lang.annotation.Aspect;

Java

package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
Kotlin

package org.xyz
import org.aspectj.lang.annotation .Aspect;
@Aspect
class NotVeryUsefulAspect

Aspects (classes annotated with @Aspect) can have methods and fields just like any other Class. They can also contain declarations of snapshots, tips and introductions (cross-type).

Automatic detection of aspects via bean scanning
You can register aspect classes as regular beans in a Spring XML configuration using methods annotated with @Bean, in classes annotated with the @Configuration annotation, or do so that Spring automatically discovers them through classpath scanning - just like any other managed Spring bean. However, note that the @Aspect annotation is not sufficient for automatic detection on the classpath. To do this, you need to add a separate @Component annotation (or, alternatively, a special stereotype annotation that matches the rules of the Spring component scanner).
Advising aspects using other aspects?
In Spring AOP, aspects themselves are not may be targets for supplying advice from other aspects. The @Aspect annotation on a class marks it as an aspect and therefore excludes it from autoproxy.

Declaring a slice

Slices define connection points and thus allow you to control the execution time of advice. Spring AOP only supports method execution join points for Spring beans, so you can think of a slice as the same as method execution for Spring beans. A slice declaration consists of two parts: a signature, which includes the name and any parameters, and a slice expression, which specifies which executions of the method we are interested in. In AOP style with the @AspectJ annotation, the slice signature is specified by the regular method definition, and the slice expression is specified using the @Pointcut annotation (the method serving as the slice signature must have a return type of void) .

An example may help clarify the difference between a slice signature and a slice expression. The following example defines a slice named anyOldTransfer that corresponds to the execution of any method named transfer:

Java

@Pointcut("execution(* transfer(..))") // cut expression
private void anyOldTransfer() {} // cut signature
Kotlin

@Pointcut("execution(* transfer(..))") // cut expression
private fun anyOldTransfer() {} // cut signature

The slice expression that produces the value of the @Pointcut annotation is a regular AspectJ slice expression. For an in-depth look at the AspectJ slicing language, see the AspectJ Programming Guide (and for extensions - AspectJ 5 Developer's Guide) or one from books on AspectJ (for example, Eclipse AspectJ by Collier et al. or AspectJ in Action by Ramniwas Laddad).

Supported slice pointers

Spring AOP supports the following AspectJ slice designators (pointcut designers/PCD) for use in slice expressions:

  • execution: To coordinate connection points when executing a method. This is the basic slicing indicator that should be used when working with Spring AOP.

  • within: Limits matching to join points within certain types (executing a method declared within matched type when using Spring AOP).

  • this: Limits matching to connection points (method execution when using Spring AOP) where the bean reference (proxy) Spring AOP) is an instance of the given type.

  • target: Restricts negotiation to connection points (method execution when using Spring AOP) where the target object (the object being proxied application) is an instance of the given type.

  • args: Restricts negotiation to join points (method execution when using Spring AOP) where the arguments are instances of the given types.

  • @target: Limits negotiation to connection points (method execution when using Spring AOP) where the class of the executing object has an annotation of a given type.

  • @args: Limits negotiation to connection points (method execution when using Spring AOP) where the runtime type of the actual arguments passed has annotations of the specified types.

  • @within: Limits negotiation to join points within types that have this annotation (executing methods declared on types with this annotation when using Spring AOP).

  • @annotation: Restricts negotiation to connection points where the connection point subject (the method executed in Spring AOP) has the given annotation.

Other pointer types

AspectJ's full-featured slicing language supports additional slice pointers that are not supported in Spring: call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution, withincode, cflow , cflowbelow, if, @this and @withincode. Using these slice pointers in slice expressions interpreted by Spring AOP results in an IllegalArgumentException.

The set of slice pointers supported by Spring AOP may be expanded in future releases to support more slice pointers from AspectJ.

Because Spring AOP limits negotiation to only method execution join points, the previous description of slice pointers gave a narrower definition than what you may find in AspectJ Programming Guide. Additionally, AspectJ itself has type-based semantics, and at the execution join point both this and target refer to the same object: the object executing the method. Spring AOP is a proxy-based system that distinguishes between the proxy object itself (which is bound to this) and the target behind the proxy (which is bound to target).

Due to the proxy-oriented nature of the AOP framework, Spring calls within the target object are not intercepted by definition. With a JDK proxy, you can only intercept method calls to the proxy's public interface. CGLIB intercepts calls to public and protected proxy methods (and even package-scoped methods, if necessary). However, general proxy interactions should always be framed using public signatures.

Note that slice definitions are typically matched to any intercepted method. If the slice is intended strictly for public use, even in a CGLIB proxy scenario with potential non-public interactions through the proxy, it should be defined accordingly.

If you also want to intercept method calls or even constructors within the target class, look into ability to use AspectJ's native binding driven by Spring instead of Spring's proxy-based AOP framework. Native binding is a different way to use AOP with different characteristics, so be sure to familiarize yourself with binding before making your decision.

Spring AOP also supports an additional PCD called bin . This PCD allows you to restrict connection point negotiation to specific named Spring beans or a set of named Spring beans (when using wildcards). The Bean PCD has the following form:

Java
bean(idOrNameOfBean)
div>
Kotlin
bean(idOrNameOfBean)

The idOrNameOfBean marker can be the name of any Spring bean. There is limited ability to use wildcards using the * character, so if you establish some naming conventions for your Spring beans, you can write a PCD bean expression to fetch them. As with other slice pointers, the PCD for bean can be used with the && (and), || (or) operators. and ! (negation).

PCD for Bean is only supported in Spring AOP, but not with native binding on AspectJ. This is a Spring-specific extension to the standard PCDs that AspectJ defines, and is therefore not available for aspects declared in a model annotated with @Aspect.

PCD for Bean works at the instance level (based on the concept of Spring bean names) and not just at the type level (which is what binding-based AOP is limited to). Instance-based slice pointers are a feature of Spring's proxy-based AOP framework, given its tight integration with the Spring bean factory, which allows you to identify specific beans by name in a natural and accessible way.

Combining slice expressions

You can combine slice expressions using &&, || and ! You can also reference slice expressions by name. The following example shows three slicing expressions:

Java

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} 
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 
  1. anyPublicOperation is negotiated if the method execution connection point is the execution of any public method.
  2. inTrading is negotiated if the method execution occurs in the trading module.
  3. tradingOperation is consistent if the method execution represents any public method in the trading module.
Kotlin

@Pointcut("execution(public * * (..))")
private fun anyPublicOperation() {} 
@Pointcut("within(com.xyz.myapp.trading.. *)")
private fun inTrading() {} 
@Pointcut("anyPublicOperation() && inTrading()")
private fun tradingOperation() {} 
  1. anyPublicOperation is consistent if the method execution join point is the execution of any public method.
  2. inTrading is agreed upon if the method execution occurs in the trading module.
  3. tradingOperation is agreed upon if the method execution represents any public method in the trading module.

A better way is to construct more complex slice expressions from smaller named components, as shown earlier.When accessing slices by name, normal Java visibility rules apply (you can see private slices in the same type, protected slices in a hierarchy, public slices in any location, etc.) Visibility does not affect slice negotiation.

Sharing common slice definitions

When working with enterprise applications, developers often need to reference application modules and specific sets of operations from several aspects. For this purpose, it is recommended to define a CommonPointcuts aspect that captures common slice expressions. This aspect usually resembles the following example:

Java

package com.xyz.myapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class CommonPointcuts {
    /**
    * The connection point is at the web interaction layer if a method
    * is defined on a type in the com.xyz.myapp.web package or any subpackage
    * below it.
    */
    @Pointcut("within(com.xyz.myapp.web..*)")
    public void inWebLayer() {}
    /**
    * The connection point is at the service level if a method is defined
    * in a type in the com.xyz package .myapp.service or any subpackage
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.service..*)")
    public void inServiceLayer() {}
    /**
    * The connection point is in the data access layer if a method is defined
    * in a type from package com .xyz.myapp.dao or any subpackage
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    public void inDataAccessLayer() {}
    /**
    * A business service is the execution of any method defined in the service.
    * interface. This definition assumes that interfaces are located in
    * the "service" package, and that implementation types are in subpackages.
    *
    * If you group service interfaces by functional area (for example,
    * in the com.xyz.myapp.abc.service and com.xyz.myapp.def.service packages) then
    * the slice expression "execution(* com.xyz.myapp. .service.*.*(...))".
    * can be used instead.
    *
    * Alternatively, you can write the expression using a "bean-oriented"
    * PCD, for example, "bean(*Service)". (This assumes that you
    * named your Spring service beans in a consistent manner).
    */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    public void businessService() {}
    /**
    * A data access operation is the execution of any method defined for
    * dao interface. This definition assumes that interfaces are located in
    * the "dao" package, and that implementation types are in subpackages.
    */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    public void dataAccessOperation() {}
}
Kotlin

package com.xyz.myapp
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
@Aspect
class CommonPointcuts {
    /**
    * The connection point is at the web interface layer if a method is defined
    * in a type in the com.xyz.myapp.web package or any subpackage
    * below that.
    */
    @Pointcut("within(com.xyz.myapp.web..*)")
    fun inWebLayer() {
    }
    /**
    * The connection point is at the services layer if a method is defined
    * on a type in the com.xyz package. myapp.service or any subpackage
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.service..*)")
    fun inServiceLayer() {
    }
    /**
    * The connection point is in the data access layer if a method
    * is defined on a type from package com.xyz.myapp.dao or any subpackage
    * under it.
    */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    fun inDataAccessLayer() {
    }
    /**
    * A business service is the execution of any method defined in the service.
    * interface. This definition assumes that interfaces are located in
    * the "service" package, and that implementation types are in subpackages.
    *
    * If you group service interfaces by functional area (for example,
    * in the com.xyz.myapp.abc.service and com.xyz.myapp.def.service packages) then
    * the slice expression "execution(* com.xyz.myapp..service.*.*(...))".
    * can be used instead.
    *
    * Alternatively, you can write the expression using a "bean-oriented"
    * PCD, for example, "bean(*Service)". (This assumes that you
    * named your Spring service beans in a consistent manner).
    */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    fun businessService() {
    }
    /**
    * A data access operation is the execution of any method defined for
    * dao interface. This definition assumes that interfaces are located in
    * the "dao" package, and that implementation types are in subpackages.
    */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    fun dataAccessOperation() {
    }
}

You can reference slices defined in such an aspect wherever you need to use a slice expression. For example, to make the service layer transactional, you could write the following:


<aop:config>
    <aop:advisor
            pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
            advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

Examples

Spring AOP users most often use the execution slicer. The format of an expression with the execution pointer is as follows:

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                throws-pattern?)

All parts except return type pattern(ret-type-pattern in the previous snippet), name pattern and parameter pattern are optional. The return type pattern specifies what the method's return type must be for the join point to match. The most commonly used return type pattern is *. It matches any return type. The fully qualified type name matches only if the method returns that type. The name pattern matches the method name. The * wildcard can be used in a name pattern either in full or in part. If you specify a declaration type template, include a trailing . to append it to the name template component. The parameters pattern is a little more complex: () matches a method that takes no parameters, while (..) matches any number (zero or more) parameters. The (*) pattern matches a method that takes one parameter of any type. (*,String) corresponds to a method that takes two parameters. The first can be of any type, and the second must be a String.

The following examples show some common slice expressions:

  • Execute any public method:

        execution(public * *(..))
  • Execute any method whose name begins with set:

        execution(* set*(..))
  • Execute any method defined by the AccountService interface:

        execution(* com.xyz.service.AccountService.*(..))
  • Execute any method defined in the service package:

        execution(* com.xyz.service.*.*(..))
  • Execute any method defined in a service package or one of its subpackages :

        execution(* com.xyz.service..*.* (..))
  • Any connection point (method execution in Spring AOP only) within a service package:

        within(com.xyz.service.*)
  • Any junction point (method execution in Spring AOP only) within a service package or one of its subpackages:

        within(com.xyz.service..*)
  • Any connection point (method execution in Spring AOP only) where the proxy implements the AccountService interface:

        this(com.xyz.service.AccountService)
    this is most often used in bindable form.
  • Any connection point (method execution in Spring AOP only) where the target implements the AccountService interface:

        target(com.xyz.service.AccountService)
    target is most often used in bindable form.
  • Any connection point (method execution in Spring AOP only) that takes one parameter and where the argument is passed at runtime is Serializable:

        args(java.io.Serializable)
    args is most often used in a bound form.

    Note that the one given in this example slice is different from execution(* * *(java.io.Serializable)). The "args" option is matched if the argument passed at runtime is Serializable, and the "execution" option is matched if the method signature declares a single parameter of type Serializable.

  • Any connection point (method execution in Spring AOP only) where the target object has the @Transactional annotation:

        @target(org.springframework.transaction.annotation.Transactional)
    You can also use @target in a bound form.
  • Any join point (method execution only in Spring AOP) where the declared target type has the annotation @Transactional:

        @within(org.springframework.transaction.annotation.Transactional)
    You can also use @within in a bound form.
  • Any join point (method execution in Spring AOP only) where the executing method has the annotation @Transactional:

        @annotation(org.springframework.transaction.annotation.Transactional)
    You can also use @annotation in a bindable form.
  • Any connection point (method execution in Spring AOP only) that takes one parameter, and where the type of the argument passed has the @Classified annotation:

        @args(com.xyz.security.Classified)
    You can also use @args in a bound form.
  • Any join point (method execution in Spring AOP only) for Spring beans named tradeService:

        bean(tradeService)
  • Any connection point (method execution only in Spring AOP) for Spring beans whose names match the wildcard expression *Service:

        bean(*Service)

Writing good slices

During compilation AspectJ processes slices to optimize reconciliation efficiency. Examining the code and determining whether each join point corresponds (statically or dynamically) to a given slice is a resource-intensive process. (Dynamic matching means that matching cannot be completely determined through static analysis and that a test is placed in the code to determine the actual matching when the code is executed.) When AspectJ first encounters a slice declaration, it rewrites it in a form that is optimal for the matching procedure. What does this mean? Essentially, the slices are rewritten in DNF (disjunctive normal form) and the components of the slice are sorted in such a way that those components that can be computed with less resources are tested first. This means that you don't have to worry about describing how the various slice pointers work and can be used in any order in the slice declaration.

However, AspectJ can only do what it is told to do. For optimal matching performance, you should think about the end goal and narrow the search space for matches in the definition as much as possible. Existing pointers naturally belong to one of three groups: generic, definitional and contextual:

  • Generic pointers select a specific type of connection point: execution, get, set, call or handler.

  • Definitive pointers select the desired group of connection points (possibly from many genera): within and withincode

  • Contextual pointers are matched (and optionally bound) based on context: this, target and @annotation

A well-written section should include at least the first two types of indicators (generic and attributive). You can include contextual pointers for matching based on the context of the join point, or bind that context for use in a tip. Specifying only the generic or only the contextual pointer will work, but may affect the complexity of performing the linking (time and memory usage) due to additional processing and parsing. Definition pointers are extremely fast to match, and their use means that AspectJ can very quickly weed out groups of join points that should not be processed further. A good slice should always include one of these if possible.

Declaring Tips

A tip is associated with a slice expression and is executed before, after, or instead of executing a method that matches the slice. A slice expression can be either a simple reference to a named slice or a direct slice expression.

Before Advice

You can declare a "before" advice in an aspect using the @Before annotation:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}

If you use a direct slice expression, you can overwrite the previous one example as follows:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    fun doAccessCheck() {
        // ...
    }
}

After Returning Advice

The "after returning" advice is executed when execution of the matched method returns in the normal order. You can declare it using the @AfterReturning annotation:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}
It is possible to have multiple declarations of councils (and other members), all within one aspect. These examples show only one advice declaration to highlight the impact of each.

Sometimes you may want to access the returned actual value in the body of the advice. You can use a @AfterReturning form that binds a return value to provide such access, as shown in the following example:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
    @AfterReturning(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }
} 
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
    @AfterReturning(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning = "retVal")
    fun doAccessCheck(retVal: Any) {
        // ...
    }
}

The name used in the returning attribute must match the name of the parameter in the advice method. If method execution returns, the return value is passed to the advice method as the corresponding argument value. The returning expression also limits the matching to only those method executions that return a value of the specified type (in this case Object, which matches any return value).

Note that when using the "after throwing" advice, it is impossible to return a completely different reference.

After Throwing advice

The "after throwing" advice is executed when the corresponding method completes execution throwing an exception. It can be declared using the @AfterThrowing annotation, as shown in the following example:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doRecoveryActions() {
        // ...
    }
}

Often you want the advice to only run when exceptions are thrown of a certain type, but also often requires access to the exception in the body of the advice. The throw attribute can be used both to restrict the matching search (if desired - otherwise use Throwable as the exception type) and to bind the thrown exception to a tip parameter. The following example shows how to do this:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
    @AfterThrowing(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }
} 
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
    @AfterThrowing(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing = "ex")
    fun doRecoveryActions(ex: DataAccessException) {
        // ...
    }
}

The name used in the throw attribute must match the name of the parameter in the advice method. If execution of a method ends with an exception being thrown, the exception is passed to the advice method as the corresponding argument value. The throw expression also limits the matching search to only those method executions that throw an exception of the specified type (in this case DataAccessException).

Note that @AfterThrowing does not point to a general exception handling callback. In particular, an advice method annotated with @AfterThrowing must only receive exceptions from the join point itself (the user-declared target method), and not from the companion @After/@AfterReturning method.

The After (Finally) Advice

The “After (finally)” advice is executed when the corresponding method completes execution. It is declared using the @After annotation. The “after” board needs to be prepared to deal with both normal and exceptional return conditions. It is typically used to release resources and similar purposes. The following example shows how to use the "after (complete)" tip:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After
@Aspect
class AfterFinallyExample {
    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doReleaseLock() {
        // ...
    }
}

Note that advice marked with the @After annotation is defined in AspectJ as "advice after (completion)", similar to the "finally" block in a try-catch statement ). It will be called for any result, normal return, or exception thrown from the join point (the user-declared target method), unlike the @AfterReturning annotation, which only applies to successful normal returns.

Around Advice

The last type of advice is the "instead of" advice. The "instead" advice replaces the execution of the associated method. It can run before or after a method runs and determine when, how, and even whether the method is run at all. The "instead" advice is often used when you want to separate the state "before" and "after" the execution of a method in a thread-safe manner - for example, as for starting and stopping a timer.

Always use the least influential form of advice that meets your needs.

For example, don't use advice instead of if adviceis sufficient for your needs." before".

The "instead" advice is declared by annotating the method with the @Around annotation. The method must declare Object as its return type, and the first parameter of the method must be of type ProceedingJoinPoint. In the body of the council method, you need to call proceed() on ProceedingJoinPoint to run the main method. Calling proceed() without arguments will cause the caller's original arguments to be passed to the base method when it is called. For more advanced use cases, there is an overload of the proceed() method that takes an array of (Object[]) arguments. The values in the array will be used as arguments to the base method when it is called.

Logic of proceed when called with using Object[] is slightly different from the logic of proceed for the "instead" advice compiled by the AspectJ compiler. For an "instead" advice written using the traditional AspectJ language, the number of arguments passed to proceed must match the number of arguments passed in the "instead" advice (not the number of arguments accepted by the underlying join point), and the value passed to continue at a given argument position displaces the original value at the join point for the entity to which that value was bound (don't worry if this seems nonsense now).

The approach used by Spring is more is simple and better suits its proxy-based and runtime-only semantics. You only need to understand this difference if you are compiling @AspectJ annotated aspects written for Spring and using the proceed method supplied with arguments with the AspectJ compiler and binding tool. There is a way to write such aspects that is 100% compatible with both Spring AOP and AspectJ.

The value returned by the "instead" advice is the return value that the calling code of the method sees . For example, a simple caching aspect might return a value from the cache if it exists, or call proceed() (and return that value) if it does not. Note that proceed can be called once, many times, or not called at all in the body of the "instead" advice. None of this is prohibited.

If you declare the return type of the advice method "instead" as void, then null will always be returned to the caller , effectively ignoring the result of any proceed() call. Therefore, it is recommended that the advice method "instead" declare the return type Object. A council method should generally return the value returned by a call to proceed(), even if the underlying method has a return type of void. However, the tip can optionally return a cached value, a wrapped value, or some other value depending on the use case.

The following example shows how to use the "instead" tip:

Java

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start the stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }
}
Kotlin

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint
@Aspect
class AroundExample {
    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
        // start the stopwatch
        val retVal = pjp.proceed()
        // stop the stopwatch
        return retVal
    }
}

Advice options

Spring offers fully typed advice, which means that you declare the necessary parameters in the advice signature (as you could see earlier in the examples with returning and throwing an exception), rather than constantly working with Object[] arrays. Later in this section, we'll look at how to make arguments and other contextual values available to the body of the advice. First we'll look at how to write a generic tip that can recognize the method that the tip is currently providing.

Access to the current JoinPoint

For any tip method, you can declare as its first parameter a parameter of type org.aspectj.lang.JoinPoint. Note that the "instead" advice requires that you declare the first parameter of type ProceedingJoinPoint, which is a subclass of JoinPoint.

The JoinPoint Interface > provides a number of useful methods:

  • getArgs(): Returns the method arguments.

  • getThis(): Returns the proxy object.

  • getTarget(): Returns the target object.

  • getSignature(): Returns the description of the method provided by the tip.

  • toString(): Prints a practically usable description of the method provided by the tip.

Passing parameters to the tip

We've already seen how to bind a return value or an exception value (using tips " after return" and "after throwing an exception"). To make argument values available to the advice body, you can use the args form of binding. If you use a parameter name instead of a type name in an args expression, the value of the corresponding argument is passed as the parameter value when the advice is called. An example will make everything clearer. Let's say you need to provide advice to perform DAO operations that take an Account object as the first parameter, and you need account access in the body of the advice. You can write the following:

Java

@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..) ")
public void validateAccount(Account account) {
    // ...
}
Kotlin

@Before( "com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
fun validateAccount(account: Account) {
    // ...
}

The args(account,..) portion of the slice expression serves two purposes. First, it limits the matching search to only those method executions in which the method takes at least one parameter and the argument passed as a parameter is an instance of Account. Second, it exposes the board to the actual Account object via the account parameter.

Another way to write it is to declare a slice that "provides" the value of the object Account if it matches the join point, then access the named slice from the board. It will look like this:

Java

@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,.. )")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}
Kotlin

@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
}
@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
    // ...
}

More details See the AspectJ Programming Guide for information.

Proxy object (this), target object (target), and annotations(@ within, @target, @annotation and @args) can be bound in a similar way. The following two examples show how to map the execution of methods annotated with the @Auditable annotation and extract the audit code:

The first of two examples shows the definition of the @Auditable annotation:

Java

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}
Kotlin

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)

The second of two examples shows advice that coincides with executing methods marked with the @Auditable annotation:

Java

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}
Kotlin

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
fun audit(auditable: Auditable) {
    val code = auditable.value()
    // ...
}

Hints and Generics Parameters

Spring AOP can handle generics used in class declarations and method parameters. Let's say you have a generic type like the following:

Java

public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}
Kotlin

interface Sample<T> {
    fun sampleGenericMethod(param: T)
    fun sampleGenericCollectionMethod(param: Collection<T>)
}

You can restrict method type interception to certain parameter types by parameter binding advice on the type of parameter for which you want to intercept the method:

Java

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Implementation of the advice
}
Kotlin

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
        // Implementation of the advice
}

This approach does not work for generic collections. Thus, it will not work to define a slice like this:

Java

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args (param)")
public void beforeSampleMethod(Collection<MyType> param) {
        // Implementation of the advice
}
Kotlin

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
        // Implementation of the advice
}

For this to work, we would have to check every element of the collection, which is impractical since we also can't decide how to handle null values in general. To achieve something like this, you need to cast the parameter to the form Collection<?> and manually check the type of elements.

Defining argument names

Binding parameters in advice calls are based on matching the names used in slice expressions with the declared parameter names in advice and slice method signatures. Parameter names are not available through Java reflection, so Spring AOP uses the following strategy to determine parameter names:

  • If parameter names have been explicitly specified by the user, then the given parameter names are used. Tip and slice annotations have an optional argNames attribute that can be used to specify the names of the arguments of the annotated method. These argument names are available at runtime. The following example shows how to use the argNames attribute:

Java

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
        AuditCode code = auditable.value();
        //... use the code, bean and connection point
}
Kotlin

@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(bean: Any, auditable: Auditable) {
    val code = auditable.value()
    //... use the code, bean and join point
}

If the first parameter is of type JoinPoint, ProceedingJoinPoint or JoinPoint.StaticPart, then you do not have to specify the parameter name in the value of the argNames attribute. For example, if you modify the previous advice to get a junction point object, the argNames attribute should not contain it:

Java

@Before( value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean ,
    Auditable auditable) { AuditCode code = auditable.value();
        // ... use the code, bean and connection point
}
Kotlin

@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(jp: JoinPoint, bean: Any, auditable : Auditable) {
    val code = auditable.value()
    // ... use code, bean and connection point
}

Special treatment of the first parameter of types JoinPoint, ProceedingJoinPoint, and JoinPoint.StaticPart are especially useful for advice instances that do not collect any other join point context. In such situations, the argNames attribute can be omitted. For example, the following tip does not need to declare the argNames attribute:

Java

@Before("com.xyz.lib.Pointcuts.anyPublicMethod ()")
public void audit(JoinPoint jp) {
    // ... use the join point
}
Kotlin

@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
fun audit(jp: JoinPoint) {
    // ... use the join point
}
  • Using the argNames attribute is somewhat awkward, so if the argNames attribute has not been set, Spring AOP looks at the debugging information for the class and tries to determine the names parameters from the table of local variables. This information will be present until the classes are compiled using debugging information (at least -g:vars). The consequences of compiling with this flag are: (1) the code becomes a little easier to understand (reverse engineering); (2) the size of the class files becomes slightly larger (usually unimportant); (3) optimizations for removing unused local variables are not applied by your compiler. In other words, you won't have any problems if you build with this flag.

    If the @AspectJ aspect was compiled by the AspectJ (ajc) compiler even without debugging information, there is no need to add the argNames attribute, since the compiler will retain the necessary information.
  • If the code was compiled without the necessary debug information, Spring AOP will try to trace pairs of bound variables to parameters (for example, if in a slice expression only one variable has a bind, and the advice method takes only one parameter, then the pairwise relationship is obvious). If the variable binding is ambiguous given the available information, an AmbiguousBindingException will be thrown.

  • If all of the above strategies fail, an IllegalArgumentException will be thrown.

Continuing execution of a method using arguments

Earlier we noted that we will tell you how to write a call to the proceed method using arguments, which will work consistently in Spring AOP and AspectJ. The solution is that the advice signature must bind all method parameters in order. The following example shows how to do this:

Java

@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
        String accountHolderNamePattern) throws Throwable {
    String newPattern = preProcess(accountHolderNamePattern);
    return pjp.proceed(new Object[] {newPattern});
}
Kotlin

@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
                        accountHolderNamePattern: String): Any {
    val newPattern = preProcess(accountHolderNamePattern)
    return pjp.proceed(arrayOf<Any>(newPattern))
}

In many cases you still have to do this binding (as in the previous example) .

Ordering Tips

What happens if multiple tips are executed at the same junction point? Spring AOP follows the same precedence rules as AspectJ to determine the order in which advice is executed. The advice with the highest seniority (priority) is executed first "upstream" (thus, if two "upstream" pieces of advice are given, the one with the highest precedence level is executed first). "On exit" from the junction point, the advice with the highest precedence level is executed last (so if two advices are given "after", the advice with the highest precedence level will be executed second).

If two advices defined in different aspects must be executed at the same connection point, unless otherwise specified, the order of execution is unspecified. You can control the order of execution by setting precedence. This is done in the usual Spring way: either by implementing the org.springframework.core.Ordered interface in the aspect class, or by annotating the advice with the @Order annotation. When there are two aspects, the aspect that returns the lesser value from Ordered.getOrder() (or annotation value) has the highest precedence level.

Each of the individual types of advice of a particular aspect is conceptually intended to apply directly to a connection point. As a consequence, a advice method annotated with @AfterThrowing is not expected to receive an exception from a companion method annotated with @After/@AfterReturning.

Starting Since Spring Framework 5.2.7, advice methods defined in the same class marked with the @Aspect annotation that must be executed at the same join point are assigned a precedence level based on their advice type in the following order, from highest to lowest precedence level: @Around, @Before, @After, @AfterReturning, @AfterThrowing. Note, however, that a tip method annotated with @After will actually be called after any tip methods annotated with the @AfterReturning or @AfterThrowing annotations. in the same aspect, following the "advice after (completion)" semantics from AspectJ for the @After annotation.

If two parts of the same advice type (for example, two advice methods with the annotation @After) defined in the same class annotated with @Aspect must be executed at the same join point, their order will not be determined (since javac-compiled classes cannot receive declaration order in the source code through reflection). Consider combining such advice methods into one advice method for each connection point in each class annotated with the @Aspect annotation, or refactor the advice into separate @Aspect annotated classes that can be ordered at the aspect level using Ordered or @Order.

Introductions

Introductions (known in AspectJ as intertype declarations) allow an aspect to declare that the objects provided with the advice implement a given interface, and to provide an implementation of that interface on behalf of those objects.

You can implement the introduction using the @DeclareParents annotation. This annotation is used to declare that matching types have a new parent type (hence the name). For example, if we take the interface UsageTracked and the implementation of that interface DefaultUsageTracked, then the next aspect will declare that all implementers of service interfaces also implement the UsageTracked interface (for example, to collect statistics via JMX):

Java

@Aspect
public class UsageTracking {
    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;
    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }
}
Kotlin

@Aspect
class UsageTracking {
    companion object {
        @DeclareParents(value = "com.xzy.myapp.service.*+", defaultImpl = DefaultUsageTracked::class)
        lateinit var mixin: UsageTracked
    }
    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    fun recordUsage(usageTracked: UsageTracked) {
        usageTracked.incrementUseCount()
    }
}

The interface that will be implemented is determined by the type of the annotated field. The value attribute of the @DeclareParents annotation is an AspectJ type template. Any bean of the appropriate type implements the UsageTracked interface. Note that in the "before" tip in the previous example, service beans can be directly used as implementations of the UsageTracked interface. When accessing the bean programmatically, you need to write the following:

Java
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
Kotlin
val usageTracked = context.getBean("myService") as UsageTracked

Aspect Instantiation Models

This is a complex topic. If you're just starting to learn AOP, you can safely skip this section.

By default, there is a single instance of each aspect in the application context. AspectJ calls this the singleton instantiation model. Aspects with alternative life cycles can be defined. Spring supports the perthis and pertarget instantiation models from AspectJ; percflow, percflowbelow and pertypewithin are not currently supported.

You can declare an aspect perthis by specifying the perthis expression in the @Aspect annotation. Consider the following example:

Java

@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {
    private int someState;
    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    public void recordServiceUsage() {
        // ...
    }
}
Kotlin

@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
class MyAspect {
    private val someState: Int = 0
    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    fun recordServiceUsage() {
        // ...
    }
}

In the previous example, the effect of the expression perthis is that one aspect instance is created for each unique service object that executes the business service (each unique object associated with this at the join points corresponding to the slice expression). An aspect instance is created the first time a method is called on a service object. An aspect goes out of scope if the service object also goes out of scope. Before an aspect instance is created, none of the advice in it is executed. Once an aspect instance is created, the advice declared in it will begin to be executed at the mapped connection points, but only if the service object is the one that the aspect is associated with. For more information on per expressions, see the AspectJ Programming Guide.

The pertarget instantiation model works exactly the same as perthis, but it creates one aspect instance for each unique target at matching join points.

AOP Example

Now that you've seen how all the parts work, we can put them together to make something practical.

Sometimes execution of business services can fail due to concurrency issues (such as deadlock). If the operation is repeated, it will most likely be successful the next time you try. For business services where it is appropriate to retry an operation under these conditions (idempotent operations that do not need to return to the user to resolve the conflict), we want to retry such an operation in a clear way without the client end seeing a PessimisticLockingFailureException. This is a requirement that obviously spans multiple services at the service level and is therefore ideally suited to be implemented through an aspect.

Since we need to repeat the operation, we need to use the "instead" advice so that we can call the proceed method several times. The following listing shows the basic implementation of the aspect:

Java

@Aspect
public class ConcurrentOperationExecutor implements Ordered {
    private static final int DEFAULT_MAX_RETRIES = 2;
    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;
    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }
    public int getOrder() {
        return this.order;
    }
    public void setOrder(int order) {
        this.order = order;
    }
    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}
Kotlin

@Aspect
class ConcurrentOperationExecutor : Ordered {
    private val DEFAULT_MAX_RETRIES = 2
    private var maxRetries = DEFAULT_MAX_RETRIES
    private var order = 1
    fun setMaxRetries(maxRetries: Int) {
        this.maxRetries = maxRetries
    }
    override fun getOrder(): Int {
        return this.order
    }
    fun setOrder(order: Int) {
        this.order = order
    }
    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
        var numAttempts = 0
        var lockFailureException: PessimisticLockingFailureException
        do {
            numAttempts++
            try {
                return pjp.proceed()
            } catch (ex: PessimisticLockingFailureException) {
                lockFailureException = ex
            }
        } while (numAttempts <= this.maxRetries)
        throw lockFailureException
    }
}

Note that the aspect implements the Ordered interface so that we can set the aspect precedence level higher than the transaction advice (we want the transaction to be new each time we retry). The maxRetries and order properties are configured by Spring. The main action happens in the advice "instead of" doConcurrentOperation. Note that we are currently applying retry logic to each businessService(). We try to continue execution, and in case of an error PessimisticLockingFailureException we try again, if all retry attempts have not been exhausted.

The following is the corresponding Spring configuration:


<aop:aspectj-autoproxy/>
<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>

To refine the aspect so that it only repeats idempotent operations, we can define the following Idempotent annotation:

Java

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
Kotlin

@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation

Then you can use the annotation to annotate the implementation of utility operations. Changing the aspect to re-execute only idempotent operations involves refining the slice expression so that only @Idempotent operations match, as shown below:

Java

@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}
Kotlin

@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
    // ...
}
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION