Merge branch '6.0.x'

This commit is contained in:
Juergen Hoeller 2023-07-09 16:56:45 +02:00
commit 71bb45c87b
9 changed files with 347 additions and 110 deletions

View File

@ -107,11 +107,9 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
protected transient Log logger = LogFactory.getLog(getClass()); protected transient Log logger = LogFactory.getLog(getClass());
@Nullable private final Set<Class<? extends Annotation>> initAnnotationTypes = new LinkedHashSet<>(2);
private Class<? extends Annotation> initAnnotationType;
@Nullable private final Set<Class<? extends Annotation>> destroyAnnotationTypes = new LinkedHashSet<>(2);
private Class<? extends Annotation> destroyAnnotationType;
private int order = Ordered.LOWEST_PRECEDENCE; private int order = Ordered.LOWEST_PRECEDENCE;
@ -125,9 +123,23 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
* <p>Any custom annotation can be used, since there are no required * <p>Any custom annotation can be used, since there are no required
* annotation attributes. There is no default, although a typical choice * annotation attributes. There is no default, although a typical choice
* is the {@link jakarta.annotation.PostConstruct} annotation. * is the {@link jakarta.annotation.PostConstruct} annotation.
* @see #addInitAnnotationType
*/ */
public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) { public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) {
this.initAnnotationType = initAnnotationType; this.initAnnotationTypes.clear();
this.initAnnotationTypes.add(initAnnotationType);
}
/**
* Add an init annotation to check for, indicating initialization
* methods to call after configuration of a bean.
* @since 6.0.11
* @see #setInitAnnotationType
*/
public void addInitAnnotationType(@Nullable Class<? extends Annotation> initAnnotationType) {
if (initAnnotationType != null) {
this.initAnnotationTypes.add(initAnnotationType);
}
} }
/** /**
@ -136,9 +148,23 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
* <p>Any custom annotation can be used, since there are no required * <p>Any custom annotation can be used, since there are no required
* annotation attributes. There is no default, although a typical choice * annotation attributes. There is no default, although a typical choice
* is the {@link jakarta.annotation.PreDestroy} annotation. * is the {@link jakarta.annotation.PreDestroy} annotation.
* @see #addDestroyAnnotationType
*/ */
public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) { public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) {
this.destroyAnnotationType = destroyAnnotationType; this.destroyAnnotationTypes.clear();
this.destroyAnnotationTypes.add(destroyAnnotationType);
}
/**
* Add a destroy annotation to check for, indicating destruction
* methods to call when the context is shutting down.
* @since 6.0.11
* @see #setDestroyAnnotationType
*/
public void addDestroyAnnotationType(@Nullable Class<? extends Annotation> destroyAnnotationType) {
if (destroyAnnotationType != null) {
this.destroyAnnotationTypes.add(destroyAnnotationType);
}
} }
public void setOrder(int order) { public void setOrder(int order) {
@ -255,7 +281,8 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
} }
private LifecycleMetadata buildLifecycleMetadata(final Class<?> beanClass) { private LifecycleMetadata buildLifecycleMetadata(final Class<?> beanClass) {
if (!AnnotationUtils.isCandidateClass(beanClass, List.of(this.initAnnotationType, this.destroyAnnotationType))) { if (!AnnotationUtils.isCandidateClass(beanClass, this.initAnnotationTypes) &&
!AnnotationUtils.isCandidateClass(beanClass, this.destroyAnnotationTypes)) {
return this.emptyLifecycleMetadata; return this.emptyLifecycleMetadata;
} }
@ -268,16 +295,20 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
final List<LifecycleMethod> currDestroyMethods = new ArrayList<>(); final List<LifecycleMethod> currDestroyMethods = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(currentClass, method -> { ReflectionUtils.doWithLocalMethods(currentClass, method -> {
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) { for (Class<? extends Annotation> initAnnotationType : this.initAnnotationTypes) {
currInitMethods.add(new LifecycleMethod(method, beanClass)); if (initAnnotationType != null && method.isAnnotationPresent(initAnnotationType)) {
if (logger.isTraceEnabled()) { currInitMethods.add(new LifecycleMethod(method, beanClass));
logger.trace("Found init method on class [" + beanClass.getName() + "]: " + method); if (logger.isTraceEnabled()) {
logger.trace("Found init method on class [" + beanClass.getName() + "]: " + method);
}
} }
} }
if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) { for (Class<? extends Annotation> destroyAnnotationType : this.destroyAnnotationTypes) {
currDestroyMethods.add(new LifecycleMethod(method, beanClass)); if (destroyAnnotationType != null && method.isAnnotationPresent(destroyAnnotationType)) {
if (logger.isTraceEnabled()) { currDestroyMethods.add(new LifecycleMethod(method, beanClass));
logger.trace("Found destroy method on class [" + beanClass.getName() + "]: " + method); if (logger.isTraceEnabled()) {
logger.trace("Found destroy method on class [" + beanClass.getName() + "]: " + method);
}
} }
} }
}); });

View File

@ -19,6 +19,7 @@ dependencies {
optional("jakarta.inject:jakarta.inject-api") optional("jakarta.inject:jakarta.inject-api")
optional("jakarta.interceptor:jakarta.interceptor-api") optional("jakarta.interceptor:jakarta.interceptor-api")
optional("jakarta.validation:jakarta.validation-api") optional("jakarta.validation:jakarta.validation-api")
optional("javax.annotation:javax.annotation-api")
optional("javax.money:money-api") optional("javax.money:money-api")
optional("org.aspectj:aspectjweaver") optional("org.aspectj:aspectjweaver")
optional("org.apache.groovy:groovy") optional("org.apache.groovy:groovy")
@ -34,12 +35,11 @@ dependencies {
testImplementation(testFixtures(project(":spring-beans"))) testImplementation(testFixtures(project(":spring-beans")))
testImplementation(testFixtures(project(":spring-core"))) testImplementation(testFixtures(project(":spring-core")))
testImplementation("io.projectreactor:reactor-core") testImplementation("io.projectreactor:reactor-core")
testImplementation("jakarta.inject:jakarta.inject-tck")
testImplementation("org.apache.groovy:groovy-jsr223") testImplementation("org.apache.groovy:groovy-jsr223")
testImplementation("org.apache.groovy:groovy-xml") testImplementation("org.apache.groovy:groovy-xml")
testImplementation("org.apache.commons:commons-pool2") testImplementation("org.apache.commons:commons-pool2")
testImplementation("org.awaitility:awaitility") testImplementation("org.awaitility:awaitility")
testImplementation("jakarta.inject:jakarta.inject-tck")
testImplementation("javax.annotation:javax.annotation-api")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
testImplementation("io.reactivex.rxjava3:rxjava") testImplementation("io.reactivex.rxjava3:rxjava")

View File

@ -23,7 +23,6 @@ import java.util.Set;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@ -174,27 +173,13 @@ public abstract class AnnotationConfigUtils {
} }
// Check for Jakarta Annotations support, and if present add the CommonAnnotationBeanPostProcessor. // Check for Jakarta Annotations support, and if present add the CommonAnnotationBeanPostProcessor.
if (jakartaAnnotationsPresent && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { if ((jakartaAnnotationsPresent || jsr250Present) &&
!registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source); def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
} }
// Check for JSR-250 support, and if present add an InitDestroyAnnotationBeanPostProcessor
// for the javax variant of PostConstruct/PreDestroy.
if (jsr250Present && !registry.containsBeanDefinition(JSR250_ANNOTATION_PROCESSOR_BEAN_NAME)) {
try {
RootBeanDefinition def = new RootBeanDefinition(InitDestroyAnnotationBeanPostProcessor.class);
def.getPropertyValues().add("initAnnotationType", classLoader.loadClass("javax.annotation.PostConstruct"));
def.getPropertyValues().add("destroyAnnotationType", classLoader.loadClass("javax.annotation.PreDestroy"));
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, JSR250_ANNOTATION_PROCESSOR_BEAN_NAME));
}
catch (ClassNotFoundException ex) {
// Failed to load javax variants of the annotation types -> ignore.
}
}
// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor. // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(); RootBeanDefinition def = new RootBeanDefinition();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -33,11 +33,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import jakarta.ejb.EJB;
import org.springframework.aop.TargetSource; import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
@ -85,10 +80,15 @@ import org.springframework.util.StringValueResolver;
* and default names as well. The target beans can be simple POJOs, with no special * and default names as well. The target beans can be simple POJOs, with no special
* requirements other than the type having to match. * requirements other than the type having to match.
* *
* <p>This post-processor also supports the EJB 3 {@link jakarta.ejb.EJB} annotation, * <p>Additionally, the original {@code javax.annotation} variants of the annotations
* dating back to the JSR-250 specification (Java EE 5-8, also included in JDK 6-8)
* are still supported as well. Note that this is primarily for a smooth upgrade path,
* not for adoption in new applications.
*
* <p>This post-processor also supports the EJB {@link jakarta.ejb.EJB} annotation,
* analogous to {@link jakarta.annotation.Resource}, with the capability to * analogous to {@link jakarta.annotation.Resource}, with the capability to
* specify both a local bean name and a global JNDI name for fallback retrieval. * specify both a local bean name and a global JNDI name for fallback retrieval.
* The target beans can be plain POJOs as well as EJB 3 Session Beans in this case. * The target beans can be plain POJOs as well as EJB Session Beans in this case.
* *
* <p>For default usage, resolving resource names as Spring bean names, * <p>For default usage, resolving resource names as Spring bean names,
* simply define the following in your application context: * simply define the following in your application context:
@ -113,8 +113,8 @@ import org.springframework.util.StringValueResolver;
* by the "context:annotation-config" and "context:component-scan" XML tags. * by the "context:annotation-config" and "context:component-scan" XML tags.
* Remove or turn off the default annotation configuration there if you intend * Remove or turn off the default annotation configuration there if you intend
* to specify a custom CommonAnnotationBeanPostProcessor bean definition! * to specify a custom CommonAnnotationBeanPostProcessor bean definition!
* <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection; thus * <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection;
* the latter configuration will override the former for properties wired through * thus the latter configuration will override the former for properties wired through
* both approaches. * both approaches.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
@ -136,14 +136,28 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = new LinkedHashSet<>(4); private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = new LinkedHashSet<>(4);
@Nullable @Nullable
private static final Class<? extends Annotation> ejbClass; private static final Class<? extends Annotation> jakartaResourceType;
@Nullable
private static final Class<? extends Annotation> javaxResourceType;
@Nullable
private static final Class<? extends Annotation> ejbAnnotationType;
static { static {
resourceAnnotationTypes.add(Resource.class); jakartaResourceType = loadAnnotationType("jakarta.annotation.Resource");
if (jakartaResourceType != null) {
resourceAnnotationTypes.add(jakartaResourceType);
}
ejbClass = loadAnnotationType("jakarta.ejb.EJB"); javaxResourceType = loadAnnotationType("javax.annotation.Resource");
if (ejbClass != null) { if (javaxResourceType != null) {
resourceAnnotationTypes.add(ejbClass); resourceAnnotationTypes.add(javaxResourceType);
}
ejbAnnotationType = loadAnnotationType("jakarta.ejb.EJB");
if (ejbAnnotationType != null) {
resourceAnnotationTypes.add(ejbAnnotationType);
} }
} }
@ -177,8 +191,14 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
*/ */
public CommonAnnotationBeanPostProcessor() { public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3); setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class); // Jakarta EE 9 set of annotations in jakarta.annotation package
addInitAnnotationType(loadAnnotationType("jakarta.annotation.PostConstruct"));
addDestroyAnnotationType(loadAnnotationType("jakarta.annotation.PreDestroy"));
// Tolerate legacy JSR-250 annotations in javax.annotation package
addInitAnnotationType(loadAnnotationType("javax.annotation.PostConstruct"));
addDestroyAnnotationType(loadAnnotationType("javax.annotation.PreDestroy"));
// java.naming module present on JDK 9+? // java.naming module present on JDK 9+?
if (jndiPresent) { if (jndiPresent) {
@ -338,13 +358,13 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>(); final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> { ReflectionUtils.doWithLocalFields(targetClass, field -> {
if (ejbClass != null && field.isAnnotationPresent(ejbClass)) { if (ejbAnnotationType != null && field.isAnnotationPresent(ejbAnnotationType)) {
if (Modifier.isStatic(field.getModifiers())) { if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static fields"); throw new IllegalStateException("@EJB annotation is not supported on static fields");
} }
currElements.add(new EjbRefElement(field, field, null)); currElements.add(new EjbRefElement(field, field, null));
} }
else if (field.isAnnotationPresent(Resource.class)) { else if (jakartaResourceType != null && field.isAnnotationPresent(jakartaResourceType)) {
if (Modifier.isStatic(field.getModifiers())) { if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields"); throw new IllegalStateException("@Resource annotation is not supported on static fields");
} }
@ -352,6 +372,14 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
currElements.add(new ResourceElement(field, field, null)); currElements.add(new ResourceElement(field, field, null));
} }
} }
else if (javaxResourceType != null && field.isAnnotationPresent(javaxResourceType)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
currElements.add(new LegacyResourceElement(field, field, null));
}
}
}); });
ReflectionUtils.doWithLocalMethods(targetClass, method -> { ReflectionUtils.doWithLocalMethods(targetClass, method -> {
@ -360,7 +388,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
return; return;
} }
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) { if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
if (Modifier.isStatic(method.getModifiers())) { if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static methods"); throw new IllegalStateException("@EJB annotation is not supported on static methods");
} }
@ -370,7 +398,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new EjbRefElement(method, bridgedMethod, pd)); currElements.add(new EjbRefElement(method, bridgedMethod, pd));
} }
else if (bridgedMethod.isAnnotationPresent(Resource.class)) { else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
if (Modifier.isStatic(method.getModifiers())) { if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods"); throw new IllegalStateException("@Resource annotation is not supported on static methods");
} }
@ -383,6 +411,19 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
currElements.add(new ResourceElement(method, bridgedMethod, pd)); currElements.add(new ResourceElement(method, bridgedMethod, pd));
} }
} }
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length != 1) {
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new LegacyResourceElement(method, bridgedMethod, pd));
}
}
} }
}); });
@ -584,7 +625,55 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) { public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd); super(member, pd);
Resource resource = ae.getAnnotation(Resource.class); jakarta.annotation.Resource resource = ae.getAnnotation(jakarta.annotation.Resource.class);
String resourceName = resource.name();
Class<?> resourceType = resource.type();
this.isDefaultName = !StringUtils.hasLength(resourceName);
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3));
}
}
else if (embeddedValueResolver != null) {
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
if (Object.class != resourceType) {
checkResourceType(resourceType);
}
else {
// No resource type specified... check field/method.
resourceType = getResourceType();
}
this.name = (resourceName != null ? resourceName : "");
this.lookupType = resourceType;
String lookupValue = resource.lookup();
this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}
@Override
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
getResource(this, requestingBeanName));
}
}
/**
* Class representing injection information about an annotated field
* or setter method, supporting the @Resource annotation.
*/
private class LegacyResourceElement extends LookupElement {
private final boolean lazyLookup;
public LegacyResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
javax.annotation.Resource resource = ae.getAnnotation(javax.annotation.Resource.class);
String resourceName = resource.name(); String resourceName = resource.name();
Class<?> resourceType = resource.type(); Class<?> resourceType = resource.type();
this.isDefaultName = !StringUtils.hasLength(resourceName); this.isDefaultName = !StringUtils.hasLength(resourceName);
@ -630,7 +719,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
public EjbRefElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) { public EjbRefElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd); super(member, pd);
EJB resource = ae.getAnnotation(EJB.class); jakarta.ejb.EJB resource = ae.getAnnotation(jakarta.ejb.EJB.class);
String resourceBeanName = resource.beanName(); String resourceBeanName = resource.beanName();
String resourceName = resource.name(); String resourceName = resource.name();
this.isDefaultName = !StringUtils.hasLength(resourceName); this.isDefaultName = !StringUtils.hasLength(resourceName);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -207,10 +207,9 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false); scanner.setIncludeAnnotationConfig(false);
scanner.scan("org.springframework.context.annotation3"); scanner.scan("org.springframework.context.annotation3");
assertThatIllegalStateException().isThrownBy(() -> assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE))
scanner.scan(BASE_PACKAGE)) .withMessageContaining("stubFooDao")
.withMessageContaining("stubFooDao") .withMessageContaining(StubFooDao.class.getName());
.withMessageContaining(StubFooDao.class.getName());
} }
@Test @Test
@ -267,11 +266,10 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false); scanner.setIncludeAnnotationConfig(false);
scanner.scan("org.springframework.context.annotation2"); scanner.scan("org.springframework.context.annotation2");
assertThatIllegalStateException().isThrownBy(() -> assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE))
scanner.scan(BASE_PACKAGE)) .withMessageContaining("myNamedDao")
.withMessageContaining("myNamedDao") .withMessageContaining(NamedStubDao.class.getName())
.withMessageContaining(NamedStubDao.class.getName()) .withMessageContaining(NamedStubDao2.class.getName());
.withMessageContaining(NamedStubDao2.class.getName());
} }
@Test @Test

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -85,13 +85,33 @@ class ClassPathScanningCandidateComponentProviderTests {
ClassPathScanningCandidateComponentProviderTests.class.getClassLoader(), ClassPathScanningCandidateComponentProviderTests.class.getClassLoader(),
new ClassPathResource("spring.components", NamedComponent.class)); new ClassPathResource("spring.components", NamedComponent.class));
private static final Set<Class<?>> springComponents = Set.of(
DefaultNamedComponent.class,
NamedComponent.class,
FooServiceImpl.class,
StubFooDao.class,
NamedStubDao.class,
ServiceInvocationCounter.class,
BarComponent.class
);
private static final Set<Class<?>> scannedJakartaComponents = Set.of(
JakartaNamedComponent.class,
JakartaManagedBeanComponent.class
);
private static final Set<Class<?>> indexedJakartaComponents = Set.of(
IndexedJakartaNamedComponent.class,
IndexedJakartaManagedBeanComponent.class
);
@Test @Test
void defaultsWithScan() { void defaultsWithScan() {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
provider.setResourceLoader(new DefaultResourceLoader( provider.setResourceLoader(new DefaultResourceLoader(
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader()))); CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader())));
testDefault(provider, true, false); testDefault(provider, TEST_BASE_PACKAGE, true, false);
} }
@Test @Test
@ -101,32 +121,9 @@ class ClassPathScanningCandidateComponentProviderTests {
testDefault(provider, "example", true, true); testDefault(provider, "example", true, true);
} }
private static final Set<Class<?>> springComponents = Set.of( private void testDefault(ClassPathScanningCandidateComponentProvider provider, String basePackage,
DefaultNamedComponent.class, boolean includeScannedJakartaComponents, boolean includeIndexedJakartaComponents) {
NamedComponent.class,
FooServiceImpl.class,
StubFooDao.class,
NamedStubDao.class,
ServiceInvocationCounter.class,
BarComponent.class
);
private static final Set<Class<?>> scannedJakartaComponents = Set.of(
JakartaNamedComponent.class,
JakartaManagedBeanComponent.class
);
private static final Set<Class<?>> indexedJakartaComponents = Set.of(
IndexedJakartaNamedComponent.class,
IndexedJakartaManagedBeanComponent.class
);
private void testDefault(ClassPathScanningCandidateComponentProvider provider, boolean includeScannedJakartaComponents, boolean includeIndexedJakartaComponents) {
testDefault(provider, TEST_BASE_PACKAGE, includeScannedJakartaComponents, includeIndexedJakartaComponents);
}
private void testDefault(ClassPathScanningCandidateComponentProvider provider, String basePackage, boolean includeScannedJakartaComponents, boolean includeIndexedJakartaComponents) {
Set<Class<?>> expectedTypes = new HashSet<>(springComponents); Set<Class<?>> expectedTypes = new HashSet<>(springComponents);
if (includeScannedJakartaComponents) { if (includeScannedJakartaComponents) {
expectedTypes.addAll(scannedJakartaComponents); expectedTypes.addAll(scannedJakartaComponents);
@ -205,7 +202,7 @@ class ClassPathScanningCandidateComponentProviderTests {
private void testCustomAnnotationTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) { private void testCustomAnnotationTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) {
provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
testDefault(provider, false, false); testDefault(provider, TEST_BASE_PACKAGE, false, false);
} }
@Test @Test

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -90,6 +90,18 @@ public class CommonAnnotationBeanPostProcessorTests {
assertThat(bean.destroyCalled).isTrue(); assertThat(bean.destroyCalled).isTrue();
} }
@Test
public void testPostConstructAndPreDestroyWithLegacyAnnotations() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor());
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(LegacyAnnotatedInitDestroyBean.class));
LegacyAnnotatedInitDestroyBean bean = (LegacyAnnotatedInitDestroyBean) bf.getBean("annotatedBean");
assertThat(bean.initCalled).isTrue();
bf.destroySingletons();
assertThat(bean.destroyCalled).isTrue();
}
@Test @Test
public void testPostConstructAndPreDestroyWithManualConfiguration() { public void testPostConstructAndPreDestroyWithManualConfiguration() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@ -200,6 +212,30 @@ public class CommonAnnotationBeanPostProcessorTests {
assertThat(bean.destroy3Called).isTrue(); assertThat(bean.destroy3Called).isTrue();
} }
@Test
public void testResourceInjectionWithLegacyAnnotations() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
CommonAnnotationBeanPostProcessor bpp = new CommonAnnotationBeanPostProcessor();
bpp.setResourceFactory(bf);
bf.addBeanPostProcessor(bpp);
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(LegacyResourceInjectionBean.class));
TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb);
TestBean tb2 = new TestBean();
bf.registerSingleton("testBean2", tb2);
LegacyResourceInjectionBean bean = (LegacyResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.initCalled).isTrue();
assertThat(bean.init2Called).isTrue();
assertThat(bean.init3Called).isTrue();
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb2);
bf.destroySingletons();
assertThat(bean.destroyCalled).isTrue();
assertThat(bean.destroy2Called).isTrue();
assertThat(bean.destroy3Called).isTrue();
}
@Test @Test
public void testResourceInjectionWithResolvableDependencyType() { public void testResourceInjectionWithResolvableDependencyType() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@ -532,6 +568,30 @@ public class CommonAnnotationBeanPostProcessorTests {
} }
public static class LegacyAnnotatedInitDestroyBean {
public boolean initCalled = false;
public boolean destroyCalled = false;
@javax.annotation.PostConstruct
private void init() {
if (this.initCalled) {
throw new IllegalStateException("Already called");
}
this.initCalled = true;
}
@javax.annotation.PreDestroy
private void destroy() {
if (this.destroyCalled) {
throw new IllegalStateException("Already called");
}
this.destroyCalled = true;
}
}
public static class InitDestroyBeanPostProcessor implements DestructionAwareBeanPostProcessor { public static class InitDestroyBeanPostProcessor implements DestructionAwareBeanPostProcessor {
@Override @Override
@ -641,6 +701,83 @@ public class CommonAnnotationBeanPostProcessorTests {
} }
public static class LegacyResourceInjectionBean extends LegacyAnnotatedInitDestroyBean {
public boolean init2Called = false;
public boolean init3Called = false;
public boolean destroy2Called = false;
public boolean destroy3Called = false;
@javax.annotation.Resource
private TestBean testBean;
private TestBean testBean2;
@javax.annotation.PostConstruct
protected void init2() {
if (this.testBean == null || this.testBean2 == null) {
throw new IllegalStateException("Resources not injected");
}
if (!this.initCalled) {
throw new IllegalStateException("Superclass init method not called yet");
}
if (this.init2Called) {
throw new IllegalStateException("Already called");
}
this.init2Called = true;
}
@javax.annotation.PostConstruct
private void init() {
if (this.init3Called) {
throw new IllegalStateException("Already called");
}
this.init3Called = true;
}
@javax.annotation.PreDestroy
protected void destroy2() {
if (this.destroyCalled) {
throw new IllegalStateException("Superclass destroy called too soon");
}
if (this.destroy2Called) {
throw new IllegalStateException("Already called");
}
this.destroy2Called = true;
}
@javax.annotation.PreDestroy
private void destroy() {
if (this.destroyCalled) {
throw new IllegalStateException("Superclass destroy called too soon");
}
if (this.destroy3Called) {
throw new IllegalStateException("Already called");
}
this.destroy3Called = true;
}
@javax.annotation.Resource
public void setTestBean2(TestBean testBean2) {
if (this.testBean2 != null) {
throw new IllegalStateException("Already called");
}
this.testBean2 = testBean2;
}
public TestBean getTestBean() {
return testBean;
}
public TestBean getTestBean2() {
return testBean2;
}
}
static class NonPublicResourceInjectionBean<B> extends ResourceInjectionBean { static class NonPublicResourceInjectionBean<B> extends ResourceInjectionBean {
@Resource(name="testBean4", type=TestBean.class) @Resource(name="testBean4", type=TestBean.class)

View File

@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
@ -254,18 +253,11 @@ class InitDestroyMethodLifecycleTests {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()); beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor());
// Configure and register an InitDestroyAnnotationBeanPostProcessor as
// done in AnnotationConfigUtils.registerAnnotationConfigProcessors()
// for an ApplicatonContext.
InitDestroyAnnotationBeanPostProcessor initDestroyBpp = new InitDestroyAnnotationBeanPostProcessor();
initDestroyBpp.setInitAnnotationType(javax.annotation.PostConstruct.class);
initDestroyBpp.setDestroyAnnotationType(javax.annotation.PreDestroy.class);
beanFactory.addBeanPostProcessor(initDestroyBpp);
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setInitMethodName(initMethodName); beanDefinition.setInitMethodName(initMethodName);
beanDefinition.setDestroyMethodName(destroyMethodName); beanDefinition.setDestroyMethodName(destroyMethodName);
beanFactory.registerBeanDefinition("lifecycleTestBean", beanDefinition); beanFactory.registerBeanDefinition("lifecycleTestBean", beanDefinition);
return beanFactory; return beanFactory;
} }
@ -275,6 +267,7 @@ class InitDestroyMethodLifecycleTests {
GenericApplicationContext context = new GenericApplicationContext(); GenericApplicationContext context = new GenericApplicationContext();
initializer.initialize(context); initializer.initialize(context);
context.refresh(); context.refresh();
return context; return context;
} }
@ -309,6 +302,7 @@ class InitDestroyMethodLifecycleTests {
} }
} }
static class CustomInitDestroyBean { static class CustomInitDestroyBean {
final List<String> initMethods = new ArrayList<>(); final List<String> initMethods = new ArrayList<>();
@ -323,6 +317,7 @@ class InitDestroyMethodLifecycleTests {
} }
} }
static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean { static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean {
@PostConstruct @PostConstruct
@ -336,6 +331,7 @@ class InitDestroyMethodLifecycleTests {
} }
} }
static class CustomAnnotatedPrivateSameNameInitDestroyBean extends CustomAnnotatedPrivateInitDestroyBean { static class CustomAnnotatedPrivateSameNameInitDestroyBean extends CustomAnnotatedPrivateInitDestroyBean {
@PostConstruct @PostConstruct
@ -351,6 +347,7 @@ class InitDestroyMethodLifecycleTests {
} }
} }
static class CustomInitializingDisposableBean extends CustomInitDestroyBean static class CustomInitializingDisposableBean extends CustomInitDestroyBean
implements InitializingBean, DisposableBean { implements InitializingBean, DisposableBean {
@ -365,6 +362,7 @@ class InitDestroyMethodLifecycleTests {
} }
} }
static class CustomAnnotatedInitDestroyBean extends CustomInitializingDisposableBean { static class CustomAnnotatedInitDestroyBean extends CustomInitializingDisposableBean {
@PostConstruct @PostConstruct
@ -378,6 +376,7 @@ class InitDestroyMethodLifecycleTests {
} }
} }
static class CustomAnnotatedInitDestroyWithShadowedMethodsBean extends CustomInitializingDisposableBean { static class CustomAnnotatedInitDestroyWithShadowedMethodsBean extends CustomInitializingDisposableBean {
@PostConstruct @PostConstruct
@ -393,6 +392,7 @@ class InitDestroyMethodLifecycleTests {
} }
} }
static class AllInOneBean implements InitializingBean, DisposableBean { static class AllInOneBean implements InitializingBean, DisposableBean {
final List<String> initMethods = new ArrayList<>(); final List<String> initMethods = new ArrayList<>();
@ -411,8 +411,9 @@ class InitDestroyMethodLifecycleTests {
} }
} }
static class SubPackagePrivateInitDestroyBean extends PackagePrivateInitDestroyBean static class SubPackagePrivateInitDestroyBean extends PackagePrivateInitDestroyBean
implements InitializingBean, DisposableBean { implements InitializingBean, DisposableBean {
@Override @Override
public void afterPropertiesSet() { public void afterPropertiesSet() {
@ -433,7 +434,6 @@ class InitDestroyMethodLifecycleTests {
void preDestroy() { void preDestroy() {
this.destroyMethods.add("SubPackagePrivateInitDestroyBean.preDestroy"); this.destroyMethods.add("SubPackagePrivateInitDestroyBean.preDestroy");
} }
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -149,8 +149,8 @@ public abstract class AnnotationUtils {
* @since 5.2 * @since 5.2
* @see #isCandidateClass(Class, String) * @see #isCandidateClass(Class, String)
*/ */
public static boolean isCandidateClass(Class<?> clazz, Class<? extends Annotation> annotationType) { public static boolean isCandidateClass(Class<?> clazz, @Nullable Class<? extends Annotation> annotationType) {
return isCandidateClass(clazz, annotationType.getName()); return (annotationType != null && isCandidateClass(clazz, annotationType.getName()));
} }
/** /**