I am trying to figure out how to define Pointcuts and how to handle multiple annotations using spring AOP.
I have the following custom annotations:
@RequiresNonBlank
@Retention (RetentionPolicy.RUNTIME)
@Target (value = {ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Documented
public @interface RequiresNonBlank {
String value();
Class<? extends StuffException> throwIfInvalid();
}
and
@RequiresNonBlankDummy
@Retention (RetentionPolicy.RUNTIME)
@Target (value = {ElementType.METHOD})
@Documented
@RequiresNonBlank (
value = "T(com.stuff.exceptions.annotation.TestDummyValueHolder).value",
throwIfInvalid = TestDummyStuffException.class
)
@interface RequiresNonBlankDummy {
}
and I have the following dummy controller:
TestDummyController
@Component
public class TestDummyController {
@RequiresNonBlank (
value = "T(com.stuff.exceptions.annotation.TestDummyValueHolder).value",
throwIfInvalid = TestDummyStuffException.class
)
public boolean methodWithRequiresNonBlankAnnotation() {
return true;
}
@RequiresNonBlankDummy
public boolean methodWithRequiresNonBlankDummyAnnotation() {
return true;
}
@RequiresNonBlankDummy
@RequiresNonBlank (
value = "T(com.stuff.exceptions.annotation.TestDummyValueHolder).anotherValue",
throwIfInvalid = TestDummyStuffException.class
)
public boolean methodWithMultipleRequiresNonBlankAnnotation() {
return true;
}
}
My TestDummyValueHolder is a class, which only contains two Strings (value and anotherValue) and their corresponding getters and setters.
I want to define one or multiple pointcuts (@Pointcut), which would handle one or multiple / stacked @RequiresNonBlank on a method ("derived" annotations such as @RequiresNonBlankDummy should be taken into account).
My aspect handler currently looks like this:
RequiresNonBlankAspect
@Component
@Aspect
@Slf4j
public class RequiresNonBlankAspect {
private static final String REQUIRES_NON_BLANK_FQPN =
"com.stuff.exceptions.annotation.RequiresNonBlank";
@Before("execution(@" + REQUIRES_NON_BLANK_FQPN + " * *(..)) && @annotation(annotation)")
public void evaluatePreconditionItself(JoinPoint joinPoint, RequiresNonBlank annotation) {
evaluatePrecondition(joinPoint, annotation);
}
@Before("execution(@(@" + REQUIRES_NON_BLANK_FQPN + " *) * *(..)) && @annotation(annotation)")
public void evaluatePreconditionOnAnnotation(JoinPoint joinPoint, RequiresNonBlank annotation) {
evaluatePrecondition(joinPoint, annotation);
}
private void evaluatePrecondition(JoinPoint joinPoint, RequiresNonBlank annotation) {
try {
Objects.requireNonNull(annotation);
} catch (NullPointerException e) {
log.error("No annotation found!", e);
}
ExpressionParser elParser = new SpelExpressionParser();
Expression expression = elParser.parseExpression(annotation.value());
String expressionToEvaluate = (String) expression.getValue(joinPoint.getArgs());
log.info("value to check: {}", expressionToEvaluate);
if (StringUtils.isEmpty(expressionToEvaluate)) {
try {
throw annotation.throwIfInvalid().getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException
| NoSuchMethodException e) {
log.error("Could not throw the exception configured!", e);
}
}
}
}
However: annotationOnAnnotation(...) does not work. annotationItself however does.
I have the following Test:
RequiresNonBlankTest
@SpringBootApplication
@ActiveProfiles (profiles = "test")
@RunWith (SpringRunner.class)
public class RequiresNonBlankTest {
@Autowired
private TestDummyController controller;
@Test (expected = TestDummyStuffException.class)
public void testRequiresNonBlank_valueIsNull() {
TestDummyValueHolder.setValue(null);
controller.methodWithRequiresNonBlankAnnotation();
}
@Test
public void testRequiresNonBlank_valueIsNotNull() {
TestDummyValueHolder.setValue("value: non-null");
assertThat(controller.methodWithRequiresNonBlankAnnotation(), equalTo(true));
}
@Test (expected = TestDummyStuffException.class)
public void testRequiresNonBlankDummy_valueIsNull() {
TestDummyValueHolder.setValue(null);
controller.methodWithRequiresNonBlankDummyAnnotation();
}
@Test
public void testRequiresNonBlankDummy_valueIsNotNull() {
TestDummyValueHolder.setValue("value: non-null");
assertThat(controller.methodWithRequiresNonBlankDummyAnnotation(), equalTo(true));
}
}
However: test testRequiresNonBlankDummy_valueIsNull() fails.
I also would like to know how to not only react to annotations on annotations on a method (see @RequiresNonBlankDummy), but also to what I have in TestDummyController#methodWithMultipleRequiresNonBlankAnnotation (stacked / multiple annotations). Is this possible and if so, how?
I am using Spring Boot and Spring AOP. I have tried using AnnotationUtils and AnnotationElementUtils, but I at least from what I can tell it did not help. Please help me or give me a hint on how to solve this.
Edit (15.08.2021):
TestDummyValueHolder
public class TestDummyValueHolder {
private static String value;
private static String anotherValue;
public static String getValue() {
return TestDummyValueHolder.value;
}
public static void setValue(String value) {
TestDummyValueHolder.value = value;
}
public static String getAnotherValue() {
return TestDummyValueHolder.anotherValue;
}
public static void setAnotherValue(String anotherValue) {
TestDummyValueHolder.anotherValue = anotherValue;
}
}
StuffException is pretty generic. In fact you could replace it with any Exception, which has a no-args constructor. I have also updated the aspect handler (RequiresNonBlankAspect) to be like what @kriegaex mentioned.