Java tutorial
/* * Copyright 2014-2017 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 * * http://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 dstrelec.nats.annotation; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import dstrelec.nats.config.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.Advised; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanExpressionContext; import org.springframework.beans.factory.config.BeanExpressionResolver; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.expression.StandardBeanExpressionResolver; import org.springframework.core.MethodIntrospector; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.messaging.converter.GenericMessageConverter; import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; import org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver; import org.springframework.messaging.handler.annotation.support.HeadersMethodArgumentResolver; import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory; import org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver; import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver; import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** * Bean post-processor that registers methods annotated with {@link NatsListener} * to be invoked by a Nats message listener container created under the covers * by a {@link NatsListenerContainerFactory} * according to the parameters of the annotation. * * <p>Annotated methods can use flexible arguments as defined by {@link NatsListener}. * * <p>This post-processor is automatically registered by {@link EnableNats} annotation. * * <p>Auto-detect any {@link NatsListenerConfigurer} instances in the container, * allowing for customization of the registry to be used, the default container * factory or for fine-grained control over endpoints registration. See * {@link EnableNats} Javadoc for complete usage details. * * @author Stephane Nicoll * @author Juergen Hoeller * @author Gary Russell * @author Artem Bilan * @author Dariusz Szablinski * @author Dario Strelec * * @see NatsListener * @see EnableNats * @see NatsListenerConfigurer * @see NatsListenerEndpointRegistrar * @see NatsListenerEndpointRegistry * @see dstrelec.nats.config.NatsListenerEndpoint * @see MethodNatsListenerEndpoint */ public class NatsListenerAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, BeanFactoryAware, SmartInitializingSingleton { /** * The bean name of the default {@link NatsListenerContainerFactory}. */ public static final String DEFAULT_NATS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "natsListenerContainerFactory"; private final Set<Class<?>> nonAnnotatedClasses = Collections .newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64)); private final Log logger = LogFactory.getLog(getClass()); private NatsListenerEndpointRegistry endpointRegistry; private String containerFactoryBeanName = DEFAULT_NATS_LISTENER_CONTAINER_FACTORY_BEAN_NAME; private BeanFactory beanFactory; private final NatsHandlerMethodFactoryAdapter messageHandlerMethodFactory = new NatsHandlerMethodFactoryAdapter(); private final NatsListenerEndpointRegistrar registrar = new NatsListenerEndpointRegistrar(); private final AtomicInteger counter = new AtomicInteger(); private BeanExpressionResolver resolver = new StandardBeanExpressionResolver(); private BeanExpressionContext expressionContext; @Override public int getOrder() { return LOWEST_PRECEDENCE; } /** * Set the {@link NatsListenerEndpointRegistry} that will hold the created * endpoint and manage the lifecycle of the related listener container. * @param endpointRegistry the {@link NatsListenerEndpointRegistry} to set. */ public void setEndpointRegistry(NatsListenerEndpointRegistry endpointRegistry) { this.endpointRegistry = endpointRegistry; } /** * Set the name of the {@link NatsListenerContainerFactory} to use by default. * <p>If none is specified, "NatsListenerContainerFactory" is assumed to be defined. * @param containerFactoryBeanName the {@link NatsListenerContainerFactory} bean name. */ public void setContainerFactoryBeanName(String containerFactoryBeanName) { this.containerFactoryBeanName = containerFactoryBeanName; } /** * Set the {@link MessageHandlerMethodFactory} to use to configure the message * listener responsible to serve an endpoint detected by this processor. * <p>By default, {@link DefaultMessageHandlerMethodFactory} is used and it * can be configured further to support additional method arguments * or to customize conversion and validation support. See * {@link DefaultMessageHandlerMethodFactory} Javadoc for more details. * @param messageHandlerMethodFactory the {@link MessageHandlerMethodFactory} instance. */ public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) { this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(messageHandlerMethodFactory); } /** * Making a {@link BeanFactory} available is optional; if not set, * {@link NatsListenerConfigurer} beans won't get autodetected and an * {@link #setEndpointRegistry endpoint registry} has to be explicitly configured. * @param beanFactory the {@link BeanFactory} to be used. */ @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; if (beanFactory instanceof ConfigurableListableBeanFactory) { this.resolver = ((ConfigurableListableBeanFactory) beanFactory).getBeanExpressionResolver(); this.expressionContext = new BeanExpressionContext((ConfigurableListableBeanFactory) beanFactory, null); } } @Override public void afterSingletonsInstantiated() { this.registrar.setBeanFactory(this.beanFactory); if (this.beanFactory instanceof ListableBeanFactory) { Map<String, NatsListenerConfigurer> instances = ((ListableBeanFactory) this.beanFactory) .getBeansOfType(NatsListenerConfigurer.class); for (NatsListenerConfigurer configurer : instances.values()) { configurer.configureNatsListeners(this.registrar); } } if (this.registrar.getEndpointRegistry() == null) { if (this.endpointRegistry == null) { Assert.state(this.beanFactory != null, "BeanFactory must be set to find endpoint registry by bean name"); this.endpointRegistry = this.beanFactory.getBean( NatsListenerConfigUtils.NATS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, NatsListenerEndpointRegistry.class); } this.registrar.setEndpointRegistry(this.endpointRegistry); } if (this.containerFactoryBeanName != null) { this.registrar.setContainerFactoryBeanName(this.containerFactoryBeanName); } // Set the custom handler method factory once resolved by the configurer MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory(); if (handlerMethodFactory != null) { this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(handlerMethodFactory); } // Actually register all listeners this.registrar.afterPropertiesSet(); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { if (beanName.equals("myListener")) { System.out.println(beanName); } if (!this.nonAnnotatedClasses.contains(bean.getClass())) { Class<?> targetClass = AopUtils.getTargetClass(bean); Collection<NatsListener> classLevelListeners = findListenerAnnotations(targetClass); final boolean hasClassLevelListeners = classLevelListeners.size() > 0; final List<Method> multiMethods = new ArrayList<>(); Map<Method, Set<NatsListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup<Set<NatsListener>>) method -> { Set<NatsListener> listenerMethods = findListenerAnnotations(method); return (!listenerMethods.isEmpty() ? listenerMethods : null); }); if (hasClassLevelListeners) { Set<Method> methodsWithHandler = MethodIntrospector.selectMethods(targetClass, (ReflectionUtils.MethodFilter) method -> AnnotationUtils.findAnnotation(method, NatsHandler.class) != null); multiMethods.addAll(methodsWithHandler); } if (annotatedMethods.isEmpty()) { this.nonAnnotatedClasses.add(bean.getClass()); if (this.logger.isTraceEnabled()) { this.logger.trace("No @NatsListener annotations found on bean type: " + bean.getClass()); } } else { // Non-empty set of methods for (Map.Entry<Method, Set<NatsListener>> entry : annotatedMethods.entrySet()) { Method method = entry.getKey(); for (NatsListener listener : entry.getValue()) { processNatsListener(listener, method, bean, beanName); } } if (this.logger.isDebugEnabled()) { this.logger.debug(annotatedMethods.size() + " @NatsListener methods processed on bean '" + beanName + "': " + annotatedMethods); } } if (hasClassLevelListeners) { processMultiMethodListeners(classLevelListeners, multiMethods, bean, beanName); } } return bean; } /* * AnnotationUtils.getRepeatableAnnotations does not look at interfaces */ private Collection<NatsListener> findListenerAnnotations(Class<?> clazz) { Set<NatsListener> listeners = new HashSet<>(); NatsListener ann = AnnotationUtils.findAnnotation(clazz, NatsListener.class); if (ann != null) { listeners.add(ann); } NatsListeners anns = AnnotationUtils.findAnnotation(clazz, NatsListeners.class); if (anns != null) { listeners.addAll(Arrays.asList(anns.value())); } return listeners; } /* * AnnotationUtils.getRepeatableAnnotations does not look at interfaces */ private Set<NatsListener> findListenerAnnotations(Method method) { Set<NatsListener> listeners = new HashSet<>(); NatsListener ann = AnnotationUtils.findAnnotation(method, NatsListener.class); if (ann != null) { listeners.add(ann); } NatsListeners anns = AnnotationUtils.findAnnotation(method, NatsListeners.class); if (anns != null) { listeners.addAll(Arrays.asList(anns.value())); } return listeners; } private void processMultiMethodListeners(Collection<NatsListener> classLevelListeners, List<Method> multiMethods, Object bean, String beanName) { List<Method> checkedMethods = new ArrayList<Method>(); for (Method method : multiMethods) { checkedMethods.add(checkProxy(method, bean)); } for (NatsListener classLevelListener : classLevelListeners) { MultiMethodNatsListenerEndpoint endpoint = new MultiMethodNatsListenerEndpoint(checkedMethods, bean); endpoint.setBeanFactory(this.beanFactory); processListener(endpoint, classLevelListener, bean, bean.getClass(), beanName); } } protected void processNatsListener(NatsListener natsListener, Method method, Object bean, String beanName) { Method methodToUse = checkProxy(method, bean); MethodNatsListenerEndpoint endpoint = new MethodNatsListenerEndpoint(); endpoint.setMethod(methodToUse); endpoint.setBeanFactory(this.beanFactory); processListener(endpoint, natsListener, bean, methodToUse, beanName); } private Method checkProxy(Method methodArg, Object bean) { Method method = methodArg; if (AopUtils.isJdkDynamicProxy(bean)) { try { // Found a @NatsListener method on the target class for this JDK proxy -> // is it also present on the proxy itself? method = bean.getClass().getMethod(method.getName(), method.getParameterTypes()); Class<?>[] proxiedInterfaces = ((Advised) bean).getProxiedInterfaces(); for (Class<?> iface : proxiedInterfaces) { try { method = iface.getMethod(method.getName(), method.getParameterTypes()); break; } catch (NoSuchMethodException noMethod) { } } } catch (SecurityException ex) { ReflectionUtils.handleReflectionException(ex); } catch (NoSuchMethodException ex) { throw new IllegalStateException(String.format( "@NatsListener method '%s' found on bean target class '%s', " + "but not found in any interface(s) for bean JDK proxy. Either " + "pull the method up to an interface or switch to subclass (CGLIB) " + "proxies by setting proxy-target-class/proxyTargetClass " + "attribute to 'true'", method.getName(), method.getDeclaringClass().getSimpleName()), ex); } } return method; } protected void processListener(MethodNatsListenerEndpoint endpoint, NatsListener natsListener, Object bean, Object adminTarget, String beanName) { endpoint.setBean(bean); endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory); endpoint.setId(getEndpointId(natsListener)); endpoint.setSubjects(resolveSubjects(natsListener)); String group = natsListener.group(); if (StringUtils.hasText(group)) { Object resolvedGroup = resolveExpression(group); if (resolvedGroup instanceof String) { endpoint.setGroup((String) resolvedGroup); } } NatsListenerContainerFactory factory = null; String containerFactoryBeanName = resolve(natsListener.containerFactory()); if (StringUtils.hasText(containerFactoryBeanName)) { Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name"); try { factory = this.beanFactory.getBean(containerFactoryBeanName, NatsListenerContainerFactory.class); } catch (NoSuchBeanDefinitionException ex) { throw new BeanInitializationException("Could not register Nats listener endpoint on [" + adminTarget + "] for bean " + beanName + ", no " + NatsListenerContainerFactory.class.getSimpleName() + " with id '" + containerFactoryBeanName + "' was found in the application context", ex); } } this.registrar.registerEndpoint(endpoint, factory); } private String getEndpointId(NatsListener natsListener) { if (StringUtils.hasText(natsListener.id())) { return resolve(natsListener.id()); } else { return "dstrelec.nats.NatsListenerEndpointContainer#" + this.counter.getAndIncrement(); } } private String[] resolveSubjects(NatsListener natsListener) { String[] subjects = natsListener.subjects(); List<String> result = new ArrayList<>(); if (subjects.length > 0) { for (int i = 0; i < subjects.length; i++) { Object topic = resolveExpression(subjects[i]); resolveAsString(topic, result); } } return result.toArray(new String[result.size()]); } @SuppressWarnings("unchecked") private void resolveAsString(Object resolvedValue, List<String> result) { if (resolvedValue instanceof String[]) { for (Object object : (String[]) resolvedValue) { resolveAsString(object, result); } } if (resolvedValue instanceof String) { result.add((String) resolvedValue); } else if (resolvedValue instanceof Iterable) { for (Object object : (Iterable<Object>) resolvedValue) { resolveAsString(object, result); } } else { throw new IllegalArgumentException( String.format("@NatsListener can't resolve '%s' as a String", resolvedValue)); } } private Object resolveExpression(String value) { String resolvedValue = resolve(value); if (!(resolvedValue.startsWith("#{") && value.endsWith("}"))) { return resolvedValue; } return this.resolver.evaluate(resolvedValue, this.expressionContext); } /** * Resolve the specified value if possible. * @param value the value to resolve * @return the resolved value * @see ConfigurableBeanFactory#resolveEmbeddedValue */ private String resolve(String value) { if (this.beanFactory != null && this.beanFactory instanceof ConfigurableBeanFactory) { return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value); } return value; } /** * An {@link MessageHandlerMethodFactory} adapter that offers a configurable underlying * instance to use. Useful if the factory to use is determined once the endpoints * have been registered but not created yet. * @see NatsListenerEndpointRegistrar#setMessageHandlerMethodFactory */ private class NatsHandlerMethodFactoryAdapter implements MessageHandlerMethodFactory { private MessageHandlerMethodFactory messageHandlerMethodFactory; public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory natsHandlerMethodFactory1) { this.messageHandlerMethodFactory = natsHandlerMethodFactory1; } @Override public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) { return getMessageHandlerMethodFactory().createInvocableHandlerMethod(bean, method); } private MessageHandlerMethodFactory getMessageHandlerMethodFactory() { if (this.messageHandlerMethodFactory == null) { this.messageHandlerMethodFactory = createDefaultMessageHandlerMethodFactory(); } return this.messageHandlerMethodFactory; } private MessageHandlerMethodFactory createDefaultMessageHandlerMethodFactory() { DefaultMessageHandlerMethodFactory defaultFactory = new DefaultMessageHandlerMethodFactory(); defaultFactory.setBeanFactory(NatsListenerAnnotationBeanPostProcessor.this.beanFactory); ConfigurableBeanFactory cbf = (NatsListenerAnnotationBeanPostProcessor.this.beanFactory instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) NatsListenerAnnotationBeanPostProcessor.this.beanFactory : null); DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); defaultFactory.setConversionService(conversionService); List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(); // Annotation-based argument resolution argumentResolvers.add(new HeaderMethodArgumentResolver(conversionService, cbf)); argumentResolvers.add(new HeadersMethodArgumentResolver()); // Type-based argument resolution final GenericMessageConverter messageConverter = new GenericMessageConverter(conversionService); argumentResolvers.add(new MessageMethodArgumentResolver(messageConverter)); argumentResolvers.add(new PayloadArgumentResolver(messageConverter) { @Override protected boolean isEmptyPayload(Object payload) { return payload == null; } }); defaultFactory.setArgumentResolvers(argumentResolvers); defaultFactory.afterPropertiesSet(); return defaultFactory; } } }