If you prefer an XML-based format, Spring also provides support for defining aspects using aop
namespace
tags. The exact same slice expressions and tip types are supported as when using the @AspectJ style. Therefore, in
this section we will focus on this syntax and refer the reader to the description in the previous section for the
specifics of writing slice expressions and advice parameter binding.
To use the AOP namespace tags described
in this section, you must import the spring-aop
schema.
In Spring configurations, all aspects
and advisors elements must be placed in the <aop:config>
element ( You can have more than one
<aop:config>
element in your application context configuration). The
<aop:config>
element can contain slicer, advisor, and aspect elements (note that they must be
declared in that order).
<aop:config>
actively uses the Spring auto-proxy mechanism. This can lead to problems (for
example, the tip will not be linked) if you are already using explicit autoproxy using
BeanNameAutoProxyCreator
or something similar. It is recommended to use only the
<aop:config>
style or only the AutoProxyCreator
style and never mix them.
Aspect Declaration
If you are using schema support, an aspect is a regular Java object defined as a bean in the context of your Spring application. State and operation logic are captured in the object's fields and methods, and information about slices and tips is captured in XML.
You can declare an aspect using the <aop:aspect>
element, and reference the base bean using the
ref
attribute, as shown in the following example:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
The bean that supports the aspect (in this case aBean
) can of course be configured and inject
dependencies like any other Spring bean.
Declaring a Slice
You can declare a named slice inside a
<aop:config>
element, allowing you to share use the slice definition in multiple aspects and
advisors.
A slice representing the execution of any business service at the service level can be defined as follows:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
Note that the slice expression itself uses the same AspectJ slice expression language. If you use a schema-based declaration style, you can reference named slices defined in types (@Aspects) in the slice expression. Another way to define the above slice could look like this:
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.myapp.CommonPointcuts.businessService()"/>
</aop:config>
Assume you have an aspect CommonPointcuts
.
Declaration a slicer inside an aspect is very similar to a top-level slicer declaration, as shown in the following example:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
Just like @AspectJ, slices declared using the schema-based definition style can collect connection point
context. For example, the following slice collects the this
object as a junction point context and
passes it to the tip:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
The advice must be declared to obtain the collected connection point context by including parameters with the appropriate names, as shown below:
public void monitor(Object service) {
// ...
}
fun monitor(service: Any) {
// ...
}
When combining slice subexpressions, use &&
in an XML document is inconvenient,
so instead of &&
, ||
and !
you can use the keywords
and
, or
and not
respectively. For example, the previous slice can be written
as follows:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
Note that slicers defined this way refer to their XML id
and do not can be used as named slices
to compose compound slices. Thus, support for named slices in the schema-based definition style is more limited than
in the @AspectJ style.
Declaring Tips
The same five kinds of tips are used to support schema-based AOP. as for the @AspectJ style, and they have exactly the same semantics.
Before Advice
The "before"
advice is executed before the corresponding method is executed. It is declared within
<aop:aspect>
using the <aop:before>
element, as shown in the following
example:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
Here dataAccessOperation
is the id
of the slice, defined at the top (<aop:config>
)
level. To define a slice embedding, replace the pointcut-ref
attribute with the pointcut
attribute, as shown below:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
As noted when looking at the @AspectJ style, using named slicers can greatly improve the readability of your code.
The method
attribute identifies the (doAccessCheck
) method that provides the
advice body. This method must be defined for the bean referenced by the aspect element containing the tip. Before
performing a data access operation (the method execution join point corresponding to the slice expression), the
doAccessCheck
method is called on the aspected bean.
After Returning Tip
Tip "after
returning" is executed when the matched method completes in normal order. It is declared inside <aop:aspect>
in the same way as the previous tip. The following example shows how to declare it:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
As in @AspectJ style, you can get the return value in the body of the advice. To do this, use the returning
attribute to specify the name of the parameter to which the return value should be passed, as shown in the following
example:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
The doAccessCheck
method must declare a parameter called retVal
. The type of this
parameter limits the matching search in the same way as described for @AfterReturning
. For example, you
cannot declare a method signature as follows:
public void doAccessCheck(Object retVal) {...
fun doAccessCheck(retVal: Any) {...
After Throwing Tip
The "after throwing" tip is executed when the execution of the corresponding
method ends with an exception being thrown. It is declared within <aop:aspect>
using the after-throwing
element, as shown in the following example:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
</aop:aspect>
As in @AspectJ style, it is possible to get the thrown exception in the body of the advice. To do this,
use the throwing
attribute to specify the name of the parameter to which the exception should be
passed, as shown in the following example:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
The doRecoveryActions
method must declare a parameter called dataAccessEx
. The type
of this parameter limits the matching search in the same way as described for @AfterThrowing
. For
example, a method signature can be declared as follows:
public void doRecoveryActions(DataAccessException dataAccessEx) {...
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
After (Finally) Tip
The After (Finally) tip is executed regardless of how the associated method
completes execution. You can declare it using the after
element, as shown in the following example:
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>
Around Advice
The last type of advice is the "instead" 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 requirements.
Always use the least influential form of advice that meets your requirements. For example, don't use the "instead of" advice if the"before" advice is sufficient for your needs.
You can declare an “instead” advice using the aop:around
element. The advice 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.
The following example shows how to declare an "instead" tip in XML:
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
The implementation of the doBasicProfiling
advice can be exactly the same as in the example for @
AspectJ (minus the annotation, of course), as shown in the following example:
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start the stopwatch
Object retVal = pjp.proceed();
// stop the stopwatch
return retVal;
}
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
// start the stopwatch
val retVal = pjp .proceed()
// stop the stopwatch
return pjp.proceed()
}
Advice options
Schema-based declaration style is fully supported typed advice in the same way as
described for support in the case of @AspectJ - by mapping slice parameters by name to advice method parameters. If
you need to explicitly specify argument names for advice methods (without using the detection strategies described
earlier), you can do this using the arg-names
attribute of the advice element, which is treated the
same as the argNames
attribute in the advice annotation. The following example shows how to specify
the argument name in XML:
<aop:before
pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
method="audit"
arg-names="auditable"/>
The arg-names
attribute accepts a comma-separated list of parameter names.
The next, slightly more complex example of the approach, based on XSD, demonstrates some tips used in combination with a number of strongly typed parameters:
package x.y.service;
public interface PersonService {
Person getPerson(String personName, int age);
}
public class DefaultPersonService implements PersonService {
public Person getPerson(String name, int age) {
return new Person(name, age);
}
}
package x.y.service
interface PersonService {
fun getPerson(personName: String, age: Int) : Person
}
class DefaultPersonService : PersonService {
fun getPerson(name: String, age: Int): Person {
return Person(name, age)
}
}
Next comes the aspect. Note that the profile(..)
method takes several strongly typed parameters,
the first of which is the connection point used to continue calling the method. The presence of this parameter
indicates that profile(..)
should be used as a around
tip, as shown in the following
example:
package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class SimpleProfiler {
public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
try {
clock.start(call.toShortString());
return call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
}
}
import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.util.StopWatch
class SimpleProfiler {
fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any {
val clock = StopWatch("Profiling for '$name' and '$age'")
try {
clock.start(call.toShortString())
return call.proceed()
} finally {
clock.stop()
println(clock.prettyPrint())
}
}
}
Finally, the following example XML configuration affects the execution of the previous advice for a specific connection point:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop ="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the object that will be proxied by the Spring AOP framework -->
<bean id="personService" class="x.y.service.DefaultPersonService"/>
<!-- this is actually the advice itself -->
<bean id="profiler" class="x.y.SimpleProfiler"/>
<aop:config>
<aop:aspect ref="profiler">
<aop:pointcut id="theExecutionOfSomePersonServiceMethod"
expression="execution(* x.y.service.PersonService.getPerson(String,int))
and args(name, age)"/>
<aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
method="profile"/>
</aop:aspect>
</aop:config>
</beans>
Consider the following driver script:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;
public final class Boot {
public static void main(final String[] args) throws Exception {
BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
PersonService person = (PersonService) ctx.getBean("personService");
person.getPerson("Pengo", 12);
}
}
fun main() {
val ctx = ClassPathXmlApplicationContext("x/y/plain.xml")
val person = ctx.getBean("personService") as PersonService
person.getPerson("Pengo", 12)
}
With this Boot class we would get an execution result similar to the following with standard output:
StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0 ----------------------------------------- ms % Task name ----------------------------------------- 00000 ? execution(getFoo)
Ordering Hints
If multiple Hints need to be executed at the same connection point (executing method),
the ordering rules are described in section "Ordering advice." Precedence between aspects is determined through the
order
attribute on the <aop:aspect>
element, or by adding the @Order
annotation to the bean supporting the aspect, or by implementing a binomial of the Ordered
interface.
Unlike the precedence rules for advice methods defined in the same class,
annotated with @Aspect
, if two advice defined in the same <aop:aspect>
element are
to be executed at the same join point, precedence is determined by the order in which the advice elements are
declared in the enclosing element <aop:aspect>
from highest to lowest level of precedence.
For example, if there is advice around
and advice before
elements defined in the same
<aop:aspect>
element that are applied to the same join point if you want the
around
advice to be guaranteed to have more higher level of precedence than the before
advice, the <aop:around>
element must be declared before the <aop:before>
element.
As a rule of thumb, if you find that there are multiple tips defined in a single <aop:aspect>
element that apply to the same join point, consider to combine such advice methods into one advice method for
each join point in each <aop:aspect>
element, or to refactor the advice into separate <aop:aspect>
elements, which can be ordered at the aspect level.
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 provide an implementation of that interface on behalf of those objects.
You can
implement the introduction using the aop:declare-parents
element inside the aop:aspect
element. You can use the aop:declare-parents
element to declare that matching types have a new parent
type (hence the name). For example, given the UsageTracked
interface and the
DefaultUsageTracked
implementation of that interface, the next aspect declares that all service
interface implementers also implement the UsageTracked
interface. (For example, to open statistics via
JMX).
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xzy.myapp.service.*+"
implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="com.xyz.myapp.CommonPointcuts.businessService()
and this(usageTracked)"
method="recordUsage"/>
</aop:aspect>
The class that serves as the basis for the UseTracking
bean will contain the following method:
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
The interface that must be implemented is determined by the implement-interface
attribute. The
value of the types-matching
attribute is the 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. To access the bean
programmatically, you can write the following:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
val usageTracked = context.getBean("myService") as UsageTracked
Aspect Instancing Models
The only supported instantiation model for schema-defined aspects is the singleton instantiation model. Support for other instantiation models may be added in future releases.
Advisors
The concept of "advisors" comes from the AOP support facilities defined in Spring and has no direct equivalent in AspectJ. An advisor is like a small self-contained aspect that is provided with a single piece of advice. The council itself is represented by a bean and must implement one of the council interfaces. Advisors can take advantage of slice expressions from AspectJ.
Spring supports the concept of an advisor using the <aop:advisor>
element. It is most often used in conjunction with transactional advice, which also has its own namespace in Spring.
The following example shows the advisor:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
In addition to the pointcut-ref
attribute used in the previous example, you can also use the
pointcut
to define the cut expression embedding.
To define the seniority of the advisor so that
the advisor can participate in ordering, use the order
attribute to define the value
Ordered
advisor.
AOP Schema Example
This section shows what the concurrent lock failure retry example from An AOP Example would look like if it were rewritten using schema support.
Sometimes business services
may 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 go back to the user to resolve the conflict), we want to retry
the 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 a basic aspect
implementation (which is a regular Java class that uses schema support):
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;
}
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;
}
}
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
}
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 precedence level of
the aspect higher than the transaction advice (we need , so that each time you retry the transaction will be a new
one). The maxRetries
and order
properties are configured by Spring. The main action
happens in the doConcurrentOperation
method of the "instead" council. Let's try to continue execution.
In the event of an error throwing an exception PessimisticLockingFailureException
, we retry if all
retry attempts have not been exhausted.
The corresponding Spring configuration looks like this:
<aop:config>
<aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:around
pointcut-ref="idempotentOperation"
method="doConcurrentOperation"/>
</aop:aspect>
</aop:config>
<bean id="concurrentOperationExecutor"
class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
</bean>
Note that at this point we are assuming that all business services are idempotent. If this is not the
case, you can modify the aspect so that it only repeats truly idempotent operations by introducing the Idempotent
annotation and using it to annotate the implementation of utility operations, as shown in the following example:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
// marker annotation
}
Changing aspect for repeating only idempotent operations involves refining the slice expression so that only
@Idempotent
operations match, as shown below:
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service. *.*(..)) and
@annotation(com.xyz.myapp.service.Idempotent)"/>