diff --git a/spring-modulith-events/spring-modulith-events-core/pom.xml b/spring-modulith-events/spring-modulith-events-core/pom.xml index 93cd6eca..7886cd9a 100644 --- a/spring-modulith-events/spring-modulith-events-core/pom.xml +++ b/spring-modulith-events/spring-modulith-events-core/pom.xml @@ -32,6 +32,14 @@ org.springframework spring-aop + + + + + org.springframework + spring-test + test + diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationConfiguration.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationConfiguration.java index 62b5d532..0cc09d51 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationConfiguration.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationConfiguration.java @@ -16,12 +16,14 @@ package org.springframework.modulith.events.config; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; import org.springframework.modulith.events.DefaultEventPublicationRegistry; import org.springframework.modulith.events.EventPublicationRegistry; import org.springframework.modulith.events.EventPublicationRepository; -import org.springframework.modulith.events.support.CompletionRegisteringBeanPostProcessor; +import org.springframework.modulith.events.support.CompletionRegisteringAdvisor; import org.springframework.modulith.events.support.PersistentApplicationEventMulticaster; /** @@ -45,7 +47,8 @@ class EventPublicationConfiguration { } @Bean - static CompletionRegisteringBeanPostProcessor bpp(ObjectFactory store) { - return new CompletionRegisteringBeanPostProcessor(store::getObject); + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + static CompletionRegisteringAdvisor completionRegisteringAdvisor(ObjectFactory registry) { + return new CompletionRegisteringAdvisor(registry::getObject); } } diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/CompletionRegisteringAdvisor.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/CompletionRegisteringAdvisor.java new file mode 100644 index 00000000..df7577b7 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/CompletionRegisteringAdvisor.java @@ -0,0 +1,204 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.modulith.events.support; + +import java.lang.reflect.Method; +import java.util.function.Supplier; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.Advisor; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; +import org.springframework.aop.support.StaticMethodMatcher; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.lang.NonNull; +import org.springframework.modulith.events.EventPublicationRegistry; +import org.springframework.modulith.events.PublicationTargetIdentifier; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalApplicationListenerMethodAdapter; +import org.springframework.transaction.event.TransactionalEventListener; +import org.springframework.util.Assert; +import org.springframework.util.ConcurrentLruCache; + +/** + * An {@link Advisor} to decorate {@link TransactionalEventListener} annotated methods to mark the previously registered + * event publications as completed on successful method execution. + * + * @author Oliver Drotbohm + */ +public class CompletionRegisteringAdvisor extends AbstractPointcutAdvisor { + + private static final long serialVersionUID = 5649563426118669238L; + + private final Pointcut pointcut; + private final Advice advice; + + /** + * Creates a new {@link CompletionRegisteringAdvisor} for the given {@link EventPublicationRegistry}. + * + * @param registry must not be {@literal null}. + */ + public CompletionRegisteringAdvisor(Supplier registry) { + + Assert.notNull(registry, "EventPublicationRegistry must not be null!"); + + this.pointcut = new AnnotationMatchingPointcut(null, TransactionalEventListener.class, true) { + + /* + * (non-Javadoc) + * @see org.springframework.aop.support.annotation.AnnotationMatchingPointcut#getMethodMatcher() + */ + @Override + public MethodMatcher getMethodMatcher() { + return new CommitListenerMethodMatcher(super.getMethodMatcher()); + } + }; + + this.advice = new CompletionRegisteringMethodInterceptor(registry); + } + + /* + * (non-Javadoc) + * @see org.springframework.aop.PointcutAdvisor#getPointcut() + */ + public Pointcut getPointcut() { + return pointcut; + } + + /* + * (non-Javadoc) + * @see org.springframework.aop.Advisor#getAdvice() + */ + @Override + public Advice getAdvice() { + return advice; + } + + /** + * An adapter for a delegating {@link MethodMatcher} to verify the + * + * @author Oliver Drotbohm + */ + private static class CommitListenerMethodMatcher extends StaticMethodMatcher { + + private final MethodMatcher delegate; + + /** + * Creates a new {@link CommitListenerMethodMatcher} with the given delegate {@link MethodMatcher}. + * + * @param delegate must not be {@literal null}. + */ + public CommitListenerMethodMatcher(MethodMatcher delegate) { + this.delegate = delegate; + } + + /* + * (non-Javadoc) + * @see org.springframework.aop.MethodMatcher#matches(java.lang.reflect.Method, java.lang.Class) + */ + @Override + public boolean matches(Method method, Class targetClass) { + + if (!delegate.matches(method, targetClass)) { + return false; + } + + var annotation = AnnotatedElementUtils.findMergedAnnotation(method, TransactionalEventListener.class); + + return annotation != null && annotation.phase().equals(TransactionPhase.AFTER_COMMIT); + } + } + + /** + * {@link MethodInterceptor} to trigger the completion of an event publication after a transaction event listener + * method has been completed successfully. + * + * @author Oliver Drotbohm + */ + static class CompletionRegisteringMethodInterceptor implements MethodInterceptor, Ordered { + + private static final Logger LOG = LoggerFactory.getLogger(CompletionRegisteringMethodInterceptor.class); + + private static final ConcurrentLruCache ADAPTERS = new ConcurrentLruCache<>( + 100, CompletionRegisteringMethodInterceptor::createAdapter); + + private final @NonNull Supplier registry; + + /** + * Creates a new {@link CompletionRegisteringMethodInterceptor} for the given {@link EventPublicationRegistry}. + * + * @param registry must not be {@literal null}. + */ + CompletionRegisteringMethodInterceptor(Supplier registry) { + + Assert.notNull(registry, "EventPublicationRegistry must not be null!"); + + this.registry = registry; + } + + /* + * (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + + Object result = null; + var method = invocation.getMethod(); + + try { + result = invocation.proceed(); + } catch (Exception o_O) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Invocation of listener {} failed. Leaving event publication uncompleted.", method, o_O); + } else { + LOG.info("Invocation of listener {} failed with message {}. Leaving event publication uncompleted.", + method, o_O.getMessage()); + } + + return result; + } + + // Mark publication complete if the method is a transactional event listener. + String adapterId = ADAPTERS.get(method).getListenerId(); + PublicationTargetIdentifier identifier = PublicationTargetIdentifier.of(adapterId); + registry.get().markCompleted(invocation.getArguments()[0], identifier); + + return result; + } + + /* + * (non-Javadoc) + * @see org.springframework.core.Ordered#getOrder() + */ + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 10; + } + + private static TransactionalApplicationListenerMethodAdapter createAdapter(Method method) { + return new TransactionalApplicationListenerMethodAdapter(null, method.getDeclaringClass(), method); + } + } +} diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/CompletionRegisteringBeanPostProcessor.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/CompletionRegisteringBeanPostProcessor.java deleted file mode 100644 index 06d4acd1..00000000 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/CompletionRegisteringBeanPostProcessor.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2017-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.modulith.events.support; - -import java.lang.reflect.Method; -import java.util.function.Supplier; - -import org.aopalliance.aop.Advice; -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.aop.framework.Advised; -import org.springframework.aop.framework.AopProxyUtils; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.lang.NonNull; -import org.springframework.modulith.events.EventPublicationRegistry; -import org.springframework.modulith.events.PublicationTargetIdentifier; -import org.springframework.transaction.event.TransactionPhase; -import org.springframework.transaction.event.TransactionalApplicationListenerMethodAdapter; -import org.springframework.transaction.event.TransactionalEventListener; -import org.springframework.util.Assert; -import org.springframework.util.ConcurrentLruCache; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.ReflectionUtils.MethodCallback; - -/** - * {@link BeanPostProcessor} that will add a {@link CompletionRegisteringMethodInterceptor} to the bean in case it - * carries a {@link TransactionalEventListener} annotation so that the successful invocation of those methods mark the - * event publication to those listeners as completed. - * - * @author Oliver Drotbohm - */ -public class CompletionRegisteringBeanPostProcessor implements BeanPostProcessor { - - private final Supplier registry; - - /** - * Creates a new {@link CompletionRegisteringBeanPostProcessor} for the given {@link EventPublicationRegistry}. - * - * @param registry must not be {@literal null}. - */ - public CompletionRegisteringBeanPostProcessor(Supplier registry) { - - Assert.notNull(registry, "EventPublicationRegistry must not be null!"); - - this.registry = registry; - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String) - */ - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - - ProxyCreatingMethodCallback callback = new ProxyCreatingMethodCallback(registry, beanName, bean); - - ReflectionUtils.doWithMethods(AopProxyUtils.ultimateTargetClass(bean), callback); - - return callback.methodFound ? callback.bean : bean; - - } - - /** - * Method callback to find a {@link TransactionalEventListener} method and creating a proxy including an - * {@link CompletionRegisteringBeanPostProcessor} for it or adding the latter to the already existing advisor chain. - * - * @author Oliver Drotbohm - */ - private static class ProxyCreatingMethodCallback implements MethodCallback { - - private final Supplier registry; - private final String beanName; - private Object bean; - private boolean methodFound; - - /** - * Creates a new {@link ProxyCreatingMethodCallback} for the given {@link EventPublicationRegistry}, bean name, bean - * and whether a completing method has been found. - * - * @param registry must not be {@literal null}. - * @param beanName must not be {@literal null} or empty. - * @param bean must not be {@literal null}. - */ - ProxyCreatingMethodCallback(Supplier registry, String beanName, Object bean) { - - Assert.notNull(registry, "EventPublicationRegistry must not be null!"); - Assert.hasText(beanName, "Bean name must not be null or empty!"); - Assert.notNull(bean, "Bean must not be null!"); - - this.registry = registry; - this.beanName = beanName; - this.bean = bean; - this.methodFound = false; - } - - /* - * (non-Javadoc) - * @see org.springframework.util.ReflectionUtils.MethodCallback#doWith(java.lang.reflect.Method) - */ - @Override - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - - if (methodFound || !CompletionRegisteringMethodInterceptor.isCompletingMethod(method)) { - return; - } - - this.methodFound = true; - this.bean = createCompletionRegisteringProxy(bean, - new CompletionRegisteringMethodInterceptor(registry, beanName)); - } - - private static Object createCompletionRegisteringProxy(Object bean, Advice interceptor) { - - if (bean instanceof Advised) { - - Advised advised = (Advised) bean; - advised.addAdvice(advised.getAdvisors().length, interceptor); - - return bean; - } - - ProxyFactory factory = new ProxyFactory(bean); - factory.setProxyTargetClass(true); - factory.addAdvice(interceptor); - - return factory.getProxy(); - } - } - - /** - * {@link MethodInterceptor} to trigger the completion of an event publication after a transaction event listener - * method has been completed successfully. - * - * @author Oliver Drotbohm - */ - private static class CompletionRegisteringMethodInterceptor implements MethodInterceptor, Ordered { - - private static final Logger LOG = LoggerFactory.getLogger(CompletionRegisteringMethodInterceptor.class); - private static final ConcurrentLruCache COMPLETING_METHOD = new ConcurrentLruCache<>(100, - CompletionRegisteringMethodInterceptor::calculateIsCompletingMethod); - private static final ConcurrentLruCache ADAPTERS = new ConcurrentLruCache<>( - 100, CompletionRegisteringMethodInterceptor::createAdapter); - - private final @NonNull Supplier registry; - private final @NonNull String beanName; - - /** - * @param registry - * @param beanName - */ - CompletionRegisteringMethodInterceptor(Supplier registry, String beanName) { - - Assert.notNull(registry, "EventPublicationRegistry must not be null!"); - Assert.hasText(beanName, "Bean name must not be null or empty!"); - - this.registry = registry; - this.beanName = beanName; - } - - /* - * (non-Javadoc) - * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) - */ - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - - Object result = null; - Method method = invocation.getMethod(); - - try { - result = invocation.proceed(); - } catch (Exception o_O) { - - if (!isCompletingMethod(method)) { - throw o_O; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Invocation of listener {} failed. Leaving event publication uncompleted.", method, o_O); - } else { - LOG.info("Invocation of listener {} failed with message {}. Leaving event publication uncompleted.", - method, o_O.getMessage()); - } - - return result; - } - - if (!isCompletingMethod(method)) { - return result; - } - - // Mark publication complete if the method is a transactional event listener. - String adapterId = ADAPTERS.get(new CacheKey(beanName, method)).getListenerId(); - PublicationTargetIdentifier identifier = PublicationTargetIdentifier.of(adapterId); - registry.get().markCompleted(invocation.getArguments()[0], identifier); - - return result; - } - - /* - * (non-Javadoc) - * @see org.springframework.core.Ordered#getOrder() - */ - @Override - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE - 10; - } - - /** - * Returns whether the given method is one that requires publication completion. - * - * @param method must not be {@literal null}. - * @return - */ - static boolean isCompletingMethod(Method method) { - - Assert.notNull(method, "Method must not be null!"); - - return COMPLETING_METHOD.get(method); - } - - private static boolean calculateIsCompletingMethod(Method method) { - - TransactionalEventListener annotation = AnnotatedElementUtils.getMergedAnnotation(method, - TransactionalEventListener.class); - - return annotation == null ? false : annotation.phase().equals(TransactionPhase.AFTER_COMMIT); - } - - private static TransactionalApplicationListenerMethodAdapter createAdapter(CacheKey key) { - return new TransactionalApplicationListenerMethodAdapter(key.beanName, key.method.getDeclaringClass(), - key.method); - } - - } - - static record CacheKey(String beanName, Method method) {} -} diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java index 1a3dbbdf..f0a4f122 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java @@ -49,7 +49,7 @@ import org.springframework.util.Assert; * for incomplete publications and * * @author Oliver Drotbohm - * @see CompletionRegisteringBeanPostProcessor + * @see CompletionRegisteringAdvisor */ public class PersistentApplicationEventMulticaster extends AbstractApplicationEventMulticaster implements SmartInitializingSingleton { diff --git a/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/CompletionRegisteringAdvisorIntegrationTests.java b/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/CompletionRegisteringAdvisorIntegrationTests.java new file mode 100644 index 00000000..7c1674fb --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/CompletionRegisteringAdvisorIntegrationTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.modulith.events.support; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.aop.Advisor; +import org.springframework.aop.framework.Advised; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.modulith.events.EventPublicationRegistry; +import org.springframework.modulith.events.support.CompletionRegisteringAdvisor.CompletionRegisteringMethodInterceptor; +import org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +/** + * @author Oliver Drotbohm + */ +@ExtendWith(SpringExtension.class) +class CompletionRegisteringAdvisorIntegrationTests { + + @Autowired SampleListener listener; + + @EnableAsync + @EnableTransactionManagement + @Configuration + static class TestConfiguration { + + @Bean + SampleListener listener() { + return new SampleListener(); + } + + @Bean + PlatformTransactionManager transactionManager() { + return mock(PlatformTransactionManager.class); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + static CompletionRegisteringAdvisor completionRegisteringAdvisor() { + + var publicationRegistry = mock(EventPublicationRegistry.class); + + return new CompletionRegisteringAdvisor(() -> publicationRegistry); + } + } + + static class SampleListener { + + @Async + @Transactional + @TransactionalEventListener + void on(Object event) {} + } + + @Test // #118 + void addsCompletionRegisteringInterceptor() throws Exception { + + assertThat(listener).isInstanceOfSatisfying(Advised.class, it -> { + + assertThat(it.getAdvisors()) + .extracting(Advisor::getAdvice) + .> extracting(Object::getClass) + .startsWith( + AnnotationAsyncExecutionInterceptor.class, + CompletionRegisteringMethodInterceptor.class, + TransactionInterceptor.class); + }); + } +} diff --git a/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/CompletionRegisteringBeanPostProcessorUnitTests.java b/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/CompletionRegisteringAdvisorUnitTests.java similarity index 84% rename from spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/CompletionRegisteringBeanPostProcessorUnitTests.java rename to spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/CompletionRegisteringAdvisorUnitTests.java index 3ca48827..2fb930f4 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/CompletionRegisteringBeanPostProcessorUnitTests.java +++ b/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/support/CompletionRegisteringAdvisorUnitTests.java @@ -23,7 +23,7 @@ import java.util.function.BiConsumer; import org.junit.jupiter.api.Test; import org.springframework.aop.framework.Advised; -import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.aop.framework.ProxyFactory; import org.springframework.context.event.EventListener; import org.springframework.modulith.events.EventPublicationRegistry; import org.springframework.transaction.event.TransactionPhase; @@ -34,20 +34,11 @@ import org.springframework.transaction.event.TransactionalEventListener; * * @author Oliver Drotbohm */ -class CompletionRegisteringBeanPostProcessorUnitTests { +class CompletionRegisteringAdvisorUnitTests { EventPublicationRegistry registry = mock(EventPublicationRegistry.class); - BeanPostProcessor processor = new CompletionRegisteringBeanPostProcessor(() -> registry); SomeEventListener bean = new SomeEventListener(); - @Test - void doesNotProxyNonTransactionalEventListenerClass() { - - NoEventListener bean = new NoEventListener(); - - assertThat(bean).isSameAs(processor.postProcessBeforeInitialization(bean, "bean")); - } - @Test void triggersCompletionForAfterCommitEventListener() throws Exception { assertCompletion(SomeEventListener::onAfterCommit); @@ -78,7 +69,7 @@ class CompletionRegisteringBeanPostProcessorUnitTests { private void assertCompletion(BiConsumer consumer, boolean expected) { - Object processed = processor.postProcessAfterInitialization(bean, "listener"); + Object processed = createProxyFor(bean); assertThat(processed).isInstanceOf(Advised.class); assertThat(processed).isInstanceOfSatisfying(SomeEventListener.class, // @@ -87,6 +78,13 @@ class CompletionRegisteringBeanPostProcessorUnitTests { verify(registry, times(expected ? 1 : 0)).markCompleted(any(), any()); } + private Object createProxyFor(Object bean) { + + ProxyFactory factory = new ProxyFactory(bean); + factory.addAdvisor(new CompletionRegisteringAdvisor(() -> registry)); + return factory.getProxy(); + } + static class SomeEventListener { @TransactionalEventListener @@ -100,6 +98,4 @@ class CompletionRegisteringBeanPostProcessorUnitTests { void nonEventListener(Object object) {} } - - static class NoEventListener {} } diff --git a/spring-modulith-events/spring-modulith-events-core/src/test/resources/logback.xml b/spring-modulith-events/spring-modulith-events-core/src/test/resources/logback.xml new file mode 100644 index 00000000..455fd805 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-core/src/test/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + +