CodeGym /Java Course /Module 5. Spring /Tips API in Spring

Tips API in Spring

Module 5. Spring
Level 5 , Lesson 9
Available

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:

Java

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;
    }
}
Kotlin

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:

Java

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;
    }
}
Kotlin

class CountingBeforeAdvice : MethodBeforeAdvice {
    var count: Int = 0
    override fun before(m: Method, args: Array<Any>, target: Any?) {
        ++count
    }
}
The "before" tip can be used with any slice.

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:

Java

public class RemoteThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with the remote exception
    }
}
Kotlin

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:

Java

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
    public void afterThrowing(Method m, Object[] args , Object target, ServletException ex) {
        // Do something with all the arguments
    }
}
Kotlin

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:

Java

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
    }
}
Kotlin

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
    }
}
If the "throw exception" tip method throws an exception itself, it overrides the original exception (that is, it modifies the exception thrown for the user). An override exception is typically a RuntimeException, which is compatible with any method signature. However, if the "throw exception" tip method throws a checked exception, it must match the target method's declared exceptions and therefore be bound to some extent by the target method's specific signatures. Do not throw an undeclared checked exception that is incompatible with the signature of the target method!
The "throw an exception" tip can be used with any slice .

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:

Java

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;
    }
}
Kotlin

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.

The "after return" tip can be used with any slice.

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:

Java

public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}
Kotlin

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:

Java

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);
    }
}
Kotlin

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:

Java

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}
Kotlin
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.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION