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.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.beans.factory.config.BeanFactoryPostProcessor;
037import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
038import org.springframework.context.ApplicationContext;
039import org.springframework.context.ApplicationContextAware;
040import org.springframework.context.ApplicationListener;
041import org.springframework.context.ConfigurableApplicationContext;
042import org.springframework.core.MethodIntrospector;
043import org.springframework.core.annotation.AnnotatedElementUtils;
044import org.springframework.core.annotation.AnnotationAwareOrderComparator;
045import org.springframework.core.annotation.AnnotationUtils;
046import org.springframework.lang.Nullable;
047import org.springframework.stereotype.Component;
048import org.springframework.util.Assert;
049import org.springframework.util.ClassUtils;
050import org.springframework.util.CollectionUtils;
051
052/**
053 * Registers {@link EventListener} methods as individual {@link ApplicationListener} instances.
054 * Implements {@link BeanFactoryPostProcessor} (as of 5.1) primarily for early retrieval,
055 * avoiding AOP checks for this processor bean and its {@link EventListenerFactory} delegates.
056 *
057 * @author Stephane Nicoll
058 * @author Juergen Hoeller
059 * @since 4.2
060 * @see EventListenerFactory
061 * @see DefaultEventListenerFactory
062 */
063public class EventListenerMethodProcessor
064                implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {
065
066        protected final Log logger = LogFactory.getLog(getClass());
067
068        @Nullable
069        private ConfigurableApplicationContext applicationContext;
070
071        @Nullable
072        private ConfigurableListableBeanFactory beanFactory;
073
074        @Nullable
075        private List<EventListenerFactory> eventListenerFactories;
076
077        private final EventExpressionEvaluator evaluator = new EventExpressionEvaluator();
078
079        private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));
080
081
082        @Override
083        public void setApplicationContext(ApplicationContext applicationContext) {
084                Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext,
085                                "ApplicationContext does not implement ConfigurableApplicationContext");
086                this.applicationContext = (ConfigurableApplicationContext) applicationContext;
087        }
088
089        @Override
090        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
091                this.beanFactory = beanFactory;
092
093                Map<String, EventListenerFactory> beans = beanFactory.getBeansOfType(EventListenerFactory.class, false, false);
094                List<EventListenerFactory> factories = new ArrayList<>(beans.values());
095                AnnotationAwareOrderComparator.sort(factories);
096                this.eventListenerFactories = factories;
097        }
098
099
100        @Override
101        public void afterSingletonsInstantiated() {
102                ConfigurableListableBeanFactory beanFactory = this.beanFactory;
103                Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
104                String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
105                for (String beanName : beanNames) {
106                        if (!ScopedProxyUtils.isScopedTarget(beanName)) {
107                                Class<?> type = null;
108                                try {
109                                        type = AutoProxyUtils.determineTargetClass(beanFactory, beanName);
110                                }
111                                catch (Throwable ex) {
112                                        // An unresolvable bean type, probably from a lazy bean - let's ignore it.
113                                        if (logger.isDebugEnabled()) {
114                                                logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
115                                        }
116                                }
117                                if (type != null) {
118                                        if (ScopedObject.class.isAssignableFrom(type)) {
119                                                try {
120                                                        Class<?> targetClass = AutoProxyUtils.determineTargetClass(
121                                                                        beanFactory, ScopedProxyUtils.getTargetBeanName(beanName));
122                                                        if (targetClass != null) {
123                                                                type = targetClass;
124                                                        }
125                                                }
126                                                catch (Throwable ex) {
127                                                        // An invalid scoped proxy arrangement - let's ignore it.
128                                                        if (logger.isDebugEnabled()) {
129                                                                logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex);
130                                                        }
131                                                }
132                                        }
133                                        try {
134                                                processBean(beanName, type);
135                                        }
136                                        catch (Throwable ex) {
137                                                throw new BeanInitializationException("Failed to process @EventListener " +
138                                                                "annotation on bean with name '" + beanName + "'", ex);
139                                        }
140                                }
141                        }
142                }
143        }
144
145        private void processBean(final String beanName, final Class<?> targetType) {
146                if (!this.nonAnnotatedClasses.contains(targetType) &&
147                                AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
148                                !isSpringContainerClass(targetType)) {
149
150                        Map<Method, EventListener> annotatedMethods = null;
151                        try {
152                                annotatedMethods = MethodIntrospector.selectMethods(targetType,
153                                                (MethodIntrospector.MetadataLookup<EventListener>) method ->
154                                                                AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
155                        }
156                        catch (Throwable ex) {
157                                // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
158                                if (logger.isDebugEnabled()) {
159                                        logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
160                                }
161                        }
162
163                        if (CollectionUtils.isEmpty(annotatedMethods)) {
164                                this.nonAnnotatedClasses.add(targetType);
165                                if (logger.isTraceEnabled()) {
166                                        logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
167                                }
168                        }
169                        else {
170                                // Non-empty set of methods
171                                ConfigurableApplicationContext context = this.applicationContext;
172                                Assert.state(context != null, "No ApplicationContext set");
173                                List<EventListenerFactory> factories = this.eventListenerFactories;
174                                Assert.state(factories != null, "EventListenerFactory List not initialized");
175                                for (Method method : annotatedMethods.keySet()) {
176                                        for (EventListenerFactory factory : factories) {
177                                                if (factory.supportsMethod(method)) {
178                                                        Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
179                                                        ApplicationListener<?> applicationListener =
180                                                                        factory.createApplicationListener(beanName, targetType, methodToUse);
181                                                        if (applicationListener instanceof ApplicationListenerMethodAdapter) {
182                                                                ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
183                                                        }
184                                                        context.addApplicationListener(applicationListener);
185                                                        break;
186                                                }
187                                        }
188                                }
189                                if (logger.isDebugEnabled()) {
190                                        logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
191                                                        beanName + "': " + annotatedMethods);
192                                }
193                        }
194                }
195        }
196
197        /**
198         * Determine whether the given class is an {@code org.springframework}
199         * bean class that is not annotated as a user or test {@link Component}...
200         * which indicates that there is no {@link EventListener} to be found there.
201         * @since 5.1
202         */
203        private static boolean isSpringContainerClass(Class<?> clazz) {
204                return (clazz.getName().startsWith("org.springframework.") &&
205                                !AnnotatedElementUtils.isAnnotated(ClassUtils.getUserClass(clazz), Component.class));
206        }
207
208}