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 things like GORM or Spring Data that will both automatically implement persistence logic for you.

Micronaut’s Client 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 @Stub annotation:

Introduction Advice Annotation Example

  1. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  2. import io.micronaut.aop.Introduction;
  3. import io.micronaut.context.annotation.Bean;
  4. import io.micronaut.context.annotation.Type;
  5. import java.lang.annotation.Documented;
  6. import java.lang.annotation.ElementType;
  7. import java.lang.annotation.Retention;
  8. import java.lang.annotation.Target;
  9. @Introduction (1)
  10. @Type(StubIntroduction.class) (2)
  11. @Bean (3)
  12. @Documented
  13. @Retention(RUNTIME)
  14. @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.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 io.micronaut.context.annotation.Type
  4. import java.lang.annotation.Documented
  5. import java.lang.annotation.ElementType
  6. import java.lang.annotation.Retention
  7. import java.lang.annotation.Target
  8. import static java.lang.annotation.RetentionPolicy.RUNTIME
  9. @Introduction (1)
  10. @Type(StubIntroduction.class) (2)
  11. @Bean (3)
  12. @Documented
  13. @Retention(RUNTIME)
  14. @Target([ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.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 io.micronaut.context.annotation.Type
  4. import java.lang.annotation.Documented
  5. import java.lang.annotation.Retention
  6. import java.lang.annotation.RetentionPolicy.RUNTIME
  7. @Introduction (1)
  8. @Type(StubIntroduction::class) (2)
  9. @Bean (3)
  10. @Documented
  11. @Retention(RUNTIME)
  12. @Target(AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
  13. annotation class Stub(val value: String = "")
1The introduction advice is annotated with Introduction
2The Type annotation is used to refer to the implementor of the advice. In this case StubIntroduction
3The 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 javax.inject.Singleton;
  3. @Singleton
  4. public class StubIntroduction implements MethodInterceptor<Object,Object> { (1)
  5. @Override
  6. public Object intercept(MethodInvocationContext<Object, Object> context) {
  7. return context.getValue( (2)
  8. Stub.class,
  9. context.getReturnType().getType()
  10. ).orElse(null); (3)
  11. }
  12. }

StubIntroduction

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

StubIntroduction

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

To now use this introduction advice in an application you simply 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 will 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. StubExample stubExample = applicationContext.getBean(StubExample.class)
  3. then:
  4. stubExample.getNumber() == 10
  5. stubExample.getDate() == null

Testing Introduction Advice

  1. val stubExample = applicationContext.getBean(StubExample::class.java)
  2. stubExample.number.toLong().shouldBe(10)
  3. stubExample.date.shouldBe(null)

Note that if the introduction advice cannot implement the method the proceed method of the MethodInvocationContext should be called. This gives the opportunity for other introduction advice interceptors to implement the method, otherwise a 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 advise.

The following sections cover core advice types that are built into Micronaut and provided by the framework.