Clarify intended advice execution behavior (includes related polishing)

Closes gh-26202
This commit is contained in:
Juergen Hoeller 2020-12-07 22:06:22 +01:00
parent 5efa4ad442
commit 834032df1f
1 changed files with 95 additions and 106 deletions

View File

@ -925,7 +925,6 @@ You can declare before advice in an aspect by using the `@Before` annotation:
public void doAccessCheck() { public void doAccessCheck() {
// ... // ...
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@ -941,7 +940,6 @@ You can declare before advice in an aspect by using the `@Before` annotation:
fun doAccessCheck() { fun doAccessCheck() {
// ... // ...
} }
} }
---- ----
@ -961,7 +959,6 @@ following example:
public void doAccessCheck() { public void doAccessCheck() {
// ... // ...
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim",role="secondary"] [source,kotlin,indent=0,subs="verbatim",role="secondary"]
@ -977,7 +974,6 @@ following example:
fun doAccessCheck() { fun doAccessCheck() {
// ... // ...
} }
} }
---- ----
@ -985,8 +981,8 @@ following example:
[[aop-advice-after-returning]] [[aop-advice-after-returning]]
==== After Returning Advice ==== After Returning Advice
After returning advice runs when a matched method execution returns normally. You can After returning advice runs when a matched method execution returns normally.
declare it by using the `@AfterReturning` annotation: You can declare it by using the `@AfterReturning` annotation:
[source,java,indent=0,subs="verbatim,quotes",role="primary"] [source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java .Java
@ -1001,7 +997,6 @@ declare it by using the `@AfterReturning` annotation:
public void doAccessCheck() { public void doAccessCheck() {
// ... // ...
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@ -1017,16 +1012,16 @@ declare it by using the `@AfterReturning` annotation:
fun doAccessCheck() { fun doAccessCheck() {
// ... // ...
} }
}
---- ----
NOTE: You can have multiple advice declarations (and other members NOTE: You can have multiple advice declarations (and other members as well),
as well), all inside the same aspect. We show only a single advice declaration in all inside the same aspect. We show only a single advice declaration in these
these examples to focus the effect of each one. examples to focus the effect of each one.
Sometimes, you need access in the advice body to the actual value that was returned. You Sometimes, you need access in the advice body to the actual value that was returned.
can use the form of `@AfterReturning` that binds the return value to get that access, as You can use the form of `@AfterReturning` that binds the return value to get that
the following example shows: access, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"] [source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java .Java
@ -1043,7 +1038,6 @@ the following example shows:
public void doAccessCheck(Object retVal) { public void doAccessCheck(Object retVal) {
// ... // ...
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@ -1061,15 +1055,14 @@ the following example shows:
fun doAccessCheck(retVal: Any) { fun doAccessCheck(retVal: Any) {
// ... // ...
} }
} }
---- ----
The name used in the `returning` attribute must correspond to the name of a parameter in The name used in the `returning` attribute must correspond to the name of a parameter
the advice method. When a method execution returns, the return value is passed to in the advice method. When a method execution returns, the return value is passed to
the advice method as the corresponding argument value. A `returning` clause also the advice method as the corresponding argument value. A `returning` clause also
restricts matching to only those method executions that return a value of the specified restricts matching to only those method executions that return a value of the
type (in this case, `Object`, which matches any return value). specified type (in this case, `Object`, which matches any return value).
Please note that it is not possible to return a totally different reference when Please note that it is not possible to return a totally different reference when
using after returning advice. using after returning advice.
@ -1079,8 +1072,8 @@ using after returning advice.
==== After Throwing Advice ==== After Throwing Advice
After throwing advice runs when a matched method execution exits by throwing an After throwing advice runs when a matched method execution exits by throwing an
exception. You can declare it by using the `@AfterThrowing` annotation, as the following exception. You can declare it by using the `@AfterThrowing` annotation, as the
example shows: following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"] [source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java .Java
@ -1095,7 +1088,6 @@ example shows:
public void doRecoveryActions() { public void doRecoveryActions() {
// ... // ...
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@ -1111,15 +1103,14 @@ example shows:
fun doRecoveryActions() { fun doRecoveryActions() {
// ... // ...
} }
} }
---- ----
Often, you want the advice to run only when exceptions of a given type are thrown, and Often, you want the advice to run only when exceptions of a given type are thrown,
you also often need access to the thrown exception in the advice body. You can use the and you also often need access to the thrown exception in the advice body. You can
`throwing` attribute to both restrict matching (if desired -- use `Throwable` as the use the `throwing` attribute to both restrict matching (if desired -- use `Throwable`
exception type otherwise) and bind the thrown exception to an advice parameter. The as the exception type otherwise) and bind the thrown exception to an advice parameter.
following example shows how to do so: The following example shows how to do so:
[source,java,indent=0,subs="verbatim,quotes",role="primary"] [source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java .Java
@ -1136,7 +1127,6 @@ following example shows how to do so:
public void doRecoveryActions(DataAccessException ex) { public void doRecoveryActions(DataAccessException ex) {
// ... // ...
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@ -1154,15 +1144,22 @@ following example shows how to do so:
fun doRecoveryActions(ex: DataAccessException) { fun doRecoveryActions(ex: DataAccessException) {
// ... // ...
} }
} }
---- ----
The name used in the `throwing` attribute must correspond to the name of a parameter in The name used in the `throwing` attribute must correspond to the name of a parameter in
the advice method. When a method execution exits by throwing an exception, the exception the advice method. When a method execution exits by throwing an exception, the exception
is passed to the advice method as the corresponding argument value. A `throwing` is passed to the advice method as the corresponding argument value. A `throwing` clause
clause also restricts matching to only those method executions that throw an exception also restricts matching to only those method executions that throw an exception of the
of the specified type ( `DataAccessException`, in this case). specified type (`DataAccessException`, in this case).
[NOTE]
====
Note that `@AfterThrowing` does not indicate a general exception handling callback.
Specifically, an `@AfterThrowing` advice method is only supposed to receive exceptions
from the join point (user-declared target method) itself but not from an accompanying
`@After`/`@AfterReturning` method.
====
[[aop-advice-after-finally]] [[aop-advice-after-finally]]
@ -1170,8 +1167,8 @@ of the specified type ( `DataAccessException`, in this case).
After (finally) advice runs when a matched method execution exits. It is declared by After (finally) advice runs when a matched method execution exits. It is declared by
using the `@After` annotation. After advice must be prepared to handle both normal and using the `@After` annotation. After advice must be prepared to handle both normal and
exception return conditions. It is typically used for releasing resources and similar purposes. exception return conditions. It is typically used for releasing resources and similar
The following example shows how to use after finally advice: purposes. The following example shows how to use after finally advice:
[source,java,indent=0,subs="verbatim,quotes",role="primary"] [source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java .Java
@ -1186,7 +1183,6 @@ The following example shows how to use after finally advice:
public void doReleaseLock() { public void doReleaseLock() {
// ... // ...
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@ -1202,30 +1198,37 @@ The following example shows how to use after finally advice:
fun doReleaseLock() { fun doReleaseLock() {
// ... // ...
} }
} }
---- ----
[NOTE]
====
Note that `@After` advice in AspectJ is defined as "after finally advice", analogous
to a finally block in a try-catch statement. It will be invoked for any outcome,
normal return or exception thrown from the join point (user-declared target method),
in contrast to `@AfterReturning` which only applies to successful normal returns.
====
[[aop-ataspectj-around-advice]] [[aop-ataspectj-around-advice]]
==== Around Advice ==== Around Advice
The last kind of advice is around advice. Around advice runs "`around`" a matched method's The last kind of advice is around advice. Around advice runs "`around`" a matched
execution. It has the opportunity to do work both before and after the method runs method's execution. It has the opportunity to do work both before and after the method
and to determine when, how, and even if the method actually gets to run at all. runs and to determine when, how, and even if the method actually gets to run at all.
Around advice is often used if you need to share state before and after a method Around advice is often used if you need to share state before and after a method
execution in a thread-safe manner (starting and stopping a timer, for example). Always execution in a thread-safe manner (starting and stopping a timer, for example).
use the least powerful form of advice that meets your requirements (that is, do not use Always use the least powerful form of advice that meets your requirements (that is,
around advice if before advice would do). do not use around advice if before advice would do).
Around advice is declared by using the `@Around` annotation. The first parameter of the Around advice is declared by using the `@Around` annotation. The first parameter of the
advice method must be of type `ProceedingJoinPoint`. Within the body of the advice, advice method must be of type `ProceedingJoinPoint`. Within the body of the advice,
calling `proceed()` on the `ProceedingJoinPoint` causes the underlying method to calling `proceed()` on the `ProceedingJoinPoint` causes the underlying method to run.
run. The `proceed` method can also pass in an `Object[]`. The values The `proceed` method can also pass in an `Object[]`. The values in the array are used
in the array are used as the arguments to the method execution when it proceeds. as the arguments to the method execution when it proceeds.
NOTE: The behavior of `proceed` when called with an `Object[]` is a little different than the NOTE: The behavior of `proceed` when called with an `Object[]` is a little different than
behavior of `proceed` for around advice compiled by the AspectJ compiler. For around the behavior of `proceed` for around advice compiled by the AspectJ compiler. For around
advice written using the traditional AspectJ language, the number of arguments passed to advice written using the traditional AspectJ language, the number of arguments passed to
`proceed` must match the number of arguments passed to the around advice (not the number `proceed` must match the number of arguments passed to the around advice (not the number
of arguments taken by the underlying join point), and the value passed to proceed in a of arguments taken by the underlying join point), and the value passed to proceed in a
@ -1257,7 +1260,6 @@ The following example shows how to use around advice:
// stop stopwatch // stop stopwatch
return retVal; return retVal;
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@ -1277,34 +1279,31 @@ The following example shows how to use around advice:
// stop stopwatch // stop stopwatch
return retVal return retVal
} }
} }
---- ----
The value returned by the around advice is the return value seen by the caller of The value returned by the around advice is the return value seen by the caller of the
the method. For example, a simple caching aspect could return a value from a cache if it method. For example, a simple caching aspect could return a value from a cache if it
has one and invoke `proceed()` if it does not. Note that `proceed` may be invoked once, has one and invoke `proceed()` if it does not. Note that `proceed` may be invoked once,
many times, or not at all within the body of the around advice. All of these are many times, or not at all within the body of the around advice. All of these are legal.
legal.
[[aop-ataspectj-advice-params]] [[aop-ataspectj-advice-params]]
==== Advice Parameters ==== Advice Parameters
Spring offers fully typed advice, meaning that you declare the parameters you need Spring offers fully typed advice, meaning that you declare the parameters you need in the
in the advice signature (as we saw earlier for the returning and throwing examples) rather advice signature (as we saw earlier for the returning and throwing examples) rather than
than work with `Object[]` arrays all the time. We see how to make argument and other work with `Object[]` arrays all the time. We see how to make argument and other contextual
contextual values available to the advice body later in this section. First, we take a look at values available to the advice body later in this section. First, we take a look at how to
how to write generic advice that can find out about the method the advice is currently write generic advice that can find out about the method the advice is currently advising.
advising.
[[aop-ataspectj-advice-params-the-joinpoint]] [[aop-ataspectj-advice-params-the-joinpoint]]
===== Access to the Current `JoinPoint` ===== Access to the Current `JoinPoint`
Any advice method may declare, as its first parameter, a parameter of type Any advice method may declare, as its first parameter, a parameter of type
`org.aspectj.lang.JoinPoint` (note that around advice is required to declare `org.aspectj.lang.JoinPoint` (note that around advice is required to declare a first
a first parameter of type `ProceedingJoinPoint`, which is a subclass of `JoinPoint`. The parameter of type `ProceedingJoinPoint`, which is a subclass of `JoinPoint`.
`JoinPoint` interface provides a number of useful methods: The `JoinPoint` interface provides a number of useful methods:
* `getArgs()`: Returns the method arguments. * `getArgs()`: Returns the method arguments.
* `getThis()`: Returns the proxy object. * `getThis()`: Returns the proxy object.
@ -1320,9 +1319,9 @@ See the https://www.eclipse.org/aspectj/doc/released/runtime-api/org/aspectj/lan
We have already seen how to bind the returned value or exception value (using after We have already seen how to bind the returned value or exception value (using after
returning and after throwing advice). To make argument values available to the advice returning and after throwing advice). To make argument values available to the advice
body, you can use the binding form of `args`. If you use a parameter name in place of a body, you can use the binding form of `args`. If you use a parameter name in place of a
type name in an args expression, the value of the corresponding argument is type name in an args expression, the value of the corresponding argument is passed as
passed as the parameter value when the advice is invoked. An example should make this the parameter value when the advice is invoked. An example should make this clearer.
clearer. Suppose you want to advise the execution of DAO operations that take an `Account` Suppose you want to advise the execution of DAO operations that take an `Account`
object as the first parameter, and you need access to the account in the advice body. object as the first parameter, and you need access to the account in the advice body.
You could write the following: You could write the following:
@ -1654,20 +1653,23 @@ the higher precedence.
[NOTE] [NOTE]
==== ====
Each of the distinct advice types of a particular aspect is conceptually meant to apply
to the join point directly. As a consequence, an `@AfterThrowing` advice method is not
supposed to receive an exception from an accompanying `@After`/`@AfterReturning` method.
As of Spring Framework 5.2.7, advice methods defined in the same `@Aspect` class that As of Spring Framework 5.2.7, advice methods defined in the same `@Aspect` class that
need to run at the same join point are assigned precedence based on their advice type in need to run at the same join point are assigned precedence based on their advice type in
the following order, from highest to lowest precedence: `@Around`, `@Before`, `@After`, the following order, from highest to lowest precedence: `@Around`, `@Before`, `@After`,
`@AfterReturning`, `@AfterThrowing`. Note, however, that due to the implementation style `@AfterReturning`, `@AfterThrowing`. Note, however, that an `@After` advice method will
in Spring's `AspectJAfterAdvice`, an `@After` advice method will effectively be invoked effectively be invoked after any `@AfterReturning` or `@AfterThrowing` advice methods
after any `@AfterReturning` or `@AfterThrowing` advice methods in the same aspect. in the same aspect, following AspectJ's "after finally advice" semantics for `@After`.
When two pieces of the same type of advice (for example, two `@After` advice methods) When two pieces of the same type of advice (for example, two `@After` advice methods)
defined in the same `@Aspect` class both need to run at the same join point, the ordering defined in the same `@Aspect` class both need to run at the same join point, the ordering
is undefined (since there is no way to retrieve the source code declaration order through is undefined (since there is no way to retrieve the source code declaration order through
reflection for javac-compiled classes). Consider collapsing such advice methods into one reflection for javac-compiled classes). Consider collapsing such advice methods into one
advice method per join point in each `@Aspect` class or refactor the pieces of advice advice method per join point in each `@Aspect` class or refactor the pieces of advice into
into separate `@Aspect` classes that you can order at the aspect level via `Ordered` or separate `@Aspect` classes that you can order at the aspect level via `Ordered` or `@Order`.
`@Order`.
==== ====
@ -1678,11 +1680,11 @@ Introductions (known as inter-type declarations in AspectJ) enable an aspect to
that advised objects implement a given interface, and to provide an implementation of that advised objects implement a given interface, and to provide an implementation of
that interface on behalf of those objects. that interface on behalf of those objects.
You can make an introduction by using the `@DeclareParents` annotation. This annotation is used You can make an introduction by using the `@DeclareParents` annotation. This annotation
to declare that matching types have a new parent (hence the name). For example, given an is used to declare that matching types have a new parent (hence the name). For example,
interface named `UsageTracked` and an implementation of that interface named `DefaultUsageTracked`, given an interface named `UsageTracked` and an implementation of that interface named
the following aspect declares that all implementors of service interfaces also implement `DefaultUsageTracked`, the following aspect declares that all implementors of service
the `UsageTracked` interface (to expose statistics via JMX for example): interfaces also implement the `UsageTracked` interface (e.g. for statistics via JMX):
[source,java,indent=0,subs="verbatim,quotes",role="primary"] [source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java .Java
@ -1764,7 +1766,6 @@ annotation. Consider the following example:
public void recordServiceUsage() { public void recordServiceUsage() {
// ... // ...
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@ -1779,7 +1780,6 @@ annotation. Consider the following example:
fun recordServiceUsage() { fun recordServiceUsage() {
// ... // ...
} }
} }
---- ----
@ -1854,7 +1854,6 @@ call `proceed` multiple times. The following listing shows the basic aspect impl
} while(numAttempts <= this.maxRetries); } while(numAttempts <= this.maxRetries);
throw lockFailureException; throw lockFailureException;
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@ -2066,7 +2065,6 @@ as the following example shows:
expression="execution(* com.xyz.myapp.service.*.*(..))"/> expression="execution(* com.xyz.myapp.service.*.*(..))"/>
... ...
</aop:aspect> </aop:aspect>
</aop:config> </aop:config>
@ -2088,7 +2086,6 @@ collects the `this` object as the join point context and passes it to the advice
<aop:before pointcut-ref="businessService" method="monitor"/> <aop:before pointcut-ref="businessService" method="monitor"/>
... ...
</aop:aspect> </aop:aspect>
</aop:config> </aop:config>
@ -2179,7 +2176,6 @@ a `pointcut` attribute, as follows:
method="doAccessCheck"/> method="doAccessCheck"/>
... ...
</aop:aspect> </aop:aspect>
---- ----
@ -2209,7 +2205,6 @@ shows how to declare it:
method="doAccessCheck"/> method="doAccessCheck"/>
... ...
</aop:aspect> </aop:aspect>
---- ----
@ -2227,7 +2222,6 @@ the return value should be passed, as the following example shows:
method="doAccessCheck"/> method="doAccessCheck"/>
... ...
</aop:aspect> </aop:aspect>
---- ----
@ -2263,7 +2257,6 @@ as the following example shows:
method="doRecoveryActions"/> method="doRecoveryActions"/>
... ...
</aop:aspect> </aop:aspect>
---- ----
@ -2281,13 +2274,12 @@ which the exception should be passed as the following example shows:
method="doRecoveryActions"/> method="doRecoveryActions"/>
... ...
</aop:aspect> </aop:aspect>
---- ----
The `doRecoveryActions` method must declare a parameter named `dataAccessEx`. The type of The `doRecoveryActions` method must declare a parameter named `dataAccessEx`.
this parameter constrains matching in the same way as described for `@AfterThrowing`. For The type of this parameter constrains matching in the same way as described for
example, the method signature may be declared as follows: `@AfterThrowing`. For example, the method signature may be declared as follows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"] [source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java .Java
@ -2304,8 +2296,8 @@ example, the method signature may be declared as follows:
[[aop-schema-advice-after-finally]] [[aop-schema-advice-after-finally]]
==== After (Finally) Advice ==== After (Finally) Advice
After (finally) advice runs no matter how a matched method execution exits. You can declare it After (finally) advice runs no matter how a matched method execution exits.
by using the `after` element, as the following example shows: You can declare it by using the `after` element, as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"] [source,xml,indent=0,subs="verbatim,quotes"]
---- ----
@ -2316,7 +2308,6 @@ by using the `after` element, as the following example shows:
method="doReleaseLock"/> method="doReleaseLock"/>
... ...
</aop:aspect> </aop:aspect>
---- ----
@ -2327,17 +2318,17 @@ by using the `after` element, as the following example shows:
The last kind of advice is around advice. Around advice runs "around" a matched method The last kind of advice is around advice. Around advice runs "around" a matched method
execution. It has the opportunity to do work both before and after the method runs execution. It has the opportunity to do work both before and after the method runs
and to determine when, how, and even if the method actually gets to run at all. and to determine when, how, and even if the method actually gets to run at all.
Around advice is often used to share state before and after a method Around advice is often used to share state before and after a method execution in a
execution in a thread-safe manner (starting and stopping a timer, for example). Always thread-safe manner (starting and stopping a timer, for example). Always use the least
use the least powerful form of advice that meets your requirements. Do not use around powerful form of advice that meets your requirements. Do not use around advice if
advice if before advice can do the job. before advice can do the job.
You can declare around advice by using the `aop:around` element. The first parameter of the You can declare around advice by using the `aop:around` element. The first parameter of
advice method must be of type `ProceedingJoinPoint`. Within the body of the advice, the advice method must be of type `ProceedingJoinPoint`. Within the body of the advice,
calling `proceed()` on the `ProceedingJoinPoint` causes the underlying method to calling `proceed()` on the `ProceedingJoinPoint` causes the underlying method to run.
run. The `proceed` method may also be called with an `Object[]`. The values The `proceed` method may also be called with an `Object[]`. The values in the array
in the array are used as the arguments to the method execution when it proceeds. See are used as the arguments to the method execution when it proceeds.
<<aop-ataspectj-around-advice>> for notes on calling `proceed` with an `Object[]`. See <<aop-ataspectj-around-advice>> for notes on calling `proceed` with an `Object[]`.
The following example shows how to declare around advice in XML: The following example shows how to declare around advice in XML:
[source,xml,indent=0,subs="verbatim,quotes"] [source,xml,indent=0,subs="verbatim,quotes"]
@ -2349,7 +2340,6 @@ The following example shows how to declare around advice in XML:
method="doBasicProfiling"/> method="doBasicProfiling"/>
... ...
</aop:aspect> </aop:aspect>
---- ----
@ -2763,7 +2753,6 @@ call `proceed` multiple times. The following listing shows the basic aspect impl
} while(numAttempts <= this.maxRetries); } while(numAttempts <= this.maxRetries);
throw lockFailureException; throw lockFailureException;
} }
} }
---- ----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]