001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jms.annotation; 018 019import java.lang.reflect.Method; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.concurrent.atomic.AtomicInteger; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030 031import org.springframework.aop.framework.AopInfrastructureBean; 032import org.springframework.aop.framework.AopProxyUtils; 033import org.springframework.aop.support.AopUtils; 034import org.springframework.beans.BeansException; 035import org.springframework.beans.factory.BeanFactory; 036import org.springframework.beans.factory.BeanFactoryAware; 037import org.springframework.beans.factory.BeanInitializationException; 038import org.springframework.beans.factory.ListableBeanFactory; 039import org.springframework.beans.factory.NoSuchBeanDefinitionException; 040import org.springframework.beans.factory.SmartInitializingSingleton; 041import org.springframework.beans.factory.config.ConfigurableBeanFactory; 042import org.springframework.beans.factory.config.EmbeddedValueResolver; 043import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; 044import org.springframework.beans.factory.support.RootBeanDefinition; 045import org.springframework.core.MethodIntrospector; 046import org.springframework.core.Ordered; 047import org.springframework.core.annotation.AnnotatedElementUtils; 048import org.springframework.core.annotation.AnnotationAwareOrderComparator; 049import org.springframework.core.annotation.AnnotationUtils; 050import org.springframework.jms.config.JmsListenerConfigUtils; 051import org.springframework.jms.config.JmsListenerContainerFactory; 052import org.springframework.jms.config.JmsListenerEndpointRegistrar; 053import org.springframework.jms.config.JmsListenerEndpointRegistry; 054import org.springframework.jms.config.MethodJmsListenerEndpoint; 055import org.springframework.lang.Nullable; 056import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; 057import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory; 058import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; 059import org.springframework.util.Assert; 060import org.springframework.util.StringUtils; 061import org.springframework.util.StringValueResolver; 062 063/** 064 * Bean post-processor that registers methods annotated with {@link JmsListener} 065 * to be invoked by a JMS message listener container created under the cover 066 * by a {@link org.springframework.jms.config.JmsListenerContainerFactory} 067 * according to the attributes of the annotation. 068 * 069 * <p>Annotated methods can use flexible arguments as defined by {@link JmsListener}. 070 * 071 * <p>This post-processor is automatically registered by Spring's 072 * {@code <jms:annotation-driven>} XML element, and also by the {@link EnableJms} 073 * annotation. 074 * 075 * <p>Autodetects any {@link JmsListenerConfigurer} instances in the container, 076 * allowing for customization of the registry to be used, the default container 077 * factory or for fine-grained control over endpoints registration. See the 078 * {@link EnableJms} javadocs for complete usage details. 079 * 080 * @author Stephane Nicoll 081 * @author Juergen Hoeller 082 * @since 4.1 083 * @see JmsListener 084 * @see EnableJms 085 * @see JmsListenerConfigurer 086 * @see JmsListenerEndpointRegistrar 087 * @see JmsListenerEndpointRegistry 088 * @see org.springframework.jms.config.JmsListenerEndpoint 089 * @see MethodJmsListenerEndpoint 090 */ 091public class JmsListenerAnnotationBeanPostProcessor 092 implements MergedBeanDefinitionPostProcessor, Ordered, BeanFactoryAware, SmartInitializingSingleton { 093 094 /** 095 * The bean name of the default {@link JmsListenerContainerFactory}. 096 */ 097 static final String DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "jmsListenerContainerFactory"; 098 099 100 protected final Log logger = LogFactory.getLog(getClass()); 101 102 @Nullable 103 private String containerFactoryBeanName = DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME; 104 105 @Nullable 106 private JmsListenerEndpointRegistry endpointRegistry; 107 108 private final MessageHandlerMethodFactoryAdapter messageHandlerMethodFactory = 109 new MessageHandlerMethodFactoryAdapter(); 110 111 @Nullable 112 private BeanFactory beanFactory; 113 114 @Nullable 115 private StringValueResolver embeddedValueResolver; 116 117 private final JmsListenerEndpointRegistrar registrar = new JmsListenerEndpointRegistrar(); 118 119 private final AtomicInteger counter = new AtomicInteger(); 120 121 private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64)); 122 123 124 @Override 125 public int getOrder() { 126 return LOWEST_PRECEDENCE; 127 } 128 129 /** 130 * Set the name of the {@link JmsListenerContainerFactory} to use by default. 131 * <p>If none is specified, "jmsListenerContainerFactory" is assumed to be defined. 132 */ 133 public void setContainerFactoryBeanName(String containerFactoryBeanName) { 134 this.containerFactoryBeanName = containerFactoryBeanName; 135 } 136 137 /** 138 * Set the {@link JmsListenerEndpointRegistry} that will hold the created 139 * endpoint and manage the lifecycle of the related listener container. 140 */ 141 public void setEndpointRegistry(JmsListenerEndpointRegistry endpointRegistry) { 142 this.endpointRegistry = endpointRegistry; 143 } 144 145 /** 146 * Set the {@link MessageHandlerMethodFactory} to use to configure the message 147 * listener responsible to serve an endpoint detected by this processor. 148 * <p>By default, {@link DefaultMessageHandlerMethodFactory} is used and it 149 * can be configured further to support additional method arguments 150 * or to customize conversion and validation support. See 151 * {@link DefaultMessageHandlerMethodFactory} Javadoc for more details. 152 */ 153 public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) { 154 this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(messageHandlerMethodFactory); 155 } 156 157 /** 158 * Making a {@link BeanFactory} available is optional; if not set, 159 * {@link JmsListenerConfigurer} beans won't get autodetected and an 160 * {@link #setEndpointRegistry endpoint registry} has to be explicitly configured. 161 */ 162 @Override 163 public void setBeanFactory(BeanFactory beanFactory) { 164 this.beanFactory = beanFactory; 165 if (beanFactory instanceof ConfigurableBeanFactory) { 166 this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory); 167 } 168 this.registrar.setBeanFactory(beanFactory); 169 } 170 171 172 @Override 173 public void afterSingletonsInstantiated() { 174 // Remove resolved singleton classes from cache 175 this.nonAnnotatedClasses.clear(); 176 177 if (this.beanFactory instanceof ListableBeanFactory) { 178 // Apply JmsListenerConfigurer beans from the BeanFactory, if any 179 Map<String, JmsListenerConfigurer> beans = 180 ((ListableBeanFactory) this.beanFactory).getBeansOfType(JmsListenerConfigurer.class); 181 List<JmsListenerConfigurer> configurers = new ArrayList<>(beans.values()); 182 AnnotationAwareOrderComparator.sort(configurers); 183 for (JmsListenerConfigurer configurer : configurers) { 184 configurer.configureJmsListeners(this.registrar); 185 } 186 } 187 188 if (this.containerFactoryBeanName != null) { 189 this.registrar.setContainerFactoryBeanName(this.containerFactoryBeanName); 190 } 191 192 if (this.registrar.getEndpointRegistry() == null) { 193 // Determine JmsListenerEndpointRegistry bean from the BeanFactory 194 if (this.endpointRegistry == null) { 195 Assert.state(this.beanFactory != null, "BeanFactory must be set to find endpoint registry by bean name"); 196 this.endpointRegistry = this.beanFactory.getBean( 197 JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, JmsListenerEndpointRegistry.class); 198 } 199 this.registrar.setEndpointRegistry(this.endpointRegistry); 200 } 201 202 203 // Set the custom handler method factory once resolved by the configurer 204 MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory(); 205 if (handlerMethodFactory != null) { 206 this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(handlerMethodFactory); 207 } 208 209 // Actually register all listeners 210 this.registrar.afterPropertiesSet(); 211 } 212 213 214 @Override 215 public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { 216 } 217 218 @Override 219 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 220 return bean; 221 } 222 223 @Override 224 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 225 if (bean instanceof AopInfrastructureBean || bean instanceof JmsListenerContainerFactory || 226 bean instanceof JmsListenerEndpointRegistry) { 227 // Ignore AOP infrastructure such as scoped proxies. 228 return bean; 229 } 230 231 Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); 232 if (!this.nonAnnotatedClasses.contains(targetClass) && 233 AnnotationUtils.isCandidateClass(targetClass, JmsListener.class)) { 234 Map<Method, Set<JmsListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, 235 (MethodIntrospector.MetadataLookup<Set<JmsListener>>) method -> { 236 Set<JmsListener> listenerMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations( 237 method, JmsListener.class, JmsListeners.class); 238 return (!listenerMethods.isEmpty() ? listenerMethods : null); 239 }); 240 if (annotatedMethods.isEmpty()) { 241 this.nonAnnotatedClasses.add(targetClass); 242 if (logger.isTraceEnabled()) { 243 logger.trace("No @JmsListener annotations found on bean type: " + targetClass); 244 } 245 } 246 else { 247 // Non-empty set of methods 248 annotatedMethods.forEach((method, listeners) -> 249 listeners.forEach(listener -> processJmsListener(listener, method, bean))); 250 if (logger.isDebugEnabled()) { 251 logger.debug(annotatedMethods.size() + " @JmsListener methods processed on bean '" + beanName + 252 "': " + annotatedMethods); 253 } 254 } 255 } 256 return bean; 257 } 258 259 /** 260 * Process the given {@link JmsListener} annotation on the given method, 261 * registering a corresponding endpoint for the given bean instance. 262 * @param jmsListener the annotation to process 263 * @param mostSpecificMethod the annotated method 264 * @param bean the instance to invoke the method on 265 * @see #createMethodJmsListenerEndpoint() 266 * @see JmsListenerEndpointRegistrar#registerEndpoint 267 */ 268 protected void processJmsListener(JmsListener jmsListener, Method mostSpecificMethod, Object bean) { 269 Method invocableMethod = AopUtils.selectInvocableMethod(mostSpecificMethod, bean.getClass()); 270 271 MethodJmsListenerEndpoint endpoint = createMethodJmsListenerEndpoint(); 272 endpoint.setBean(bean); 273 endpoint.setMethod(invocableMethod); 274 endpoint.setMostSpecificMethod(mostSpecificMethod); 275 endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory); 276 endpoint.setEmbeddedValueResolver(this.embeddedValueResolver); 277 endpoint.setBeanFactory(this.beanFactory); 278 endpoint.setId(getEndpointId(jmsListener)); 279 endpoint.setDestination(resolve(jmsListener.destination())); 280 if (StringUtils.hasText(jmsListener.selector())) { 281 endpoint.setSelector(resolve(jmsListener.selector())); 282 } 283 if (StringUtils.hasText(jmsListener.subscription())) { 284 endpoint.setSubscription(resolve(jmsListener.subscription())); 285 } 286 if (StringUtils.hasText(jmsListener.concurrency())) { 287 endpoint.setConcurrency(resolve(jmsListener.concurrency())); 288 } 289 290 JmsListenerContainerFactory<?> factory = null; 291 String containerFactoryBeanName = resolve(jmsListener.containerFactory()); 292 if (StringUtils.hasText(containerFactoryBeanName)) { 293 Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name"); 294 try { 295 factory = this.beanFactory.getBean(containerFactoryBeanName, JmsListenerContainerFactory.class); 296 } 297 catch (NoSuchBeanDefinitionException ex) { 298 throw new BeanInitializationException("Could not register JMS listener endpoint on [" + 299 mostSpecificMethod + "], no " + JmsListenerContainerFactory.class.getSimpleName() + 300 " with id '" + containerFactoryBeanName + "' was found in the application context", ex); 301 } 302 } 303 304 this.registrar.registerEndpoint(endpoint, factory); 305 } 306 307 /** 308 * Instantiate an empty {@link MethodJmsListenerEndpoint} for further 309 * configuration with provided parameters in {@link #processJmsListener}. 310 * @return a new {@code MethodJmsListenerEndpoint} or subclass thereof 311 * @since 4.1.9 312 * @see MethodJmsListenerEndpoint#createMessageListenerInstance() 313 */ 314 protected MethodJmsListenerEndpoint createMethodJmsListenerEndpoint() { 315 return new MethodJmsListenerEndpoint(); 316 } 317 318 private String getEndpointId(JmsListener jmsListener) { 319 if (StringUtils.hasText(jmsListener.id())) { 320 String id = resolve(jmsListener.id()); 321 return (id != null ? id : ""); 322 } 323 else { 324 return "org.springframework.jms.JmsListenerEndpointContainer#" + this.counter.getAndIncrement(); 325 } 326 } 327 328 @Nullable 329 private String resolve(String value) { 330 return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value); 331 } 332 333 334 /** 335 * A {@link MessageHandlerMethodFactory} adapter that offers a configurable underlying 336 * instance to use. Useful if the factory to use is determined once the endpoints 337 * have been registered but not created yet. 338 * @see JmsListenerEndpointRegistrar#setMessageHandlerMethodFactory 339 */ 340 private class MessageHandlerMethodFactoryAdapter implements MessageHandlerMethodFactory { 341 342 @Nullable 343 private MessageHandlerMethodFactory messageHandlerMethodFactory; 344 345 public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) { 346 this.messageHandlerMethodFactory = messageHandlerMethodFactory; 347 } 348 349 @Override 350 public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) { 351 return getMessageHandlerMethodFactory().createInvocableHandlerMethod(bean, method); 352 } 353 354 private MessageHandlerMethodFactory getMessageHandlerMethodFactory() { 355 if (this.messageHandlerMethodFactory == null) { 356 this.messageHandlerMethodFactory = createDefaultJmsHandlerMethodFactory(); 357 } 358 return this.messageHandlerMethodFactory; 359 } 360 361 private MessageHandlerMethodFactory createDefaultJmsHandlerMethodFactory() { 362 DefaultMessageHandlerMethodFactory defaultFactory = new DefaultMessageHandlerMethodFactory(); 363 if (beanFactory != null) { 364 defaultFactory.setBeanFactory(beanFactory); 365 } 366 defaultFactory.afterPropertiesSet(); 367 return defaultFactory; 368 } 369 } 370 371}