From fc16b2d3fb32660f2a1217330083e26fedb70d34 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 11 Aug 2018 01:20:17 +0200 Subject: [PATCH] AbstractHandlerMethodMapping allows for customized bean retrieval Issue: SPR-15535 --- .../beans/factory/BeanFactoryUtils.java | 67 ++++++++----- .../handler/AbstractHandlerMethodMapping.java | 94 +++++++++++-------- 2 files changed, 98 insertions(+), 63 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java index 8ed7394a0e..9d8ff98cdd 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java @@ -119,6 +119,8 @@ public abstract class BeanFactoryUtils { } + // Retrieval of bean names + /** * Count all beans in any hierarchy in which this factory participates. * Includes counts of ancestor bean factories. @@ -126,6 +128,7 @@ public abstract class BeanFactoryUtils { * with the same name) are only counted once. * @param lbf the bean factory * @return count of beans including those defined in ancestor factories + * @see #beanNamesIncludingAncestors */ public static int countBeansIncludingAncestors(ListableBeanFactory lbf) { return beanNamesIncludingAncestors(lbf).length; @@ -153,6 +156,7 @@ public abstract class BeanFactoryUtils { * @param type the type that beans must match (as a {@code ResolvableType}) * @return the array of matching bean names, or an empty array if none * @since 4.2 + * @see ListableBeanFactory#getBeanNamesForType(ResolvableType) */ public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, ResolvableType type) { Assert.notNull(lbf, "ListableBeanFactory must not be null"); @@ -179,6 +183,7 @@ public abstract class BeanFactoryUtils { * @param lbf the bean factory * @param type the type that beans must match (as a {@code Class}) * @return the array of matching bean names, or an empty array if none + * @see ListableBeanFactory#getBeanNamesForType(Class) */ public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class type) { Assert.notNull(lbf, "ListableBeanFactory must not be null"); @@ -213,6 +218,7 @@ public abstract class BeanFactoryUtils { * for this flag will initialize FactoryBeans and "factory-bean" references. * @param type the type that beans must match * @return the array of matching bean names, or an empty array if none + * @see ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean) */ public static String[] beanNamesForTypeIncludingAncestors( ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) { @@ -230,6 +236,35 @@ public abstract class BeanFactoryUtils { return result; } + /** + * Get all bean names whose {@code Class} has the supplied {@link Annotation} + * type, including those defined in ancestor factories, without creating any bean + * instances yet. Will return unique names in case of overridden bean definitions. + * @param lbf the bean factory + * @param annotationType the type of annotation to look for + * @return the array of matching bean names, or an empty array if none + * @since 5.0 + * @see ListableBeanFactory#getBeanNamesForAnnotation(Class) + */ + public static String[] beanNamesForAnnotationIncludingAncestors( + ListableBeanFactory lbf, Class annotationType) { + + Assert.notNull(lbf, "ListableBeanFactory must not be null"); + String[] result = lbf.getBeanNamesForAnnotation(annotationType); + if (lbf instanceof HierarchicalBeanFactory) { + HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf; + if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { + String[] parentResult = beanNamesForAnnotationIncludingAncestors( + (ListableBeanFactory) hbf.getParentBeanFactory(), annotationType); + result = mergeNamesWithParent(result, parentResult, hbf); + } + } + return result; + } + + + // Retrieval of bean instances + /** * Return all beans of the given type or subtypes, also picking up beans defined in * ancestor bean factories if the current bean factory is a HierarchicalBeanFactory. @@ -246,6 +281,7 @@ public abstract class BeanFactoryUtils { * @param type type of bean to match * @return the Map of matching bean instances, or an empty Map if none * @throws BeansException if a bean could not be created + * @see ListableBeanFactory#getBeansOfType(Class) */ public static Map beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class type) throws BeansException { @@ -293,6 +329,7 @@ public abstract class BeanFactoryUtils { * for this flag will initialize FactoryBeans and "factory-bean" references. * @return the Map of matching bean instances, or an empty Map if none * @throws BeansException if a bean could not be created + * @see ListableBeanFactory#getBeansOfType(Class, boolean, boolean) */ public static Map beansOfTypeIncludingAncestors( ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) @@ -316,7 +353,6 @@ public abstract class BeanFactoryUtils { return result; } - /** * Return a single bean of the given type or subtypes, also picking up beans * defined in ancestor bean factories if the current bean factory is a @@ -338,6 +374,7 @@ public abstract class BeanFactoryUtils { * @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws BeansException if the bean could not be created + * @see #beansOfTypeIncludingAncestors(ListableBeanFactory, Class) */ public static T beanOfTypeIncludingAncestors(ListableBeanFactory lbf, Class type) throws BeansException { @@ -346,31 +383,6 @@ public abstract class BeanFactoryUtils { return uniqueBean(type, beansOfType); } - /** - * Get all bean names whose {@code Class} has the supplied {@link Annotation} - * type, including those defined in ancestor factories, without creating any bean - * instances yet. Will return unique names in case of overridden bean definitions. - * @param lbf the bean factory - * @param annotationType the type of annotation to look for - * @return the array of matching bean names, or an empty array if none - * @since 5.0 - */ - public static String[] beanNamesForAnnotationIncludingAncestors( - ListableBeanFactory lbf, Class annotationType) { - - Assert.notNull(lbf, "ListableBeanFactory must not be null"); - String[] result = lbf.getBeanNamesForAnnotation(annotationType); - if (lbf instanceof HierarchicalBeanFactory) { - HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf; - if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) { - String[] parentResult = beanNamesForAnnotationIncludingAncestors( - (ListableBeanFactory) hbf.getParentBeanFactory(), annotationType); - result = mergeNamesWithParent(result, parentResult, hbf); - } - } - return result; - } - /** * Return a single bean of the given type or subtypes, also picking up beans * defined in ancestor bean factories if the current bean factory is a @@ -399,6 +411,7 @@ public abstract class BeanFactoryUtils { * @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws BeansException if the bean could not be created + * @see #beansOfTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean) */ public static T beanOfTypeIncludingAncestors( ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) @@ -423,6 +436,7 @@ public abstract class BeanFactoryUtils { * @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws BeansException if the bean could not be created + * @see ListableBeanFactory#getBeansOfType(Class) */ public static T beanOfType(ListableBeanFactory lbf, Class type) throws BeansException { Assert.notNull(lbf, "ListableBeanFactory must not be null"); @@ -453,6 +467,7 @@ public abstract class BeanFactoryUtils { * @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws BeansException if the bean could not be created + * @see ListableBeanFactory#getBeansOfType(Class, boolean, boolean) */ public static T beanOfType( ListableBeanFactory lbf, Class type, boolean includeNonSingletons, boolean allowEagerInit) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index 82f09755d3..d176a984a7 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -101,6 +101,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap * is defined in (typically the current DispatcherServlet's context). *

Switch this flag on to detect handler beans in ancestor contexts * (typically the Spring root WebApplicationContext) as well. + * @see #getCandidateBeanNames() */ public void setDetectHandlerMethodsInAncestorContexts(boolean detectHandlerMethodsInAncestorContexts) { this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts; @@ -178,7 +179,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap */ public void unregisterMapping(T mapping) { if (logger.isTraceEnabled()) { - logger.trace("Unregister mapping \"" + mapping); + logger.trace("Unregister mapping \"" + mapping + "\""); } this.mappingRegistry.unregister(mapping); } @@ -188,62 +189,78 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap /** * Detects handler methods at initialization. + * @see #initHandlerMethods */ @Override public void afterPropertiesSet() { - initHandlerMethods(); - - // Total includes detected mappings + explicit registrations via registerMapping.. - int total = this.getHandlerMethods().size(); - - if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) { - logger.debug(total + " mappings in " + formatMappingName()); - } } /** * Scan beans in the ApplicationContext, detect and register handler methods. - * @see #isHandler(Class) - * @see #getMappingForMethod(Method, Class) - * @see #handlerMethodsInitialized(Map) + * @see #getCandidateBeanNames() + * @see #processCandidateBean + * @see #handlerMethodsInitialized */ protected void initHandlerMethods() { - - String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? - BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : - obtainApplicationContext().getBeanNamesForType(Object.class)); - - for (String beanName : beanNames) { + for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { - Class beanType = null; - try { - beanType = obtainApplicationContext().getType(beanName); - } - catch (Throwable ex) { - // An unresolvable bean type, probably from a lazy bean - let's ignore it. - if (logger.isTraceEnabled()) { - logger.trace("Could not resolve type for bean '" + beanName + "'", ex); - } - } - if (beanType != null && isHandler(beanType)) { - detectHandlerMethods(beanName); - } + processCandidateBean(beanName); } } handlerMethodsInitialized(getHandlerMethods()); } /** - * Look for handler methods in a handler. - * @param handler the bean name of a handler or a handler instance + * Determine the names of candidate beans in the application context. + * @since 5.1 + * @see #setDetectHandlerMethodsInAncestorContexts + * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors */ - protected void detectHandlerMethods(final Object handler) { + protected String[] getCandidateBeanNames() { + return (this.detectHandlerMethodsInAncestorContexts ? + BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : + obtainApplicationContext().getBeanNamesForType(Object.class)); + } + + /** + * Determine the type of the specified candidate bean and call + * {@link #detectHandlerMethods} if identified as a handler type. + *

This implementation avoids bean creation through checking + * {@link org.springframework.beans.factory.BeanFactory#getType} + * and calling {@link #detectHandlerMethods} with the bean name. + * @param beanName the name of the candidate bean + * @since 5.1 + * @see #isHandler + * @see #detectHandlerMethods + */ + protected void processCandidateBean(String beanName) { + Class beanType = null; + try { + beanType = obtainApplicationContext().getType(beanName); + } + catch (Throwable ex) { + // An unresolvable bean type, probably from a lazy bean - let's ignore it. + if (logger.isTraceEnabled()) { + logger.trace("Could not resolve type for bean '" + beanName + "'", ex); + } + } + if (beanType != null && isHandler(beanType)) { + detectHandlerMethods(beanName); + } + } + + /** + * Look for handler methods in the specified handler bean. + * @param handler either a bean name or an actual handler instance + * @see #getMappingForMethod + */ + protected void detectHandlerMethods(Object handler) { Class handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { - final Class userType = ClassUtils.getUserClass(handlerType); + Class userType = ClassUtils.getUserClass(handlerType); Map methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup) method -> { try { @@ -309,6 +326,11 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap * @param handlerMethods a read-only map with handler methods and mappings. */ protected void handlerMethodsInitialized(Map handlerMethods) { + // Total includes detected mappings + explicit registrations via registerMapping + int total = handlerMethods.size(); + if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) { + logger.debug(total + " mappings in " + formatMappingName()); + } } @@ -476,7 +498,6 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap /** * A registry that maintains all mappings to handler methods, exposing methods * to perform lookups and providing concurrent access. - * *

Package-private for testing purposes. */ class MappingRegistry { @@ -544,7 +565,6 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap try { HandlerMethod handlerMethod = createHandlerMethod(handler, method); assertUniqueMethodMapping(handlerMethod, mapping); - this.mappingLookup.put(mapping, handlerMethod); List directUrls = getDirectUrls(mapping);