Examples of introduction advice include things like or Spring Data that will both automatically implement persistence logic for you.
Micronaut’s annotation is another example of introduction advice where Micronaut will, at compile time, implement HTTP client interfaces for you.
The way you implement Introduction advice is very similar to how you implement Around advice.
You start off by defining an annotation that will power the introduction advice. As an example, say you want to implement advice that will return a stubbed value for every method in an interface (a common requirement in testing frameworks). Consider the following annotation:
Introduction Advice Annotation Example
Introduction Advice Annotation Example
import io.micronaut.aop.Introduction
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Type
import java.lang.annotation.Documented
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.Target
import static java.lang.annotation.RetentionPolicy.RUNTIME
@Introduction (1)
@Type(StubIntroduction.class) (2)
@Bean (3)
@Documented
@Retention(RUNTIME)
@Target([ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD])
@interface Stub {
String value() default ""
}
Introduction Advice Annotation Example
import io.micronaut.aop.Introduction
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Type
import java.lang.annotation.Documented
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy.RUNTIME
@Introduction (1)
@Bean (3)
@Documented
@Retention(RUNTIME)
annotation class Stub(val value: String = "")
The following is an example implementation:
StubIntroduction
import io.micronaut.aop.*;
import javax.inject.Singleton;
@Singleton
public class StubIntroduction implements MethodInterceptor<Object,Object> { (1)
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
return context.getValue( (2)
Stub.class,
context.getReturnType().getType()
).orElse(null); (3)
}
}
StubIntroduction
StubIntroduction
import io.micronaut.aop.MethodInterceptor
import io.micronaut.aop.MethodInvocationContext
import javax.inject.Singleton
@Singleton
class StubIntroduction : MethodInterceptor<Any, Any> { (1)
override fun intercept(context: MethodInvocationContext<Any, Any>): Any? {
return context.getValue<Any>( (2)
Stub::class.java,
).orElse(null) (3)
}
}
1 | The class is annotated with @Singleton and implements the MethodInterceptor interface |
2 | The value of the annotation is read from the context and an attempt made to convert the value to the return type |
3 | Otherwise null is returned |
To now use this introduction advice in an application you simply 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()
}
All abstract methods will 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:
StubExample stubExample = applicationContext.getBean(StubExample.class)
then:
stubExample.getNumber() == 10
stubExample.getDate() == null
Testing Introduction Advice
val stubExample = applicationContext.getBean(StubExample::class.java)
stubExample.date.shouldBe(null)
Note that if the introduction advice cannot implement the method the proceed
method of the should be called. This gives the opportunity for other introduction advice interceptors to implement the method, otherwise a 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 advise.