2

Java: 11, Spring-boot: 2.5.3

I am trying to implement some annotation-based abstraction on event processing. I have created annotation @EventConsumer and I get all methods annotated with it after context is up. The problem is when I invoke such method, it does not respect any spring proxies/aspects, i.e. @Transactional, @Cacheable, @NewSpan, etc...

Here is code how I get those methods:

    private void processBean(final String beanName, final Object bean, final Class<?> targetType) {
        if (!this.nonAnnotatedClasses.contains(targetType)) {

            final MethodIntrospector.MetadataLookup<EventConsumer> metadataLookup =
                    method -> AnnotatedElementUtils.findMergedAnnotation(method, EventConsumer.class);

            Map<Method, EventConsumer> annotatedMethods = MethodIntrospector.selectMethods(targetType, metadataLookup);

            if (MapUtils.isEmpty(annotatedMethods)) {
                this.nonAnnotatedClasses.add(targetType);
                logger.debug("No @EventConsumer annotations found on bean class: {}", targetType.getName());
            } else {
                for (Method method : annotatedMethods.keySet()) {
                    Method methodToUse = AopUtils.selectInvocableMethod(method, targetType);
                    final EventConsumer eventConsumerAnnotation = annotatedMethods.get(methodToUse);

                    getProxiedMethod(bean, methodToUse).ifPresent(proxiedMethod -> {
                        MethodInvokingEventConsumer consumer = methodInvokingEventConsumerFactory.create(bean, proxiedMethod, eventConsumerAnnotation);
                        eventConsumerRegistry.register(consumer);
                    });
                }
                logger.info("{} @EventConsumer methods processed on bean '{}': {}", annotatedMethods.size(), beanName, annotatedMethods);
            }
        }
    }

    private Optional<Method> getProxiedMethod(Object bean, Method methodToUse) {
        try {
            return Optional.of(bean.getClass().getDeclaredMethod(methodToUse.getName(), methodToUse.getParameterTypes()));
        } catch (NoSuchMethodException e) {
            logger.error("Class {} do not contains method {}, which was marked as @EventConsumer. This consumer will not be active!", methodToUse.getDeclaringClass(), methodToUse.getName(), e);
            return Optional.empty();
        }
    }

And invocation part:

    public EventConsumptionFailures consume(Object obj) {
        ReflectionUtils.makeAccessible(methodToUse);
        Object[] args = new Object[]{obj};
        Span span = getSpan(obj);
        try (Tracer.SpanInScope ignore = tracing.tracer().withSpanInScope(span.name(name).start())) {
            return (EventConsumptionFailures) methodToUse.invoke(bean, args);
        } catch (IllegalArgumentException | IllegalAccessException ex) {
            span.error(ex);
            throw new IllegalStateException(ex.getMessage(), ex);
        } catch (InvocationTargetException ex) {
            Throwable targetException = ex.getTargetException();
            span.error(targetException);
            if (targetException instanceof RuntimeException) {
                throw (RuntimeException) targetException;
            } else {
                throw new UndeclaredThrowableException(targetException, targetException.getMessage());
            }
        } finally {
            span.finish();
        }
    }

the question is how to get method reference with all proxies and aspects? so i can benefit from annotation configuration.


edit I want to achieve smth similar to org.springframework.kafka.config.MethodKafkaListenerEndpoint, so:

  1. Scan all beans
  2. Look for methods annotated with @EventConsumer
  3. Add them to registry
  4. Invoke the Method but with all aspects, e.g. @Transactional, @Cacheable

so far I have points 1-3, but when I invoke method, it does not create transaction.


edit

other words:

@Service 
class SomeService { @Transactional void methodA(){}; }

...
@Autowired SomeService someService


someService.methodA(); -> this will create transaction
SomeService.class.getDeclaredMethod("methodA").invoke(someService); -> this will not create transaction

question: how to make 2nd approach to respect aspects and all proxies?

--- edit 4 I dont know, when exactly it stopped working, but the method getProxiedMethod was already fixing this issue in past. In the mean time we had many library (including spring-boot) upgrades as well as java upgrade (8 -> 11)

here is list of related questions, but all of them do not work for me:

Piotr Joński
  • 33
  • 1
  • 8
  • By using proper aspects instead of trying to write your own. Write an `@Aspect` and spring will take care of the rest. – M. Deinum Aug 16 '21 at 08:52
  • @M.Deinum, thanks for replay, but I dont want to write aspect that will do smth additionally around the method. I want to invoke method with all aspects around it. – Piotr Joński Aug 16 '21 at 10:01
  • If you write a proper aspect that will work out of the box and is less code. What you have done is basically write a new Aspect Oriented way (not really) to invoke methods. THis is what Aspect Oriented programming already does, so instead of bolting on an homegrown solution, use a proven one which does what you want out-of-the-box. – M. Deinum Aug 16 '21 at 10:04
  • Still, I think you are proposing to write Aspect that will do smth when method is invoked. And in my case I want to invoke method. Not do smth when it is invoked. I init the method invocation, I dont plug-in. Aspects, AFAIK, are designed to plug-in into method invocations. Not to invoke methods itself. My point is to be able to use @Transactional on such methods. I cannot imagine how writing `@Aspect` around `@EventConsumer` method can make `@Transactional` working for it... (I want to make sure that method.invoke(bean,args) will invoke TransactionalAspect) – Piotr Joński Aug 16 '21 at 10:26
  • What do you think your `consume` method is. That is nothing more than an advice (part of the aspect) and your processing code in the `BeanPostProcessor` is the joinpoint. So basically you have written a aop mechanism. You are doing something before and after a method annotated with `@EventConsumer`. This is basically an around advice. – M. Deinum Aug 16 '21 at 11:09
  • You are doing something before and after -> yes, we do some tracing, but this is not the main goal. The goal is to ivoke method annotated with @EventCosnumer with ARG1. Aspect can decorate the call, but can it invoke method itself? If so, how it knows ARG1? – Piotr Joński Aug 16 '21 at 11:35
  • You are nowhere mentioning arg1. The only thing your code does is decorating the method invocation. Your current description and code don't do anything else with arg1. So either still your question is incomplete, your code is incomplete or you don't understand your code. Nonetheless I give up. – M. Deinum Aug 16 '21 at 11:43
  • `consume(Object obj)` -> `obj` ==== `arg1`. As I said, the decorating part is a MINOR stuff. The main goal of this code is to INVOKE method WITH ALL ASPECTS/PROXIES AROUND. We do smth with `arg1` -- we pass it to `consumer` – Piotr Joński Aug 16 '21 at 11:50
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/236048/discussion-between-piotr-jonski-and-m-deinum). – Piotr Joński Aug 16 '21 at 12:33

0 Answers0