@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.
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 @Configuration
from Java, add the @EnableAspectJAutoProxy
annotation
as shown in the following example:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
@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
;
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
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).
@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).
@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
:
@Pointcut("execution(* transfer(..))") // cut expression
private void anyOldTransfer() {} // cut signature
@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.
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:
bean(idOrNameOfBean)
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:
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
anyPublicOperation
is negotiated if the method execution connection point is the execution of any public method.inTrading
is negotiated if the method execution occurs in the trading module.tradingOperation
is consistent if the method execution represents any public method in the trading module.
@Pointcut("execution(public * * (..))")
private fun anyPublicOperation() {}
@Pointcut("within(com.xyz.myapp.trading.. *)")
private fun inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private fun tradingOperation() {}
anyPublicOperation
is consistent if the method execution join point is the execution of any public method.inTrading
is agreed upon if the method execution occurs in the trading module.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:
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() {}
}
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 isSerializable
, and the "execution" option is matched if the method signature declares a single parameter of typeSerializable
.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
orhandler
.Definitive pointers select the desired group of connection points (possibly from many genera):
within
andwithincode
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:
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() {
// ...
}
}
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:
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() {
// ...
}
}
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:
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() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
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:
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) {
// ...
}
}
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:
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() {
// ...
}
}
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:
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) {
// ...
}
}
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:
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() {
// ...
}
}
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.
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:
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;
}
}
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:
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..) ")
public void validateAccount(Account account) {
// ...
}
@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:
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,.. )")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
@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:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
@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:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
@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:
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
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:
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Implementation of the advice
}
@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:
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args (param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Implementation of the advice
}
@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 theargNames
attribute:
@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
}
@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:
@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
}
@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:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod ()")
public void audit(JoinPoint jp) {
// ... use the join point
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
fun audit(jp: JoinPoint) {
// ... use the join point
}
Using the
argNames
attribute is somewhat awkward, so if theargNames
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 theargNames
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:
@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});
}
@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):
@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();
}
}
@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:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
val usageTracked = context.getBean("myService") as UsageTracked
Aspect Instantiation Models
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:
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {
private int someState;
@Before("com.xyz.myapp.CommonPointcuts.businessService()")
public void recordServiceUsage() {
// ...
}
}
@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:
@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;
}
}
@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:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
@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:
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
// ...
}
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
// ...
}
GO TO FULL VERSION