Skip to content

Commit 4583afe

Browse files
authored
GH-8611: Extract MessagingAnnotationBeanPostProcessor (#8661)
Fixes #8611 According to the latest Spring Framework requirements related to AOT the `BeanDefinitionRegistryPostProcessor` must not do anything but only bean registrations * Extract a `MessagingAnnotationBeanPostProcessor` component with a `BeanPostProcessor` logic from the `MessagingAnnotationPostProcessor` and leave the last one as just a `BeanDefinitionRegistryPostProcessor` impl * Fix tests according to a new logic
1 parent 13980af commit 4583afe

File tree

7 files changed

+321
-286
lines changed

7 files changed

+321
-286
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.config;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.Method;
21+
import java.util.ArrayList;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Set;
26+
import java.util.concurrent.ConcurrentHashMap;
27+
28+
import org.springframework.aop.support.AopUtils;
29+
import org.springframework.beans.BeansException;
30+
import org.springframework.beans.factory.BeanFactory;
31+
import org.springframework.beans.factory.BeanFactoryAware;
32+
import org.springframework.beans.factory.SmartInitializingSingleton;
33+
import org.springframework.beans.factory.config.BeanPostProcessor;
34+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
35+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
36+
import org.springframework.beans.factory.support.RootBeanDefinition;
37+
import org.springframework.context.annotation.Bean;
38+
import org.springframework.core.annotation.AnnotationUtils;
39+
import org.springframework.core.annotation.MergedAnnotation;
40+
import org.springframework.core.annotation.MergedAnnotations;
41+
import org.springframework.integration.annotation.EndpointId;
42+
import org.springframework.integration.annotation.Role;
43+
import org.springframework.integration.config.annotation.MethodAnnotationPostProcessor;
44+
import org.springframework.integration.endpoint.AbstractEndpoint;
45+
import org.springframework.integration.util.MessagingAnnotationUtils;
46+
import org.springframework.util.Assert;
47+
import org.springframework.util.ClassUtils;
48+
import org.springframework.util.ReflectionUtils;
49+
import org.springframework.util.StringUtils;
50+
51+
/**
52+
* An infrastructure {@link BeanPostProcessor} implementation that processes method-level
53+
* messaging annotations such as @Transformer, @Splitter, @Router, and @Filter.
54+
*
55+
* @author Artem Bilan
56+
*
57+
* @since 6.2
58+
*/
59+
public class MessagingAnnotationBeanPostProcessor
60+
implements BeanPostProcessor, BeanFactoryAware, SmartInitializingSingleton {
61+
62+
private final Set<Class<?>> noAnnotationsCache = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
63+
64+
private final Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> postProcessors;
65+
66+
private final List<Runnable> methodsToPostProcessAfterContextInitialization = new ArrayList<>();
67+
68+
private final BeanDefinitionRegistry registry;
69+
70+
private ConfigurableListableBeanFactory beanFactory;
71+
72+
private volatile boolean initialized;
73+
74+
public MessagingAnnotationBeanPostProcessor(BeanDefinitionRegistry registry,
75+
Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> postProcessors) {
76+
77+
this.registry = registry;
78+
this.postProcessors = postProcessors;
79+
}
80+
81+
@Override
82+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
83+
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
84+
}
85+
86+
@Override
87+
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
88+
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
89+
90+
Class<?> beanClass = AopUtils.getTargetClass(bean);
91+
92+
// the set will hold records of prior class scans and indicate if no messaging annotations were found
93+
if (this.noAnnotationsCache.contains(beanClass)) {
94+
return bean;
95+
}
96+
97+
ReflectionUtils.doWithMethods(beanClass,
98+
method -> doWithMethod(method, bean, beanName, beanClass),
99+
ReflectionUtils.USER_DECLARED_METHODS);
100+
101+
return bean;
102+
}
103+
104+
private void doWithMethod(Method method, Object bean, String beanName, Class<?> beanClass) {
105+
MergedAnnotations mergedAnnotations = MergedAnnotations.from(method);
106+
107+
boolean noMessagingAnnotations = true;
108+
109+
// See MessagingAnnotationPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)
110+
if (!mergedAnnotations.isPresent(Bean.class)) {
111+
List<MessagingMetaAnnotation> messagingAnnotations =
112+
obtainMessagingAnnotations(this.postProcessors.keySet(), mergedAnnotations,
113+
method.toGenericString());
114+
115+
for (MessagingMetaAnnotation messagingAnnotation : messagingAnnotations) {
116+
noMessagingAnnotations = false;
117+
Class<? extends Annotation> annotationType = messagingAnnotation.annotationType;
118+
List<Annotation> annotationChain =
119+
MessagingAnnotationUtils.getAnnotationChain(messagingAnnotation.annotation, annotationType);
120+
processAnnotationTypeOnMethod(bean, beanName, method, annotationType, annotationChain);
121+
}
122+
}
123+
if (noMessagingAnnotations) {
124+
this.noAnnotationsCache.add(beanClass);
125+
}
126+
}
127+
128+
private void processAnnotationTypeOnMethod(Object bean, String beanName, Method method,
129+
Class<? extends Annotation> annotationType, List<Annotation> annotations) {
130+
131+
MethodAnnotationPostProcessor<?> postProcessor = this.postProcessors.get(annotationType);
132+
if (postProcessor != null && postProcessor.supportsPojoMethod()
133+
&& postProcessor.shouldCreateEndpoint(method, annotations)) {
134+
135+
Method targetMethod = method;
136+
if (AopUtils.isJdkDynamicProxy(bean)) {
137+
try {
138+
targetMethod = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
139+
}
140+
catch (NoSuchMethodException e) {
141+
throw new IllegalArgumentException("Service methods must be extracted to the service "
142+
+ "interface for JdkDynamicProxy. The affected bean is: '" + beanName + "' "
143+
+ "and its method: '" + method + "'", e);
144+
}
145+
}
146+
147+
if (this.initialized) {
148+
postProcessMethodAndRegisterEndpointIfAny(bean, beanName, method, annotationType, annotations,
149+
postProcessor, targetMethod);
150+
}
151+
else {
152+
Method methodToPostProcess = targetMethod;
153+
this.methodsToPostProcessAfterContextInitialization.add(() ->
154+
postProcessMethodAndRegisterEndpointIfAny(bean, beanName, method, annotationType, annotations,
155+
postProcessor, methodToPostProcess));
156+
}
157+
}
158+
}
159+
160+
@SuppressWarnings("unchecked")
161+
private void postProcessMethodAndRegisterEndpointIfAny(Object bean, String beanName, Method method,
162+
Class<? extends Annotation> annotationType, List<Annotation> annotations,
163+
MethodAnnotationPostProcessor<?> postProcessor, Method targetMethod) {
164+
165+
Object result = postProcessor.postProcess(bean, beanName, targetMethod, annotations);
166+
if (result instanceof AbstractEndpoint endpoint) {
167+
String autoStartup = MessagingAnnotationUtils.resolveAttribute(annotations, "autoStartup", String.class);
168+
if (StringUtils.hasText(autoStartup)) {
169+
autoStartup = this.beanFactory.resolveEmbeddedValue(autoStartup);
170+
if (StringUtils.hasText(autoStartup)) {
171+
endpoint.setAutoStartup(Boolean.parseBoolean(autoStartup));
172+
}
173+
}
174+
175+
String phase = MessagingAnnotationUtils.resolveAttribute(annotations, "phase", String.class);
176+
if (StringUtils.hasText(phase)) {
177+
phase = this.beanFactory.resolveEmbeddedValue(phase);
178+
if (StringUtils.hasText(phase)) {
179+
endpoint.setPhase(Integer.parseInt(phase));
180+
}
181+
}
182+
183+
Role role = AnnotationUtils.findAnnotation(method, Role.class);
184+
if (role != null) {
185+
endpoint.setRole(role.value());
186+
}
187+
188+
String endpointBeanName = generateBeanName(beanName, method, annotationType);
189+
endpoint.setBeanName(endpointBeanName);
190+
this.registry.registerBeanDefinition(endpointBeanName,
191+
new RootBeanDefinition((Class<AbstractEndpoint>) endpoint.getClass(), () -> endpoint));
192+
this.beanFactory.getBean(endpointBeanName);
193+
}
194+
}
195+
196+
197+
protected String generateBeanName(String originalBeanName, Method method,
198+
Class<? extends Annotation> annotationType) {
199+
200+
String name = MessagingAnnotationUtils.endpointIdValue(method);
201+
if (!StringUtils.hasText(name)) {
202+
String baseName = originalBeanName + "." + method.getName() + "."
203+
+ ClassUtils.getShortNameAsProperty(annotationType);
204+
name = baseName;
205+
int count = 1;
206+
while (this.beanFactory.containsBean(name)) {
207+
name = baseName + "#" + (++count);
208+
}
209+
}
210+
return name;
211+
}
212+
213+
@Override
214+
public void afterSingletonsInstantiated() {
215+
this.initialized = true;
216+
this.methodsToPostProcessAfterContextInitialization.forEach(Runnable::run);
217+
this.methodsToPostProcessAfterContextInitialization.clear();
218+
}
219+
220+
protected static List<MessagingMetaAnnotation> obtainMessagingAnnotations(
221+
Set<Class<? extends Annotation>> postProcessors, MergedAnnotations annotations, String identified) {
222+
223+
List<MessagingMetaAnnotation> messagingAnnotations = new ArrayList<>();
224+
225+
for (Class<? extends Annotation> annotationType : postProcessors) {
226+
annotations.stream()
227+
.filter((ann) -> ann.getType().equals(annotationType))
228+
.map(MergedAnnotation::getRoot)
229+
.map(MergedAnnotation::synthesize)
230+
.map((ann) -> new MessagingMetaAnnotation(ann, annotationType))
231+
.forEach(messagingAnnotations::add);
232+
}
233+
234+
if (annotations.get(EndpointId.class, (ann) -> ann.hasNonDefaultValue("value")).isPresent()
235+
&& messagingAnnotations.size() > 1) {
236+
237+
throw new IllegalStateException("@EndpointId on " + identified
238+
+ " can only have one EIP annotation, found: " + messagingAnnotations.size());
239+
}
240+
241+
return messagingAnnotations;
242+
}
243+
244+
public record MessagingMetaAnnotation(Annotation annotation, Class<? extends Annotation> annotationType) {
245+
246+
}
247+
248+
}

0 commit comments

Comments
 (0)