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.context.event;
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;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
031import org.springframework.aop.scope.ScopedObject;
032import org.springframework.aop.scope.ScopedProxyUtils;
033import org.springframework.aop.support.AopUtils;
034import org.springframework.beans.factory.BeanInitializationException;
035import org.springframework.beans.factory.SmartInitializingSingleton;
036import org.springframework.context.ApplicationContext;
037import org.springframework.context.ApplicationContextAware;
038import org.springframework.context.ApplicationListener;
039import org.springframework.context.ConfigurableApplicationContext;
040import org.springframework.core.MethodIntrospector;
041import org.springframework.core.annotation.AnnotatedElementUtils;
042import org.springframework.core.annotation.AnnotationAwareOrderComparator;
043import org.springframework.util.Assert;
044import org.springframework.util.CollectionUtils;
045
046/**
047 * Registers {@link EventListener} methods as individual {@link ApplicationListener} instances.
048 *
049 * @author Stephane Nicoll
050 * @author Juergen Hoeller
051 * @since 4.2
052 */
053public class EventListenerMethodProcessor implements SmartInitializingSingleton, ApplicationContextAware {
054
055        protected final Log logger = LogFactory.getLog(getClass());
056
057        private ConfigurableApplicationContext applicationContext;
058
059        private final EventExpressionEvaluator evaluator = new EventExpressionEvaluator();
060
061        private final Set<Class<?>> nonAnnotatedClasses =
062                        Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));
063
064
065        @Override
066        public void setApplicationContext(ApplicationContext applicationContext) {
067                Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext,
068                                "ApplicationContext does not implement ConfigurableApplicationContext");
069                this.applicationContext = (ConfigurableApplicationContext) applicationContext;
070        }
071
072        @Override
073        public void afterSingletonsInstantiated() {
074                List<EventListenerFactory> factories = getEventListenerFactories();
075                String[] beanNames = this.applicationContext.getBeanNamesForType(Object.class);
076                for (String beanName : beanNames) {
077                        if (!ScopedProxyUtils.isScopedTarget(beanName)) {
078                                Class<?> type = null;
079                                try {
080                                        type = AutoProxyUtils.determineTargetClass(this.applicationContext.getBeanFactory(), beanName);
081                                }
082                                catch (Throwable ex) {
083                                        // An unresolvable bean type, probably from a lazy bean - let's ignore it.
084                                        if (logger.isDebugEnabled()) {
085                                                logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
086                                        }
087                                }
088                                if (type != null) {
089                                        if (ScopedObject.class.isAssignableFrom(type)) {
090                                                try {
091                                                        type = AutoProxyUtils.determineTargetClass(this.applicationContext.getBeanFactory(),
092                                                                        ScopedProxyUtils.getTargetBeanName(beanName));
093                                                }
094                                                catch (Throwable ex) {
095                                                        // An invalid scoped proxy arrangement - let's ignore it.
096                                                        if (logger.isDebugEnabled()) {
097                                                                logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex);
098                                                        }
099                                                }
100                                        }
101                                        try {
102                                                processBean(factories, beanName, type);
103                                        }
104                                        catch (Throwable ex) {
105                                                throw new BeanInitializationException("Failed to process @EventListener " +
106                                                                "annotation on bean with name '" + beanName + "'", ex);
107                                        }
108                                }
109                        }
110                }
111        }
112
113
114        /**
115         * Return the {@link EventListenerFactory} instances to use to handle
116         * {@link EventListener} annotated methods.
117         */
118        protected List<EventListenerFactory> getEventListenerFactories() {
119                Map<String, EventListenerFactory> beans = this.applicationContext.getBeansOfType(EventListenerFactory.class);
120                List<EventListenerFactory> factories = new ArrayList<EventListenerFactory>(beans.values());
121                AnnotationAwareOrderComparator.sort(factories);
122                return factories;
123        }
124
125        protected void processBean(final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) {
126                if (!this.nonAnnotatedClasses.contains(targetType)) {
127                        Map<Method, EventListener> annotatedMethods = null;
128                        try {
129                                annotatedMethods = MethodIntrospector.selectMethods(targetType,
130                                                new MethodIntrospector.MetadataLookup<EventListener>() {
131                                                        @Override
132                                                        public EventListener inspect(Method method) {
133                                                                return AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class);
134                                                        }
135                                                });
136                        }
137                        catch (Throwable ex) {
138                                // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
139                                if (logger.isDebugEnabled()) {
140                                        logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
141                                }
142                        }
143                        if (CollectionUtils.isEmpty(annotatedMethods)) {
144                                this.nonAnnotatedClasses.add(targetType);
145                                if (logger.isTraceEnabled()) {
146                                        logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
147                                }
148                        }
149                        else {
150                                // Non-empty set of methods
151                                for (Method method : annotatedMethods.keySet()) {
152                                        for (EventListenerFactory factory : factories) {
153                                                if (factory.supportsMethod(method)) {
154                                                        Method methodToUse = AopUtils.selectInvocableMethod(
155                                                                        method, this.applicationContext.getType(beanName));
156                                                        ApplicationListener<?> applicationListener =
157                                                                        factory.createApplicationListener(beanName, targetType, methodToUse);
158                                                        if (applicationListener instanceof ApplicationListenerMethodAdapter) {
159                                                                ((ApplicationListenerMethodAdapter) applicationListener)
160                                                                                .init(this.applicationContext, this.evaluator);
161                                                        }
162                                                        this.applicationContext.addApplicationListener(applicationListener);
163                                                        break;
164                                                }
165                                        }
166                                }
167                                if (logger.isDebugEnabled()) {
168                                        logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
169                                                        beanName + "': " + annotatedMethods);
170                                }
171                        }
172                }
173        }
174
175}