diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index c506958cf2..512bbec6b3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -87,7 +87,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { private transient Method destroyMethod; @Nullable - private List beanPostProcessors; + private final List beanPostProcessors; /** @@ -120,14 +120,16 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { } } else { - Class[] paramTypes = destroyMethod.getParameterTypes(); - if (paramTypes.length > 1) { - throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + - beanName + "' has more than one parameter - not supported as destroy method"); - } - else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) { - throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + - beanName + "' has a non-boolean parameter - not supported as destroy method"); + if (destroyMethod.getParameterCount() > 0) { + Class[] paramTypes = destroyMethod.getParameterTypes(); + if (paramTypes.length > 1) { + throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + + beanName + "' has more than one parameter - not supported as destroy method"); + } + else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) { + throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + + beanName + "' has a non-boolean parameter - not supported as destroy method"); + } } destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod); } @@ -169,67 +171,6 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { } - /** - * If the current value of the given beanDefinition's "destroyMethodName" property is - * {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method. - * Candidate methods are currently limited to public, no-arg methods named "close" or - * "shutdown" (whether declared locally or inherited). The given BeanDefinition's - * "destroyMethodName" is updated to be null if no such method is found, otherwise set - * to the name of the inferred method. This constant serves as the default for the - * {@code @Bean#destroyMethod} attribute and the value of the constant may also be - * used in XML within the {@code } or {@code - * } attributes. - *

Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable} - * interfaces, reflectively calling the "close" method on implementing beans as well. - */ - @Nullable - private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) { - String destroyMethodName = beanDefinition.getDestroyMethodName(); - if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) || - (destroyMethodName == null && bean instanceof AutoCloseable)) { - // Only perform destroy method inference or Closeable detection - // in case of the bean not explicitly implementing DisposableBean - if (!(bean instanceof DisposableBean)) { - try { - return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName(); - } - catch (NoSuchMethodException ex) { - try { - return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName(); - } - catch (NoSuchMethodException ex2) { - // no candidate destroy method found - } - } - } - return null; - } - return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null); - } - - /** - * Search for all DestructionAwareBeanPostProcessors in the List. - * @param processors the List to search - * @return the filtered List of DestructionAwareBeanPostProcessors - */ - @Nullable - private List filterPostProcessors(List processors, Object bean) { - List filteredPostProcessors = null; - if (!CollectionUtils.isEmpty(processors)) { - filteredPostProcessors = new ArrayList<>(processors.size()); - for (BeanPostProcessor processor : processors) { - if (processor instanceof DestructionAwareBeanPostProcessor) { - DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor; - if (dabpp.requiresDestruction(bean)) { - filteredPostProcessors.add(dabpp); - } - } - } - } - return filteredPostProcessors; - } - - @Override public void run() { destroy(); @@ -384,12 +325,50 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { if (bean instanceof DisposableBean || bean instanceof AutoCloseable) { return true; } - String destroyMethodName = beanDefinition.getDestroyMethodName(); - if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) { - return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) || - ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME)); + return inferDestroyMethodIfNecessary(bean, beanDefinition) != null; + } + + + /** + * If the current value of the given beanDefinition's "destroyMethodName" property is + * {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method. + * Candidate methods are currently limited to public, no-arg methods named "close" or + * "shutdown" (whether declared locally or inherited). The given BeanDefinition's + * "destroyMethodName" is updated to be null if no such method is found, otherwise set + * to the name of the inferred method. This constant serves as the default for the + * {@code @Bean#destroyMethod} attribute and the value of the constant may also be + * used in XML within the {@code } or {@code + * } attributes. + *

Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable} + * interfaces, reflectively calling the "close" method on implementing beans as well. + */ + @Nullable + private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) { + String destroyMethodName = beanDefinition.resolvedDestroyMethodName; + if (destroyMethodName == null) { + destroyMethodName = beanDefinition.getDestroyMethodName(); + if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) || + (destroyMethodName == null && bean instanceof AutoCloseable)) { + // Only perform destroy method inference or Closeable detection + // in case of the bean not explicitly implementing DisposableBean + destroyMethodName = null; + if (!(bean instanceof DisposableBean)) { + try { + destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName(); + } + catch (NoSuchMethodException ex) { + try { + destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName(); + } + catch (NoSuchMethodException ex2) { + // no candidate destroy method found + } + } + } + } + beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : ""); } - return StringUtils.hasLength(destroyMethodName); + return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null); } /** @@ -411,4 +390,26 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { return false; } + /** + * Search for all DestructionAwareBeanPostProcessors in the List. + * @param processors the List to search + * @return the filtered List of DestructionAwareBeanPostProcessors + */ + @Nullable + private List filterPostProcessors(List processors, Object bean) { + List filteredPostProcessors = null; + if (!CollectionUtils.isEmpty(processors)) { + filteredPostProcessors = new ArrayList<>(processors.size()); + for (BeanPostProcessor processor : processors) { + if (processor instanceof DestructionAwareBeanPostProcessor) { + DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor; + if (dabpp.requiresDestruction(bean)) { + filteredPostProcessors.add(dabpp); + } + } + } + } + return filteredPostProcessors; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index 8422ad309e..8638734992 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -65,7 +65,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { boolean allowCaching = true; - boolean isFactoryMethodUnique = false; + boolean isFactoryMethodUnique; @Nullable volatile ResolvableType targetType; @@ -86,6 +86,10 @@ public class RootBeanDefinition extends AbstractBeanDefinition { @Nullable volatile Method factoryMethodToIntrospect; + /** Package-visible field for caching a resolved destroy method name (also for inferred). */ + @Nullable + volatile String resolvedDestroyMethodName; + /** Common lock for the four constructor fields below. */ final Object constructorArgumentLock = new Object();