Writing Around Advice

The first step to defining Around advice is to implement a MethodInterceptor. For example the following interceptor disallows parameters with null values:

MethodInterceptor Example

  1. import io.micronaut.aop.*;
  2. import io.micronaut.core.type.MutableArgumentValue;
  3. import javax.inject.Singleton;
  4. import java.util.*;
  5. @Singleton
  6. public class NotNullInterceptor implements MethodInterceptor<Object, Object> { (1)
  7. @Override
  8. public Object intercept(MethodInvocationContext<Object, Object> context) {
  9. Optional<Map.Entry<String, MutableArgumentValue<?>>> nullParam = context.getParameters()
  10. .entrySet()
  11. .stream()
  12. .filter(entry -> {
  13. MutableArgumentValue<?> argumentValue = entry.getValue();
  14. return Objects.isNull(argumentValue.getValue());
  15. })
  16. .findFirst(); (2)
  17. if (nullParam.isPresent()) {
  18. throw new IllegalArgumentException("Null parameter [" + nullParam.get().getKey() + "] not allowed"); (3)
  19. } else {
  20. return context.proceed(); (4)
  21. }
  22. }
  23. }

MethodInterceptor Example

  1. import io.micronaut.aop.MethodInterceptor
  2. import io.micronaut.aop.MethodInvocationContext
  3. import io.micronaut.core.type.MutableArgumentValue
  4. import javax.inject.Singleton
  5. @Singleton
  6. class NotNullInterceptor implements MethodInterceptor<Object, Object> { (1)
  7. @Override
  8. Object intercept(MethodInvocationContext<Object, Object> context) {
  9. Optional<Map.Entry<String, MutableArgumentValue<?>>> nullParam = context.getParameters()
  10. .entrySet()
  11. .stream()
  12. .filter({entry ->
  13. MutableArgumentValue<?> argumentValue = entry.getValue()
  14. return Objects.isNull(argumentValue.getValue())
  15. })
  16. .findFirst() (2)
  17. if (nullParam.isPresent()) {
  18. throw new IllegalArgumentException("Null parameter [" + nullParam.get().getKey() + "] not allowed") (3)
  19. } else {
  20. return context.proceed() (4)
  21. }
  22. }
  23. }

MethodInterceptor Example

  1. import io.micronaut.aop.MethodInterceptor
  2. import io.micronaut.aop.MethodInvocationContext
  3. import io.micronaut.core.type.MutableArgumentValue
  4. import javax.inject.Singleton
  5. import java.util.Objects
  6. import java.util.Optional
  7. @Singleton
  8. class NotNullInterceptor : MethodInterceptor<Any, Any> { (1)
  9. override fun intercept(context: MethodInvocationContext<Any, Any>): Any {
  10. val nullParam = context.parameters
  11. .entries
  12. .stream()
  13. .filter { entry ->
  14. val argumentValue = entry.value
  15. Objects.isNull(argumentValue.value)
  16. }
  17. .findFirst() (2)
  18. return if (nullParam.isPresent) {
  19. throw IllegalArgumentException("Null parameter [" + nullParam.get().key + "] not allowed") (3)
  20. } else {
  21. context.proceed() (4)
  22. }
  23. }
  24. }
1An interceptor implements the MethodInterceptor interface
2The passed MethodInvocationContext is used to find the first parameter that is null
3If a null parameter is found an exception is thrown
4Otherwise proceed() is called to proceed with the method invocation.
Micronaut AOP interceptors use no reflection which improves performance and reducing stack trace sizes, thus improving debugging.

To put the new MethodInterceptor to work the next step is to define an annotation that will trigger the MethodInterceptor:

Around Advice Annotation Example

  1. import io.micronaut.context.annotation.Type;
  2. import io.micronaut.aop.Around;
  3. import java.lang.annotation.*;
  4. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  5. @Documented
  6. @Retention(RUNTIME) (1)
  7. @Target({ElementType.TYPE, ElementType.METHOD}) (2)
  8. @Around (3)
  9. @Type(NotNullInterceptor.class) (4)
  10. public @interface NotNull {
  11. }

Around Advice Annotation Example

  1. import io.micronaut.aop.Around
  2. import io.micronaut.context.annotation.Type
  3. import java.lang.annotation.Documented
  4. import java.lang.annotation.ElementType
  5. import java.lang.annotation.Retention
  6. import java.lang.annotation.Target
  7. import static java.lang.annotation.RetentionPolicy.RUNTIME
  8. @Documented
  9. @Retention(RUNTIME) (1)
  10. @Target([ElementType.TYPE, ElementType.METHOD]) (2)
  11. @Around (3)
  12. @Type(NotNullInterceptor.class) (4)
  13. @interface NotNull {
  14. }

Around Advice Annotation Example

  1. import io.micronaut.aop.Around
  2. import io.micronaut.context.annotation.Type
  3. import java.lang.annotation.Documented
  4. import java.lang.annotation.Retention
  5. import java.lang.annotation.RetentionPolicy.RUNTIME
  6. @Documented
  7. @Retention(RUNTIME) (1)
  8. @Target(AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) (2)
  9. @Around (3)
  10. @Type(NotNullInterceptor::class) (4)
  11. annotation class NotNull
1The retention policy of the annotation should be RUNTIME
2Generally you want to be able to apply advice at the class or method level so the target types are TYPE and METHOD
3The Around annotation is added to tell Micronaut that the annotation is Around advice
4The @Type annotation is used to configure which type implements the advice (in this case the previously defined NotNullInterceptor)

With the interceptor and annotation implemented you can then simply apply the annotation to the target classes:

Around Advice Usage Example

  1. @Singleton
  2. public class NotNullExample {
  3. @NotNull
  4. void doWork(String taskName) {
  5. System.out.println("Doing job: " + taskName);
  6. }
  7. }

Around Advice Usage Example

  1. @Singleton
  2. class NotNullExample {
  3. @NotNull
  4. void doWork(String taskName) {
  5. println("Doing job: " + taskName)
  6. }
  7. }

Around Advice Usage Example

  1. @Singleton
  2. open class NotNullExample {
  3. @NotNull
  4. open fun doWork(taskName: String?) {
  5. println("Doing job: $taskName")
  6. }
  7. }

Whenever the type NotNullExample is injected into any class, a compile time generated proxy will instead be injected that decorates the appropriate method calls with the @NotNull advice defined earlier. You can verify that the advice works by writing a test. The following test uses a JUnit ExpectedException rule to verify the appropriate exception is thrown when an argument is null:

Around Advice Test

  1. @Rule
  2. public ExpectedException thrown = ExpectedException.none();
  3. @Test
  4. public void testNotNull() {
  5. try (ApplicationContext applicationContext = ApplicationContext.run()) {
  6. NotNullExample exampleBean = applicationContext.getBean(NotNullExample.class);
  7. thrown.expect(IllegalArgumentException.class);
  8. thrown.expectMessage("Null parameter [taskName] not allowed");
  9. exampleBean.doWork(null);
  10. }
  11. }

Around Advice Test

  1. void "test not null"() {
  2. when:
  3. ApplicationContext applicationContext = ApplicationContext.run()
  4. NotNullExample exampleBean = applicationContext.getBean(NotNullExample.class)
  5. exampleBean.doWork(null)
  6. then:
  7. IllegalArgumentException ex = thrown()
  8. ex.message == 'Null parameter [taskName] not allowed'
  9. cleanup:
  10. applicationContext.close()
  11. }

Around Advice Test

  1. @Test
  2. fun testNotNull() {
  3. val applicationContext = ApplicationContext.run()
  4. val exampleBean = applicationContext.getBean(NotNullExample::class.java)
  5. val exception = shouldThrow<IllegalArgumentException> {
  6. exampleBean.doWork(null)
  7. }
  8. exception.message shouldBe "Null parameter [taskName] not allowed"
  9. applicationContext.close()
  10. }
Since Micronaut injection is done at compile time, generally the advice should be packaged in a dependent JAR file that is on the classpath when the above test is compiled and should not be in the same codebase since you don’t want the test to be compiled before the advice itself is compiled.