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}