Skip to content

Commit

Permalink
Bypass method traversal for annotation introspection if possible
Browse files Browse the repository at this point in the history
The isCandidateClass mechanism is consistently used for a bypass check before method traversal attempts. While by default this is only bypassing standard java types, the same mechanism can be used with index metadata which indicates non-presence of certain annotations.

See gh-22420
  • Loading branch information
jhoeller committed Mar 11, 2019
1 parent 6266370 commit e3a9826
Show file tree
Hide file tree
Showing 25 changed files with 400 additions and 88 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;

Expand Down Expand Up @@ -96,7 +97,7 @@ public AnnotationMatchingPointcut(@Nullable Class<? extends Annotation> classAnn
this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited);
}
else {
this.classFilter = ClassFilter.TRUE;
this.classFilter = new AnnotationCandidateClassFilter(methodAnnotationType);
}

if (methodAnnotationType != null) {
Expand Down Expand Up @@ -164,4 +165,23 @@ public static AnnotationMatchingPointcut forMethodAnnotation(Class<? extends Ann
return new AnnotationMatchingPointcut(null, annotationType);
}


/**
* {@link ClassFilter} that delegates to {@link AnnotationUtils#isCandidateClass}
* for filtering classes whose methods are not worth searching to begin with.
*/
private static class AnnotationCandidateClassFilter implements ClassFilter {

private final Class<? extends Annotation> annotationType;

public AnnotationCandidateClassFilter(Class<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}

@Override
public boolean matches(Class<?> clazz) {
return AnnotationUtils.isCandidateClass(clazz, this.annotationType);
}
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<LifecycleElement> initMethods = new ArrayList<>();
List<LifecycleElement> destroyMethods = new ArrayList<>();
Class<?> targetClass = clazz;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -103,6 +103,16 @@ public AnnotationCacheOperationSource(Set<CacheAnnotationParser> annotationParse
}


@Override
public boolean isCandidateClass(Class<?> targetClass) {
for (CacheAnnotationParser parser : this.annotationParsers) {
if (parser.isCandidateClass(targetClass)) {
return true;
}
}
return false;
}

@Override
@Nullable
protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
Expand All @@ -127,8 +137,8 @@ protected Collection<CacheOperation> findCacheOperations(Method method) {
@Nullable
protected Collection<CacheOperation> determineCacheOperations(CacheOperationProvider provider) {
Collection<CacheOperation> ops = null;
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
Collection<CacheOperation> annOps = provider.getCacheOperations(annotationParser);
for (CacheAnnotationParser parser : this.annotationParsers) {
Collection<CacheOperation> annOps = provider.getCacheOperations(parser);
if (annOps != null) {
if (ops == null) {
ops = annOps;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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}.
* <p>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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<CacheOperation> parseCacheAnnotations(Class<?> type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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}.
* <p>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 <em>cacheable</em> annotations.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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)));
}
Expand Down Expand Up @@ -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));
}
}

}
Loading

0 comments on commit e3a9826

Please sign in to comment.