5.2 Introduction Advice
Introduction advice is distinct from Around advice in that it involves providing an implementation instead of decorating.
Examples of introduction advice include GORM and Spring Data which implement persistence logic for you.
Micronaut’s Client annotation is another example of introduction advice where Micronaut implements HTTP client interfaces for you at compile time.
The way you implement Introduction advice is very similar to how you implement Around advice.
You start by defining an annotation that powers the introduction advice. As an example, say you want to implement advice to return a stubbed value for every method in an interface (a common requirement in testing frameworks). Consider the following @Stub
annotation:
Introduction Advice Annotation Example
import io.micronaut.aop.Introduction;
import io.micronaut.context.annotation.Bean;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Introduction (1)
@Bean (2)
@Documented
@Retention(RUNTIME)
@Target({TYPE, ANNOTATION_TYPE, METHOD})
public @interface Stub {
String value() default "";
}
Introduction Advice Annotation Example
import io.micronaut.aop.Introduction
import io.micronaut.context.annotation.Bean
import java.lang.annotation.Documented
import java.lang.annotation.Retention
import java.lang.annotation.Target
import static java.lang.annotation.ElementType.ANNOTATION_TYPE
import static java.lang.annotation.ElementType.METHOD
import static java.lang.annotation.ElementType.TYPE
import static java.lang.annotation.RetentionPolicy.RUNTIME
@Introduction (1)
@Bean (2)
@Documented
@Retention(RUNTIME)
@Target([TYPE, ANNOTATION_TYPE, METHOD])
@interface Stub {
String value() default ""
}
Introduction Advice Annotation Example
import io.micronaut.aop.Introduction
import io.micronaut.context.annotation.Bean
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FILE
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER
@Introduction (1)
@Bean (2)
@MustBeDocumented
@Retention(RUNTIME)
@Target(CLASS, FILE, ANNOTATION_CLASS, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
annotation class Stub(val value: String = "")
1 | The introduction advice is annotated with Introduction |
2 | The Bean annotation is added so that all types annotated with @Stub become beans |
The StubIntroduction
class referred to in the previous example must then implement the MethodInterceptor interface, just like around advice.
The following is an example implementation:
StubIntroduction
import io.micronaut.aop.*;
import io.micronaut.core.annotation.Nullable;
import jakarta.inject.Singleton;
@Singleton
@InterceptorBean(Stub.class) (1)
public class StubIntroduction implements MethodInterceptor<Object,Object> { (2)
@Nullable
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
return context.getValue( (3)
Stub.class,
context.getReturnType().getType()
).orElse(null); (4)
}
}
StubIntroduction
import io.micronaut.aop.MethodInterceptor
import io.micronaut.aop.MethodInvocationContext
import io.micronaut.aop.InterceptorBean
import io.micronaut.core.annotation.Nullable
import jakarta.inject.Singleton
@Singleton
@InterceptorBean(Stub) (1)
class StubIntroduction implements MethodInterceptor<Object,Object> { (2)
@Nullable
@Override
Object intercept(MethodInvocationContext<Object, Object> context) {
context.getValue( (3)
Stub,
context.returnType.type
).orElse(null) (4)
}
}
StubIntroduction
import io.micronaut.aop.*
import jakarta.inject.Singleton
@Singleton
@InterceptorBean(Stub::class) (1)
class StubIntroduction : MethodInterceptor<Any, Any> { (2)
override fun intercept(context: MethodInvocationContext<Any, Any>): Any? {
return context.getValue<Any>( (3)
Stub::class.java,
context.returnType.type
).orElse(null) (4)
}
}
1 | The InterceptorBean annotation is used to associate the interceptor with the @Stub annotation |
2 | The class is annotated with @Singleton and implements the MethodInterceptor interface |
3 | The value of the @Stub annotation is read from the context and an attempt made to convert the value to the return type |
4 | Otherwise null is returned |
To now use this introduction advice in an application, annotate your abstract classes or interfaces with @Stub
:
StubExample
@Stub
public interface StubExample {
@Stub("10")
int getNumber();
LocalDateTime getDate();
}
StubExample
@Stub
interface StubExample {
@Stub("10")
int getNumber()
LocalDateTime getDate()
}
StubExample
@Stub
interface StubExample {
@get:Stub("10")
val number: Int
val date: LocalDateTime?
}
All abstract methods delegate to the StubIntroduction
class to be implemented.
The following test demonstrates the behaviour or StubIntroduction
:
Testing Introduction Advice
StubExample stubExample = applicationContext.getBean(StubExample.class);
assertEquals(10, stubExample.getNumber());
assertNull(stubExample.getDate());
Testing Introduction Advice
when:
def stubExample = applicationContext.getBean(StubExample)
then:
stubExample.number == 10
stubExample.date == null
Testing Introduction Advice
val stubExample = applicationContext.getBean(StubExample::class.java)
stubExample.number.shouldBe(10)
stubExample.date.shouldBe(null)
Note that if the introduction advice cannot implement the method, call the proceed
method of the MethodInvocationContext. This lets other introduction advice interceptors implement the method, and an UnsupportedOperationException
will be thrown if no advice can implement the method.
In addition, if multiple introduction advice are present you may wish to override the getOrder()
method of MethodInterceptor to control the priority of advice.
The following sections cover core advice types provided by Micronaut.