diff --git a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java index 2eb8b15bd5a5..4a7a06b24313 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 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. @@ -21,6 +21,7 @@ import org.springframework.aop.ClassFilter; import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -96,7 +97,7 @@ public AnnotationMatchingPointcut(@Nullable Class classAnn this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited); } else { - this.classFilter = ClassFilter.TRUE; + this.classFilter = new AnnotationCandidateClassFilter(methodAnnotationType); } if (methodAnnotationType != null) { @@ -164,4 +165,23 @@ public static AnnotationMatchingPointcut forMethodAnnotation(Class annotationType; + + public AnnotationCandidateClassFilter(Class annotationType) { + this.annotationType = annotationType; + } + + @Override + public boolean matches(Class clazz) { + return AnnotationUtils.isCandidateClass(clazz, this.annotationType); + } + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index 8de861d21c90..d19d2340b0c5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -59,6 +59,7 @@ import org.springframework.core.PriorityOrdered; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -244,25 +245,33 @@ public Constructor[] determineCandidateConstructors(Class beanClass, final // Let's check for lookup methods here.. if (!this.lookupMethodsChecked.contains(beanName)) { - try { - ReflectionUtils.doWithMethods(beanClass, method -> { - Lookup lookup = method.getAnnotation(Lookup.class); - if (lookup != null) { - Assert.state(this.beanFactory != null, "No BeanFactory available"); - LookupOverride override = new LookupOverride(method, lookup.value()); - try { - RootBeanDefinition mbd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName); - mbd.getMethodOverrides().addOverride(override); - } - catch (NoSuchBeanDefinitionException ex) { - throw new BeanCreationException(beanName, - "Cannot apply @Lookup to beans without corresponding bean definition"); - } + if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) { + try { + Class targetClass = beanClass; + do { + ReflectionUtils.doWithLocalMethods(targetClass, method -> { + Lookup lookup = method.getAnnotation(Lookup.class); + if (lookup != null) { + Assert.state(this.beanFactory != null, "No BeanFactory available"); + LookupOverride override = new LookupOverride(method, lookup.value()); + try { + RootBeanDefinition mbd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName); + mbd.getMethodOverrides().addOverride(override); + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanCreationException(beanName, + "Cannot apply @Lookup to beans without corresponding bean definition"); + } + } + }); + targetClass = targetClass.getSuperclass(); } - }); - } - catch (IllegalStateException ex) { - throw new BeanCreationException(beanName, "Lookup method resolution failed", ex); + while (targetClass != null && targetClass != Object.class); + + } + catch (IllegalStateException ex) { + throw new BeanCreationException(beanName, "Lookup method resolution failed", ex); + } } this.lookupMethodsChecked.add(beanName); } @@ -433,6 +442,10 @@ private InjectionMetadata findAutowiringMetadata(String beanName, Class clazz } private InjectionMetadata buildAutowiringMetadata(final Class clazz) { + if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) { + return new InjectionMetadata(clazz, Collections.emptyList()); + } + List elements = new ArrayList<>(); Class targetClass = clazz; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java index 96bb24e0d955..162e464a6907 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -24,7 +24,9 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -41,6 +43,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -196,6 +199,10 @@ private LifecycleMetadata findLifecycleMetadata(Class clazz) { } private LifecycleMetadata buildLifecycleMetadata(final Class clazz) { + if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) { + return new LifecycleMetadata(clazz, Collections.emptyList(), Collections.emptyList()); + } + List initMethods = new ArrayList<>(); List destroyMethods = new ArrayList<>(); Class targetClass = clazz; diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java index e2f8681e181e..16c2f5124e02 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -103,6 +103,16 @@ public AnnotationCacheOperationSource(Set annotationParse } + @Override + public boolean isCandidateClass(Class targetClass) { + for (CacheAnnotationParser parser : this.annotationParsers) { + if (parser.isCandidateClass(targetClass)) { + return true; + } + } + return false; + } + @Override @Nullable protected Collection findCacheOperations(Class clazz) { @@ -127,8 +137,8 @@ protected Collection findCacheOperations(Method method) { @Nullable protected Collection determineCacheOperations(CacheOperationProvider provider) { Collection ops = null; - for (CacheAnnotationParser annotationParser : this.annotationParsers) { - Collection annOps = provider.getCacheOperations(annotationParser); + for (CacheAnnotationParser parser : this.annotationParsers) { + Collection annOps = provider.getCacheOperations(parser); if (annOps != null) { if (ops == null) { ops = annOps; diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java b/spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java index e03bedee72f8..7cb51bd7f8d5 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -30,12 +30,31 @@ * * @author Costin Leau * @author Stephane Nicoll + * @author Juergen Hoeller * @since 3.1 * @see AnnotationCacheOperationSource * @see SpringCacheAnnotationParser */ public interface CacheAnnotationParser { + /** + * Determine whether the given class is a candidate for cache operations + * in the annotation format of this {@code CacheAnnotationParser}. + *

If this method returns {@code false}, the methods on the given class + * will not get traversed for {@code #parseCacheAnnotations} introspection. + * Returning {@code false} is therefore an optimization for non-affected + * classes, whereas {@code true} simply means that the class needs to get + * fully introspected for each method on the given class individually. + * @param targetClass the class to introspect + * @return {@code false} if the class is known to have no cache operation + * annotations at class or method level; {@code true} otherwise. The default + * implementation returns {@code true}, leading to regular introspection. + * @since 5.2 + */ + default boolean isCandidateClass(Class targetClass) { + return true; + } + /** * Parse the cache definition for the given class, * based on an annotation type understood by this parser. diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java index a660f4f6c315..3111bfc6dbb1 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -30,6 +30,7 @@ import org.springframework.cache.interceptor.CachePutOperation; import org.springframework.cache.interceptor.CacheableOperation; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; @@ -58,6 +59,11 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria } + @Override + public boolean isCandidateClass(Class targetClass) { + return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS); + } + @Override @Nullable public Collection parseCacheAnnotations(Class type) { diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSource.java index 0ab1059325b8..40e27ff24a62 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSource.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2019 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. @@ -27,10 +27,29 @@ * source level, or elsewhere. * * @author Costin Leau + * @author Juergen Hoeller * @since 3.1 */ public interface CacheOperationSource { + /** + * Determine whether the given class is a candidate for cache operations + * in the metadata format of this {@code CacheOperationSource}. + *

If this method returns {@code false}, the methods on the given class + * will not get traversed for {@link #getCacheOperations} introspection. + * Returning {@code false} is therefore an optimization for non-affected + * classes, whereas {@code true} simply means that the class needs to get + * fully introspected for each method on the given class individually. + * @param targetClass the class to introspect + * @return {@code false} if the class is known to have no cache operation + * metadata at class or method level; {@code true} otherwise. The default + * implementation returns {@code true}, leading to regular introspection. + * @since 5.2 + */ + default boolean isCandidateClass(Class targetClass) { + return true; + } + /** * Return the collection of cache operations for this method, or {@code null} * if the method contains no cacheable annotations. diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSourcePointcut.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSourcePointcut.java index edc01b01a7c9..476ad279894a 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSourcePointcut.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSourcePointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -19,6 +19,7 @@ import java.io.Serializable; import java.lang.reflect.Method; +import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcut; import org.springframework.cache.CacheManager; import org.springframework.lang.Nullable; @@ -36,11 +37,13 @@ @SuppressWarnings("serial") abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { + protected CacheOperationSourcePointcut() { + setClassFilter(new CacheOperationSourceClassFilter()); + } + + @Override public boolean matches(Method method, Class targetClass) { - if (CacheManager.class.isAssignableFrom(targetClass)) { - return false; - } CacheOperationSource cas = getCacheOperationSource(); return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass))); } @@ -75,4 +78,21 @@ public String toString() { @Nullable protected abstract CacheOperationSource getCacheOperationSource(); + + /** + * {@link ClassFilter} that delegates to {@link CacheOperationSource#isCandidateClass} + * for filtering classes whose methods are not worth searching to begin with. + */ + private class CacheOperationSourceClassFilter implements ClassFilter { + + @Override + public boolean matches(Class clazz) { + if (CacheManager.class.isAssignableFrom(clazz)) { + return false; + } + CacheOperationSource cas = getCacheOperationSource(); + return (cas == null || cas.isCandidateClass(clazz)); + } + } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CompositeCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CompositeCacheOperationSource.java index a5baa2cc43e8..4c9fdf43be9a 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CompositeCacheOperationSource.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CompositeCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -29,6 +29,7 @@ * over a given array of {@code CacheOperationSource} instances. * * @author Costin Leau + * @author Juergen Hoeller * @since 3.1 */ @SuppressWarnings("serial") @@ -42,7 +43,7 @@ public class CompositeCacheOperationSource implements CacheOperationSource, Seri * @param cacheOperationSources the CacheOperationSource instances to combine */ public CompositeCacheOperationSource(CacheOperationSource... cacheOperationSources) { - Assert.notEmpty(cacheOperationSources, "cacheOperationSources array must not be empty"); + Assert.notEmpty(cacheOperationSources, "CacheOperationSource array must not be empty"); this.cacheOperationSources = cacheOperationSources; } @@ -54,18 +55,27 @@ public final CacheOperationSource[] getCacheOperationSources() { return this.cacheOperationSources; } + + @Override + public boolean isCandidateClass(Class targetClass) { + for (CacheOperationSource source : this.cacheOperationSources) { + if (source.isCandidateClass(targetClass)) { + return true; + } + } + return false; + } + @Override @Nullable public Collection getCacheOperations(Method method, @Nullable Class targetClass) { Collection ops = null; - for (CacheOperationSource source : this.cacheOperationSources) { Collection cacheOperations = source.getCacheOperations(method, targetClass); if (cacheOperations != null) { if (ops == null) { ops = new ArrayList<>(); } - ops.addAll(cacheOperations); } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java index 2ed96949b367..19d4bdd98c3b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java @@ -64,6 +64,7 @@ import org.springframework.core.BridgeMethodResolver; import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.jndi.support.SimpleJndiBeanFactory; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -149,6 +150,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean @Nullable private static Class ejbRefClass; + private static Set> resourceAnnotationTypes = new LinkedHashSet<>(4); + static { try { @SuppressWarnings("unchecked") @@ -159,6 +162,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean catch (ClassNotFoundException ex) { webServiceRefClass = null; } + try { @SuppressWarnings("unchecked") Class clazz = (Class) @@ -168,6 +172,14 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean catch (ClassNotFoundException ex) { ejbRefClass = null; } + + resourceAnnotationTypes.add(Resource.class); + if (webServiceRefClass != null) { + resourceAnnotationTypes.add(webServiceRefClass); + } + if (ejbRefClass != null) { + resourceAnnotationTypes.add(ejbRefClass); + } } @@ -356,6 +368,10 @@ private InjectionMetadata findResourceMetadata(String beanName, final Class c } private InjectionMetadata buildResourceMetadata(final Class clazz) { + if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) { + return new InjectionMetadata(clazz, Collections.emptyList()); + } + List elements = new ArrayList<>(); Class targetClass = clazz; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index 22e735da59a6..94e1c9c3511c 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -24,9 +24,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.context.event.EventListenerFactory; import org.springframework.core.Conventions; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; @@ -96,6 +100,12 @@ else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) // Check already loaded Class if present... // since we possibly can't even load the class file for this Class. Class beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass(); + if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) || + BeanPostProcessor.class.isAssignableFrom(beanClass) || + AopInfrastructureBean.class.isAssignableFrom(beanClass) || + EventListenerFactory.class.isAssignableFrom(beanClass)) { + return false; + } metadata = new StandardAnnotationMetadata(beanClass, true); } else { diff --git a/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java b/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java index 191f08925b2a..504a3b8b7767 100644 --- a/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -42,6 +42,7 @@ import org.springframework.core.MethodIntrospector; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.util.Assert; @@ -142,7 +143,10 @@ public void afterSingletonsInstantiated() { } private void processBean(final String beanName, final Class targetType) { - if (!this.nonAnnotatedClasses.contains(targetType) && !isSpringContainerClass(targetType)) { + if (!this.nonAnnotatedClasses.contains(targetType) && + AnnotationUtils.isCandidateClass(targetType, EventListener.class) && + !isSpringContainerClass(targetType)) { + Map annotatedMethods = null; try { annotatedMethods = MethodIntrospector.selectMethods(targetType, @@ -155,6 +159,7 @@ private void processBean(final String beanName, final Class targetType) { logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex); } } + if (CollectionUtils.isEmpty(annotatedMethods)) { this.nonAnnotatedClasses.add(targetType); if (logger.isTraceEnabled()) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java index 2a0218b60b45..e3f9aa40a014 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; @@ -59,6 +60,7 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.Trigger; @@ -340,7 +342,8 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { } Class targetClass = AopProxyUtils.ultimateTargetClass(bean); - if (!this.nonAnnotatedClasses.contains(targetClass)) { + if (!this.nonAnnotatedClasses.contains(targetClass) && + AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) { Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup>) method -> { Set scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations( diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index c8982c81a74e..2bf0f2dc24aa 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -28,6 +28,7 @@ import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; @@ -151,6 +152,55 @@ public abstract class AnnotationUtils { private static transient Log logger; + /** + * Determine whether the given class is a candidate for carrying one of the specified + * annotations (at type, method or field level). + * @param clazz the class to introspect + * @param annotationTypes the searchable annotation types + * @return {@code false} if the class is known to have no such annotations at any level; + * {@code true} otherwise. Callers will usually perform full method/field introspection + * if {@code true} is being returned here. + * @since 5.2 + * @see #isCandidateClass(Class, Class) + */ + public static boolean isCandidateClass(Class clazz, Collection> annotationTypes) { + for (Class annotationType : annotationTypes) { + if (isCandidateClass(clazz, annotationType)) { + return true; + } + } + return false; + } + + /** + * Determine whether the given class is a candidate for carrying the specified annotation + * (at type, method or field level). + * @param clazz the class to introspect + * @param annotationType the searchable annotation type + * @return {@code false} if the class is known to have no such annotations at any level; + * {@code true} otherwise. Callers will usually perform full method/field introspection + * if {@code true} is being returned here. + * @since 5.2 + * @see #isCandidateClass(Class, String) + */ + public static boolean isCandidateClass(Class clazz, Class annotationType) { + return isCandidateClass(clazz, annotationType.getName()); + } + + /** + * Determine whether the given class is a candidate for carrying the specified annotation + * (at type, method or field level). + * @param clazz the class to introspect + * @param annotationName the fully-qualified name of the searchable annotation type + * @return {@code false} if the class is known to have no such annotations at any level; + * {@code true} otherwise. Callers will usually perform full method/field introspection + * if {@code true} is being returned here. + * @since 5.2 + */ + public static boolean isCandidateClass(Class clazz, String annotationName) { + return !clazz.getName().startsWith("java"); + } + /** * Get a single {@link Annotation} of {@code annotationType} from the supplied * annotation: either the given annotation itself or a direct meta-annotation diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 4a9195b0d6fb..76bfae4f370f 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 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. @@ -24,6 +24,7 @@ import java.util.Set; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; @@ -137,37 +138,41 @@ public MultiValueMap getAllAnnotationAttributes(String annotatio @Override public boolean hasAnnotatedMethods(String annotationName) { - try { - Method[] methods = getIntrospectedClass().getDeclaredMethods(); - for (Method method : methods) { - if (!method.isBridge() && method.getAnnotations().length > 0 && - AnnotatedElementUtils.isAnnotated(method, annotationName)) { - return true; + if (AnnotationUtils.isCandidateClass(getIntrospectedClass(), annotationName)) { + try { + Method[] methods = getIntrospectedClass().getDeclaredMethods(); + for (Method method : methods) { + if (!method.isBridge() && method.getAnnotations().length > 0 && + AnnotatedElementUtils.isAnnotated(method, annotationName)) { + return true; + } } } - return false; - } - catch (Throwable ex) { - throw new IllegalStateException("Failed to introspect annotated methods on " + getIntrospectedClass(), ex); + catch (Throwable ex) { + throw new IllegalStateException("Failed to introspect annotated methods on " + getIntrospectedClass(), ex); + } } + return false; } @Override public Set getAnnotatedMethods(String annotationName) { - try { - Method[] methods = getIntrospectedClass().getDeclaredMethods(); - Set annotatedMethods = new LinkedHashSet<>(4); - for (Method method : methods) { - if (!method.isBridge() && method.getAnnotations().length > 0 && - AnnotatedElementUtils.isAnnotated(method, annotationName)) { - annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); + Set annotatedMethods = new LinkedHashSet<>(4); + if (AnnotationUtils.isCandidateClass(getIntrospectedClass(), annotationName)) { + try { + Method[] methods = getIntrospectedClass().getDeclaredMethods(); + for (Method method : methods) { + if (!method.isBridge() && method.getAnnotations().length > 0 && + AnnotatedElementUtils.isAnnotated(method, annotationName)) { + annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); + } } } - return annotatedMethods; - } - catch (Throwable ex) { - throw new IllegalStateException("Failed to introspect annotated methods on " + getIntrospectedClass(), ex); + catch (Throwable ex) { + throw new IllegalStateException("Failed to introspect annotated methods on " + getIntrospectedClass(), ex); + } } + return annotatedMethods; } } diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java index 9fc1a4db3759..2d8274bc8852 100644 --- a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java +++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -46,6 +46,7 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.jms.config.JmsListenerConfigUtils; import org.springframework.jms.config.JmsListenerContainerFactory; import org.springframework.jms.config.JmsListenerEndpointRegistrar; @@ -228,7 +229,8 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw } Class targetClass = AopProxyUtils.ultimateTargetClass(bean); - if (!this.nonAnnotatedClasses.contains(targetClass)) { + if (!this.nonAnnotatedClasses.contains(targetClass) && + AnnotationUtils.isCandidateClass(targetClass, JmsListener.class)) { Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup>) method -> { Set listenerMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations( diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java index eea66f6a16d0..094f6ca70d71 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java @@ -23,6 +23,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -54,6 +56,7 @@ import org.springframework.core.BridgeMethodResolver; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.jndi.JndiLocatorDelegate; import org.springframework.jndi.JndiTemplate; import org.springframework.lang.Nullable; @@ -412,12 +415,15 @@ private InjectionMetadata findPersistenceMetadata(String beanName, final Class clazz) { + if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(PersistenceContext.class, PersistenceUnit.class))) { + return new InjectionMetadata(clazz, Collections.emptyList()); + } + List elements = new ArrayList<>(); Class targetClass = clazz; do { - final LinkedList currElements = - new LinkedList<>(); + final LinkedList currElements = new LinkedList<>(); ReflectionUtils.doWithLocalFields(targetClass, field -> { if (field.isAnnotationPresent(PersistenceContext.class) || diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java index 4761dd510b4a..3dcd5713bc89 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -137,6 +137,16 @@ public AnnotationTransactionAttributeSource(Set ann } + @Override + public boolean isCandidateClass(Class targetClass) { + for (TransactionAnnotationParser parser : this.annotationParsers) { + if (parser.isCandidateClass(targetClass)) { + return true; + } + } + return false; + } + @Override @Nullable protected TransactionAttribute findTransactionAttribute(Class clazz) { @@ -161,8 +171,8 @@ protected TransactionAttribute findTransactionAttribute(Method method) { */ @Nullable protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) { - for (TransactionAnnotationParser annotationParser : this.annotationParsers) { - TransactionAttribute attr = annotationParser.parseTransactionAnnotation(element); + for (TransactionAnnotationParser parser : this.annotationParsers) { + TransactionAttribute attr = parser.parseTransactionAnnotation(element); if (attr != null) { return attr; } diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java index 57a3177f1838..0567411def28 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -21,6 +21,7 @@ import javax.ejb.ApplicationException; import javax.ejb.TransactionAttributeType; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; import org.springframework.transaction.interceptor.TransactionAttribute; @@ -35,6 +36,11 @@ @SuppressWarnings("serial") public class Ejb3TransactionAnnotationParser implements TransactionAnnotationParser, Serializable { + @Override + public boolean isCandidateClass(Class targetClass) { + return AnnotationUtils.isCandidateClass(targetClass, javax.ejb.TransactionAttribute.class); + } + @Override @Nullable public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) { diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java index df4dcf90bc2c..fef233b50824 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -39,6 +39,11 @@ @SuppressWarnings("serial") public class JtaTransactionAnnotationParser implements TransactionAnnotationParser, Serializable { + @Override + public boolean isCandidateClass(Class targetClass) { + return AnnotationUtils.isCandidateClass(targetClass, javax.transaction.Transactional.class); + } + @Override @Nullable public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) { diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java index 4c2bbce77906..31c3a7d44c2a 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -39,6 +39,11 @@ @SuppressWarnings("serial") public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable { + @Override + public boolean isCandidateClass(Class targetClass) { + return AnnotationUtils.isCandidateClass(targetClass, Transactional.class); + } + @Override @Nullable public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) { diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionAnnotationParser.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionAnnotationParser.java index b41b6bce6c5f..323fd57f9599 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionAnnotationParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -37,6 +37,24 @@ */ public interface TransactionAnnotationParser { + /** + * Determine whether the given class is a candidate for transaction attributes + * in the annotation format of this {@code TransactionAnnotationParser}. + *

If this method returns {@code false}, the methods on the given class + * will not get traversed for {@code #parseTransactionAnnotation} introspection. + * Returning {@code false} is therefore an optimization for non-affected + * classes, whereas {@code true} simply means that the class needs to get + * fully introspected for each method on the given class individually. + * @param targetClass the class to introspect + * @return {@code false} if the class is known to have no transaction + * annotations at class or method level; {@code true} otherwise. The default + * implementation returns {@code true}, leading to regular introspection. + * @since 5.2 + */ + default boolean isCandidateClass(Class targetClass) { + return true; + } + /** * Parse the transaction attribute for the given method or class, * based on an annotation type understood by this parser. diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java index 521b2eca00b6..a0b6436c3224 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/CompositeTransactionAttributeSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2019 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. @@ -39,7 +39,7 @@ public class CompositeTransactionAttributeSource implements TransactionAttribute * Create a new CompositeTransactionAttributeSource for the given sources. * @param transactionAttributeSources the TransactionAttributeSource instances to combine */ - public CompositeTransactionAttributeSource(TransactionAttributeSource[] transactionAttributeSources) { + public CompositeTransactionAttributeSource(TransactionAttributeSource... transactionAttributeSources) { Assert.notNull(transactionAttributeSources, "TransactionAttributeSource array must not be null"); this.transactionAttributeSources = transactionAttributeSources; } @@ -53,13 +53,23 @@ public final TransactionAttributeSource[] getTransactionAttributeSources() { } + @Override + public boolean isCandidateClass(Class targetClass) { + for (TransactionAttributeSource source : this.transactionAttributeSources) { + if (source.isCandidateClass(targetClass)) { + return true; + } + } + return false; + } + @Override @Nullable public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass) { - for (TransactionAttributeSource tas : this.transactionAttributeSources) { - TransactionAttribute ta = tas.getTransactionAttribute(method, targetClass); - if (ta != null) { - return ta; + for (TransactionAttributeSource source : this.transactionAttributeSources) { + TransactionAttribute attr = source.getTransactionAttribute(method, targetClass); + if (attr != null) { + return attr; } } return null; diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSource.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSource.java index 3b384ab6e364..2d1c97ad27b3 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSource.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSource.java @@ -34,14 +34,31 @@ */ public interface TransactionAttributeSource { + /** + * Determine whether the given class is a candidate for transaction attributes + * in the metadata format of this {@code TransactionAttributeSource}. + *

If this method returns {@code false}, the methods on the given class + * will not get traversed for {@link #getTransactionAttribute} introspection. + * Returning {@code false} is therefore an optimization for non-affected + * classes, whereas {@code true} simply means that the class needs to get + * fully introspected for each method on the given class individually. + * @param targetClass the class to introspect + * @return {@code false} if the class is known to have no transaction + * attributes at class or method level; {@code true} otherwise. The default + * implementation returns {@code true}, leading to regular introspection. + * @since 5.2 + */ + default boolean isCandidateClass(Class targetClass) { + return true; + } + /** * Return the transaction attribute for the given method, * or {@code null} if the method is non-transactional. * @param method the method to introspect * @param targetClass the target class (may be {@code null}, * in which case the declaring class of the method must be used) - * @return the TransactionAttribute the matching transaction attribute, - * or {@code null} if none found + * @return the matching transaction attribute, or {@code null} if none found */ @Nullable TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass); diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java index 4e2e20706bfc..af66291352d0 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -19,6 +19,7 @@ import java.io.Serializable; import java.lang.reflect.Method; +import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcut; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.lang.Nullable; @@ -35,13 +36,13 @@ @SuppressWarnings("serial") abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { + protected TransactionAttributeSourcePointcut() { + setClassFilter(new TransactionAttributeSourceClassFilter()); + } + + @Override public boolean matches(Method method, Class targetClass) { - if (TransactionalProxy.class.isAssignableFrom(targetClass) || - PlatformTransactionManager.class.isAssignableFrom(targetClass) || - PersistenceExceptionTranslator.class.isAssignableFrom(targetClass)) { - return false; - } TransactionAttributeSource tas = getTransactionAttributeSource(); return (tas == null || tas.getTransactionAttribute(method, targetClass) != null); } @@ -76,4 +77,23 @@ public String toString() { @Nullable protected abstract TransactionAttributeSource getTransactionAttributeSource(); + + /** + * {@link ClassFilter} that delegates to {@link TransactionAttributeSource#isCandidateClass} + * for filtering classes whose methods are not worth searching to begin with. + */ + private class TransactionAttributeSourceClassFilter implements ClassFilter { + + @Override + public boolean matches(Class clazz) { + if (TransactionalProxy.class.isAssignableFrom(clazz) || + PlatformTransactionManager.class.isAssignableFrom(clazz) || + PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) { + return false; + } + TransactionAttributeSource tas = getTransactionAttributeSource(); + return (tas == null || tas.isCandidateClass(clazz)); + } + } + }