Advice life cycles
Each advisory is a Spring bean. An instance of an advice may be common to all advice-equipped objects or unique to each advice-equipped object. This corresponds to per-class or per-instance advice.
The most commonly used advice is per-class. This is suitable for general advice such as transactional advisors. They do not depend on the state of the proxied object and do not add new state. They simply work according to the method and arguments.
Hints for each instance are suitable for introductions, to support mixins. In this case, the advice adds state to the proxied object.
You can use a mixture of general and per-instance advice in a single AOP proxy.
Types of advice in Spring
Spring provides several tip types and can be extended to support custom tip types. This section describes the basic concepts and standard types of advice.
Interception Around Tip
The most fundamental type of advice in Spring is the Interception Around tip.
Spring is compatible with the AOP
Alliance
project interface to work with tips that use method call interception. Classes that implement
MethodInterceptor
and implement the "intercept" tip must also implement the following interface:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
The MethodInvocation
argument of the invoke()
method opens the called method,
the connection target, the AOP proxy and method arguments. The invoke()
method must return the result
of the call: the return value of the connection point.
The following example shows a sample implementation of
MethodInterceptor
:
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}
class DebugInterceptor : MethodInterceptor {
override fun invoke(invocation: MethodInvocation): Any {
println ("Before: invocation=[$invocation]")
val rval = invocation.proceed()
println("Invocation returned")
return rval
}
}
Note the call to the proceed()
method from MethodInvocation
. It follows a chain of
interceptors to the connection point. Most interceptors call this method and return its return value. However,
MethodInterceptor
, like any other tip, can return a different value or throw an exception instead of
calling the continuation method. But you shouldn't do this without a good reason.
MethodInterceptor
implementations provide compatibility with
other AOP Alliance implementations that comply with AOP requirements. The other types of advice discussed in the
remainder of this section implement general AOP concepts, but in a Spring-specific way. While there is an advantage
to using the most specific type of advice, stick with the MethodInterceptor
advice if you might need to
implement an aspect in another AOP framework. Note that slices are currently not functionally compatible between
frameworks, and the AOP Alliance cannot currently define slice interfaces.
Before Tip
A simpler type of tip is the "before" tip ( before)". It does not require a MethodInvocation
object because it is called only before the method is entered.
The main advantage of the "before" advice is
that there is no need to call the proceed()
method and therefore there is no way to inadvertently fail
to continue the interceptor chain.
The following listing shows the MethodBeforeAdvice
interface:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
(The Spring API structure allows for a field before the advice, although field interception is handled by normal objects, so it is unlikely that Spring will ever implement this ).
Note that the return type is
void
. The "before" advice is capable of injecting custom logic before the join point is run, but cannot
change the return value. If the "before" advice throws an exception, then further execution of the interceptor chain
will stop. The exception will propagate back up the chain of interceptors. If it is not marked or is in the
signature of the called method, then it will be passed directly to the client code. Otherwise, it will be wrapped in
an unchecked exception by the AOP proxy.
The following example shows the "before" tip in Spring that counts all method calls:
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
} public int getCount() {
return count;
}
}
class CountingBeforeAdvice : MethodBeforeAdvice {
var count: Int = 0
override fun before(m: Method, args: Array<Any>, target: Any?) {
++count
}
}
The "throw exception" tip
The "throw exception (throws)" tip is called after the join point is returned if the join point has
thrown an exception . Spring introduces a typed "throw an exception" tip. Note that this means that the org.springframework.aop.ThrowsAdvice
interface does not contain any methods. This is an interface tag that identifies that this object implements one or
more typed methods of the "throw an exception" advice. They should have the following form:
afterThrowing([Method, args, target], subclassOfThrowable)
Only the last argument is required. Method signatures can have either one or four arguments, depending on whether the advice method is interested in the method and arguments. The following two listings show classes that are examples of "throw exception" tips.
If a RemoteException
exception is thrown (including from
subclasses), the following tip is called:
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with the remote exception
}
}
class RemoteThrowsAdvice : ThrowsAdvice {
fun afterThrowing(ex: RemoteException) {
// Do something with the remote exception
}
}
Unlike the previous tip, the following example declares four arguments, so the tip has access to the
method being called, the method arguments, and the target object. When a ServletException
occurs, the
following advice is called:
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args , Object target, ServletException ex) {
// Do something with all the arguments
}
}
class ServletThrowsAdviceWithArguments : ThrowsAdvice {
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
// Do something with all the arguments
}
}
The last example illustrates how these two methods can be used in a single class that handles both RemoteException
and ServletException
. In one class you can combine any number of methods of the “throwing an exception”
advice. The following listing shows the latest example:
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all the arguments
}
}
class CombinedThrowsAdvice : ThrowsAdvice {
fun afterThrowing(ex: RemoteException) {
// Do something with the remote exception
}
fun afterThrowing(m: Method, args: Array<Any> , target: Any, ex: ServletException) {
// Do something with all the arguments
}
}
Returning Advice
The "after returning" advice in Spring must implement the org.springframework.aop.AfterReturningAdvice
interface, as shown in the following listing:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
The after-return advice has access to the return value (which it cannot change), the method called, the method arguments, and the target.
The following "after return" tip counts all successful method calls that did not throw an exception:
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
class CountingAfterReturningAdvice : AfterReturningAdvice {
var count: Int = 0
private set
override fun afterReturning( returnValue: Any?, m: Method, args: Array<Any>, target: Any?) {
++count
}
}
This advice does not change execution path. If it throws an exception, it is passed along the interceptor chain instead of the return value.
Introduction Advice
Spring treats the "Introduction" advice as a special kind of "interception" advice.
Introduction requires IntroductionAdvisor
and IntroductionInterceptor
, which implement
the following interface:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
The invoke()
method, inherited from the AOP Alliance MethodInterceptor
interface,
must implement the introduction. Thus, if the called method is in the injected interface, the injection interceptor
is responsible for handling the method call - it cannot call proceed()
.
The "introduce" tip
cannot be used with any slice , since it only applies at the class level, not the method level. You can only use the
"introduction" advice with IntroductionAdvisor
, which has the following methods:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class<?>[] getInterfaces();
}
There is no MethodMatcher
and therefore no Pointcut
associated with the
"introduction" tip. Only class filtering is logical.
The getInterfaces()
method returns the
interfaces represented by this EA.
The validateInterfaces()
method is used to check whether the
injected interfaces can be implemented by the configured IntroductionInterceptor
.
Consider an example from the Spring test suite and assume that we want to inject the following interface into one or more objects:
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
interface Lockable {
fun lock()
fun unlock()
fun locked(): Boolean
}
This example illustrates a mixin. We need to be able to cast advised objects to Lockable
,
regardless of their type, and call locking and unlocking methods. If we call the lock()
method, we want
all setters to throw a LockedException
exception. So we can add an aspect that provides the ability to
make objects immutable without them knowing about it: a good example of AOP.
First, we need an IntroductionInterceptor
that will do all the hard work. In this case, we extend the org.springframework.aop.support.DelegatingIntroductionInterceptor
helper class. It would be possible to implement IntroductionInterceptor
directly, but using DelegatingIntroductionInterceptor
is better suited for most cases.
DelegatingIntroductionInterceptor
is intended to delegate the
actual introduction implementation of the introduced interfaces, hiding the use of interception for this purpose.
You can set a delegate for any object using a constructor argument. The default delegate (when using a no-argument
constructor) is this
. Thus, in the following example, the delegate is a LockMixin
subclass
of the DelegatingIntroductionInterceptor
class. Given a delegate (by default, itself), an instance of
DelegatingIntroductionInterceptor
looks up all interfaces implemented by the delegate (except IntroductionInterceptor
)
and supports introductions for any of them. Subclasses such as LockMixin
can call the suppressInterface(Class
intf)
method to suppress interfaces that should not be open. However, no matter how many interfaces the
IntroductionInterceptor
is willing to support, the IntroductionAdvisor
used controls which
interfaces will actually be exposed. The exposed interface hides any implementation of the same interface by an
object.
Thus, LockMixin
extends DelegatingIntroductionInterceptor
and implements
Lockable
itself. The superclass automatically determines that Lockable
can be supported
for insertion, so there is no need to specify this. This way we can introduce any number of interfaces.
Note
the use of the locked
instance variable. This allows you to effectively add additional state to the one
stored in the target object.
The following example shows an example of the LockMixin
class:
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
private boolean locked;
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
public boolean locked() {
return this.locked;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
throw new LockedException();
}
return super.invoke(invocation);
}
}
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {
private var locked: Boolean = false
fun lock() {
this.locked = true
}
fun unlock() {
this.locked = false
}
fun locked(): Boolean {
return this.locked
}
override fun invoke(invocation: MethodInvocation): Any? {
if (locked() && invocation.method.name.indexOf("set") == 0) {
throw LockedException()
}
return super.invoke(invocation)
}
}
Often there is no need to override the invoke()
method. An implementation of DelegatingIntroductionInterceptor
(which calls the delegate
method if that method is present, and otherwise goes to the connection
point) is usually sufficient. In this case, we need to add a check: the setter cannot be called if it is in
locked mode.
The required injection must contain only a single instance of LockMixin
and indicate the interfaces
entered (in this case only Lockable
). A more complex example might contain a reference to an
input interceptor (which would be defined as a prototype). In this case, there is no configuration for LockMixin
,
so we create it using new
. The following example shows our LockMixinAdvisor
class:
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
This EA can be used without interference as it does not require any configuration. (However, you cannot use
IntroductionInterceptor
without IntroductionAdvisor
). As is usually the case with
introductions, the EA must be instantiated first because it saves state. We require a separate instance of
LockMixinAdvisor
, and therefore LockMixin
, for each advised object. The advisor
includes part of the state of the advised object.
We can apply this advisor programmatically using the Advised.addAdvisor()
method or (the recommended
way) in the XML configuration, like any another advisor. All proxy creation options discussed below, including
"auto-proxy creators", handle injections and stateful mixins properly.
6.3. Spring Advisor API
In Spring, an advisor is an aspect that contains only one advice object associated with a slice expression.
Except in the special case of introduction, any advisor can be used with any advice. org.springframework.aop.support.DefaultPointcutAdvisor
is the most commonly used advisor class. It can be used with MethodInterceptor
,
BeforeAdvice
or ThrowsAdvice
.
In Spring, you can mix types of advisors and tips in a single AOP proxy . For example, you can use the "catch" tip, the "throw exception" tip, and the "before" tip in the same proxy configuration.Spring automatically creates the necessary chain of interceptors.
GO TO FULL VERSION