001/* 002 * Copyright 2002-2018 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.jms.config.JmsListenerConfigUtils; 050import org.springframework.jms.config.JmsListenerContainerFactory; 051import org.springframework.jms.config.JmsListenerEndpointRegistrar; 052import org.springframework.jms.config.JmsListenerEndpointRegistry; 053import org.springframework.jms.config.MethodJmsListenerEndpoint; 054import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; 055import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory; 056import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; 057import org.springframework.util.Assert; 058import org.springframework.util.StringUtils; 059import org.springframework.util.StringValueResolver; 060 061/** 062 * Bean post-processor that registers methods annotated with {@link JmsListener} 063 * to be invoked by a JMS message listener container created under the cover 064 * by a {@link org.springframework.jms.config.JmsListenerContainerFactory} 065 * according to the attributes of the annotation. 066 * 067 * <p>Annotated methods can use flexible arguments as defined by {@link JmsListener}. 068 * 069 * <p>This post-processor is automatically registered by Spring's 070 * {@code <jms:annotation-driven>} XML element, and also by the {@link EnableJms} 071 * annotation. 072 * 073 * <p>Autodetects any {@link JmsListenerConfigurer} instances in the container, 074 * allowing for customization of the registry to be used, the default container 075 * factory or for fine-grained control over endpoints registration. See the 076 * {@link EnableJms} javadocs for complete usage details. 077 * 078 * @author Stephane Nicoll 079 * @author Juergen Hoeller 080 * @since 4.1 081 * @see JmsListener 082 * @see EnableJms 083 * @see JmsListenerConfigurer 084 * @see JmsListenerEndpointRegistrar 085 * @see JmsListenerEndpointRegistry 086 * @see org.springframework.jms.config.JmsListenerEndpoint 087 * @see MethodJmsListenerEndpoint 088 */ 089public class JmsListenerAnnotationBeanPostProcessor 090 implements MergedBeanDefinitionPostProcessor, Ordered, BeanFactoryAware, SmartInitializingSingleton { 091 092 /** 093 * The bean name of the default {@link JmsListenerContainerFactory}. 094 */ 095 static final String DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "jmsListenerContainerFactory"; 096 097 098 protected final Log logger = LogFactory.getLog(getClass()); 099 100 private String containerFactoryBeanName = DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME; 101 102 private JmsListenerEndpointRegistry endpointRegistry; 103 104 private final MessageHandlerMethodFactoryAdapter messageHandlerMethodFactory = 105 new MessageHandlerMethodFactoryAdapter(); 106 107 private BeanFactory beanFactory; 108 109 private StringValueResolver embeddedValueResolver; 110 111 private final JmsListenerEndpointRegistrar registrar = new JmsListenerEndpointRegistrar(); 112 113 private final AtomicInteger counter = new AtomicInteger(); 114 115 private final Set<Class<?>> nonAnnotatedClasses = 116 Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64)); 117 118 119 @Override 120 public int getOrder() { 121 return LOWEST_PRECEDENCE; 122 } 123 124 /** 125 * Set the name of the {@link JmsListenerContainerFactory} to use by default. 126 * <p>If none is specified, "jmsListenerContainerFactory" is assumed to be defined. 127 */ 128 public void setContainerFactoryBeanName(String containerFactoryBeanName) { 129 this.containerFactoryBeanName = containerFactoryBeanName; 130 } 131 132 /** 133 * Set the {@link JmsListenerEndpointRegistry} that will hold the created 134 * endpoint and manage the lifecycle of the related listener container. 135 */ 136 public void setEndpointRegistry(JmsListenerEndpointRegistry endpointRegistry) { 137 this.endpointRegistry = endpointRegistry; 138 } 139 140 /** 141 * Set the {@link MessageHandlerMethodFactory} to use to configure the message 142 * listener responsible to serve an endpoint detected by this processor. 143 * <p>By default, {@link DefaultMessageHandlerMethodFactory} is used and it 144 * can be configured further to support additional method arguments 145 * or to customize conversion and validation support. See 146 * {@link DefaultMessageHandlerMethodFactory} Javadoc for more details. 147 */ 148 public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) { 149 this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(messageHandlerMethodFactory); 150 } 151 152 /** 153 * Making a {@link BeanFactory} available is optional; if not set, 154 * {@link JmsListenerConfigurer} beans won't get autodetected and an 155 * {@link #setEndpointRegistry endpoint registry} has to be explicitly configured. 156 */ 157 @Override 158 public void setBeanFactory(BeanFactory beanFactory) { 159 this.beanFactory = beanFactory; 160 if (beanFactory instanceof ConfigurableBeanFactory) { 161 this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory); 162 } 163 this.registrar.setBeanFactory(beanFactory); 164 } 165 166 167 @Override 168 public void afterSingletonsInstantiated() { 169 // Remove resolved singleton classes from cache 170 this.nonAnnotatedClasses.clear(); 171 172 if (this.beanFactory instanceof ListableBeanFactory) { 173 // Apply JmsListenerConfigurer beans from the BeanFactory, if any 174 Map<String, JmsListenerConfigurer> beans = 175 ((ListableBeanFactory) this.beanFactory).getBeansOfType(JmsListenerConfigurer.class); 176 List<JmsListenerConfigurer> configurers = new ArrayList<JmsListenerConfigurer>(beans.values()); 177 AnnotationAwareOrderComparator.sort(configurers); 178 for (JmsListenerConfigurer configurer : configurers) { 179 configurer.configureJmsListeners(this.registrar); 180 } 181 } 182 183 if (this.containerFactoryBeanName != null) { 184 this.registrar.setContainerFactoryBeanName(this.containerFactoryBeanName); 185 } 186 187 if (this.registrar.getEndpointRegistry() == null) { 188 // Determine JmsListenerEndpointRegistry bean from the BeanFactory 189 if (this.endpointRegistry == null) { 190 Assert.state(this.beanFactory != null, "BeanFactory must be set to find endpoint registry by bean name"); 191 this.endpointRegistry = this.beanFactory.getBean( 192 JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, JmsListenerEndpointRegistry.class); 193 } 194 this.registrar.setEndpointRegistry(this.endpointRegistry); 195 } 196 197 198 // Set the custom handler method factory once resolved by the configurer 199 MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory(); 200 if (handlerMethodFactory != null) { 201 this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(handlerMethodFactory); 202 } 203 204 // Actually register all listeners 205 this.registrar.afterPropertiesSet(); 206 } 207 208 209 @Override 210 public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { 211 } 212 213 @Override 214 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 215 return bean; 216 } 217 218 @Override 219 public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { 220 if (bean instanceof AopInfrastructureBean) { 221 // Ignore AOP infrastructure such as scoped proxies. 222 return bean; 223 } 224 225 Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); 226 if (!this.nonAnnotatedClasses.contains(targetClass)) { 227 Map<Method, Set<JmsListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, 228 new MethodIntrospector.MetadataLookup<Set<JmsListener>>() { 229 @Override 230 public Set<JmsListener> inspect(Method method) { 231 Set<JmsListener> listenerMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations( 232 method, JmsListener.class, JmsListeners.class); 233 return (!listenerMethods.isEmpty() ? listenerMethods : null); 234 } 235 }); 236 if (annotatedMethods.isEmpty()) { 237 this.nonAnnotatedClasses.add(targetClass); 238 if (logger.isTraceEnabled()) { 239 logger.trace("No @JmsListener annotations found on bean type: " + targetClass); 240 } 241 } 242 else { 243 // Non-empty set of methods 244 for (Map.Entry<Method, Set<JmsListener>> entry : annotatedMethods.entrySet()) { 245 Method method = entry.getKey(); 246 for (JmsListener listener : entry.getValue()) { 247 processJmsListener(listener, method, bean); 248 } 249 } 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 return resolve(jmsListener.id()); 321 } 322 else { 323 return "org.springframework.jms.JmsListenerEndpointContainer#" + this.counter.getAndIncrement(); 324 } 325 } 326 327 private String resolve(String value) { 328 return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value); 329 } 330 331 332 /** 333 * A {@link MessageHandlerMethodFactory} adapter that offers a configurable underlying 334 * instance to use. Useful if the factory to use is determined once the endpoints 335 * have been registered but not created yet. 336 * @see JmsListenerEndpointRegistrar#setMessageHandlerMethodFactory 337 */ 338 private class MessageHandlerMethodFactoryAdapter implements MessageHandlerMethodFactory { 339 340 private MessageHandlerMethodFactory messageHandlerMethodFactory; 341 342 public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) { 343 this.messageHandlerMethodFactory = messageHandlerMethodFactory; 344 } 345 346 @Override 347 public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) { 348 return getMessageHandlerMethodFactory().createInvocableHandlerMethod(bean, method); 349 } 350 351 private MessageHandlerMethodFactory getMessageHandlerMethodFactory() { 352 if (this.messageHandlerMethodFactory == null) { 353 this.messageHandlerMethodFactory = createDefaultJmsHandlerMethodFactory(); 354 } 355 return this.messageHandlerMethodFactory; 356 } 357 358 private MessageHandlerMethodFactory createDefaultJmsHandlerMethodFactory() { 359 DefaultMessageHandlerMethodFactory defaultFactory = new DefaultMessageHandlerMethodFactory(); 360 defaultFactory.setBeanFactory(beanFactory); 361 defaultFactory.afterPropertiesSet(); 362 return defaultFactory; 363 } 364 } 365 366}