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

  1. import io.micronaut.aop.Introduction;
  2. import io.micronaut.context.annotation.Bean;
  3. import java.lang.annotation.Documented;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.Target;
  6. import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
  7. import static java.lang.annotation.ElementType.METHOD;
  8. import static java.lang.annotation.ElementType.TYPE;
  9. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  10. @Introduction (1)
  11. @Bean (2)
  12. @Documented
  13. @Retention(RUNTIME)
  14. @Target({TYPE, ANNOTATION_TYPE, METHOD})
  15. public @interface Stub {
  16. String value() default "";
  17. }

Introduction Advice Annotation Example

  1. import io.micronaut.aop.Introduction
  2. import io.micronaut.context.annotation.Bean
  3. import java.lang.annotation.Documented
  4. import java.lang.annotation.Retention
  5. import java.lang.annotation.Target
  6. import static java.lang.annotation.ElementType.ANNOTATION_TYPE
  7. import static java.lang.annotation.ElementType.METHOD
  8. import static java.lang.annotation.ElementType.TYPE
  9. import static java.lang.annotation.RetentionPolicy.RUNTIME
  10. @Introduction (1)
  11. @Bean (2)
  12. @Documented
  13. @Retention(RUNTIME)
  14. @Target([TYPE, ANNOTATION_TYPE, METHOD])
  15. @interface Stub {
  16. String value() default ""
  17. }

Introduction Advice Annotation Example

  1. import io.micronaut.aop.Introduction
  2. import io.micronaut.context.annotation.Bean
  3. import kotlin.annotation.AnnotationRetention.RUNTIME
  4. import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
  5. import kotlin.annotation.AnnotationTarget.CLASS
  6. import kotlin.annotation.AnnotationTarget.FILE
  7. import kotlin.annotation.AnnotationTarget.FUNCTION
  8. import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
  9. import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER
  10. @Introduction (1)
  11. @Bean (2)
  12. @MustBeDocumented
  13. @Retention(RUNTIME)
  14. @Target(CLASS, FILE, ANNOTATION_CLASS, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
  15. annotation class Stub(val value: String = "")
1The introduction advice is annotated with Introduction
2The 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

  1. import io.micronaut.aop.*;
  2. import io.micronaut.core.annotation.Nullable;
  3. import jakarta.inject.Singleton;
  4. @Singleton
  5. @InterceptorBean(Stub.class) (1)
  6. public class StubIntroduction implements MethodInterceptor<Object,Object> { (2)
  7. @Nullable
  8. @Override
  9. public Object intercept(MethodInvocationContext<Object, Object> context) {
  10. return context.getValue( (3)
  11. Stub.class,
  12. context.getReturnType().getType()
  13. ).orElse(null); (4)
  14. }
  15. }

StubIntroduction

  1. import io.micronaut.aop.MethodInterceptor
  2. import io.micronaut.aop.MethodInvocationContext
  3. import io.micronaut.aop.InterceptorBean
  4. import io.micronaut.core.annotation.Nullable
  5. import jakarta.inject.Singleton
  6. @Singleton
  7. @InterceptorBean(Stub) (1)
  8. class StubIntroduction implements MethodInterceptor<Object,Object> { (2)
  9. @Nullable
  10. @Override
  11. Object intercept(MethodInvocationContext<Object, Object> context) {
  12. context.getValue( (3)
  13. Stub,
  14. context.returnType.type
  15. ).orElse(null) (4)
  16. }
  17. }

StubIntroduction

  1. import io.micronaut.aop.*
  2. import jakarta.inject.Singleton
  3. @Singleton
  4. @InterceptorBean(Stub::class) (1)
  5. class StubIntroduction : MethodInterceptor<Any, Any> { (2)
  6. override fun intercept(context: MethodInvocationContext<Any, Any>): Any? {
  7. return context.getValue<Any>( (3)
  8. Stub::class.java,
  9. context.returnType.type
  10. ).orElse(null) (4)
  11. }
  12. }
1The InterceptorBean annotation is used to associate the interceptor with the @Stub annotation
2The class is annotated with @Singleton and implements the MethodInterceptor interface
3The value of the @Stub annotation is read from the context and an attempt made to convert the value to the return type
4Otherwise null is returned

To now use this introduction advice in an application, annotate your abstract classes or interfaces with @Stub:

StubExample

  1. @Stub
  2. public interface StubExample {
  3. @Stub("10")
  4. int getNumber();
  5. LocalDateTime getDate();
  6. }

StubExample

  1. @Stub
  2. interface StubExample {
  3. @Stub("10")
  4. int getNumber()
  5. LocalDateTime getDate()
  6. }

StubExample

  1. @Stub
  2. interface StubExample {
  3. @get:Stub("10")
  4. val number: Int
  5. val date: LocalDateTime?
  6. }

All abstract methods delegate to the StubIntroduction class to be implemented.

The following test demonstrates the behaviour or StubIntroduction:

Testing Introduction Advice

  1. StubExample stubExample = applicationContext.getBean(StubExample.class);
  2. assertEquals(10, stubExample.getNumber());
  3. assertNull(stubExample.getDate());

Testing Introduction Advice

  1. when:
  2. def stubExample = applicationContext.getBean(StubExample)
  3. then:
  4. stubExample.number == 10
  5. stubExample.date == null

Testing Introduction Advice

  1. val stubExample = applicationContext.getBean(StubExample::class.java)
  2. stubExample.number.shouldBe(10)
  3. 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.