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 f0aeaffd95..708064488a 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 @@ -107,11 +107,9 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB protected transient Log logger = LogFactory.getLog(getClass()); - @Nullable - private Class initAnnotationType; + private final Set> initAnnotationTypes = new LinkedHashSet<>(2); - @Nullable - private Class destroyAnnotationType; + private final Set> destroyAnnotationTypes = new LinkedHashSet<>(2); private int order = Ordered.LOWEST_PRECEDENCE; @@ -125,9 +123,23 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB *

Any custom annotation can be used, since there are no required * annotation attributes. There is no default, although a typical choice * is the {@link jakarta.annotation.PostConstruct} annotation. + * @see #addInitAnnotationType */ public void setInitAnnotationType(Class 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 initAnnotationType) { + if (initAnnotationType != null) { + this.initAnnotationTypes.add(initAnnotationType); + } } /** @@ -136,9 +148,23 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB *

Any custom annotation can be used, since there are no required * annotation attributes. There is no default, although a typical choice * is the {@link jakarta.annotation.PreDestroy} annotation. + * @see #addDestroyAnnotationType */ public void setDestroyAnnotationType(Class 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 destroyAnnotationType) { + if (destroyAnnotationType != null) { + this.destroyAnnotationTypes.add(destroyAnnotationType); + } } public void setOrder(int order) { @@ -255,7 +281,8 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB } 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; } @@ -268,16 +295,20 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB final List currDestroyMethods = new ArrayList<>(); ReflectionUtils.doWithLocalMethods(currentClass, method -> { - if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) { - currInitMethods.add(new LifecycleMethod(method, beanClass)); - if (logger.isTraceEnabled()) { - logger.trace("Found init method on class [" + beanClass.getName() + "]: " + method); + for (Class initAnnotationType : this.initAnnotationTypes) { + if (initAnnotationType != null && method.isAnnotationPresent(initAnnotationType)) { + currInitMethods.add(new LifecycleMethod(method, beanClass)); + if (logger.isTraceEnabled()) { + logger.trace("Found init method on class [" + beanClass.getName() + "]: " + method); + } } } - if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) { - currDestroyMethods.add(new LifecycleMethod(method, beanClass)); - if (logger.isTraceEnabled()) { - logger.trace("Found destroy method on class [" + beanClass.getName() + "]: " + method); + for (Class destroyAnnotationType : this.destroyAnnotationTypes) { + if (destroyAnnotationType != null && method.isAnnotationPresent(destroyAnnotationType)) { + currDestroyMethods.add(new LifecycleMethod(method, beanClass)); + if (logger.isTraceEnabled()) { + logger.trace("Found destroy method on class [" + beanClass.getName() + "]: " + method); + } } } }); diff --git a/spring-context/spring-context.gradle b/spring-context/spring-context.gradle index 3b93d1bc0c..2fc2d2005d 100644 --- a/spring-context/spring-context.gradle +++ b/spring-context/spring-context.gradle @@ -18,6 +18,7 @@ dependencies { optional("jakarta.inject:jakarta.inject-api") optional("jakarta.interceptor:jakarta.interceptor-api") optional("jakarta.validation:jakarta.validation-api") + optional("javax.annotation:javax.annotation-api") optional("javax.money:money-api") optional("org.aspectj:aspectjweaver") optional("org.apache.groovy:groovy") @@ -31,12 +32,11 @@ dependencies { testImplementation(testFixtures(project(":spring-beans"))) testImplementation(testFixtures(project(":spring-core"))) testImplementation("io.projectreactor:reactor-core") + testImplementation("jakarta.inject:jakarta.inject-tck") testImplementation("org.apache.groovy:groovy-jsr223") testImplementation("org.apache.groovy:groovy-xml") testImplementation("org.apache.commons:commons-pool2") testImplementation("org.awaitility:awaitility") - testImplementation("jakarta.inject:jakarta.inject-tck") - testImplementation("javax.annotation:javax.annotation-api") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") testRuntimeOnly("jakarta.xml.bind:jakarta.xml.bind-api") testRuntimeOnly("org.glassfish:jakarta.el") diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index 0203485ce4..e0ba5fed72 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -23,7 +23,6 @@ import java.util.Set; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 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.BeanDefinitionHolder; 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. - 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); def.setSource(source); 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. if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); 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 b7b31fba94..aa0a5ba5e9 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 @@ -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"); * 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.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.framework.ProxyFactory; 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 * requirements other than the type having to match. * - *

This post-processor also supports the EJB 3 {@link jakarta.ejb.EJB} annotation, + *

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. + * + *

This post-processor also supports the EJB {@link jakarta.ejb.EJB} annotation, * analogous to {@link jakarta.annotation.Resource}, with the capability to * 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. * *

For default usage, resolving resource names as Spring bean names, * 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. * Remove or turn off the default annotation configuration there if you intend * to specify a custom CommonAnnotationBeanPostProcessor bean definition! - *

NOTE: Annotation injection will be performed before XML injection; thus - * the latter configuration will override the former for properties wired through + *

NOTE: Annotation injection will be performed before XML injection; + * thus the latter configuration will override the former for properties wired through * both approaches. * * @author Juergen Hoeller @@ -136,14 +136,28 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean private static final Set> resourceAnnotationTypes = new LinkedHashSet<>(4); @Nullable - private static final Class ejbClass; + private static final Class jakartaResourceType; + + @Nullable + private static final Class javaxResourceType; + + @Nullable + private static final Class ejbAnnotationType; static { - resourceAnnotationTypes.add(Resource.class); + jakartaResourceType = loadAnnotationType("jakarta.annotation.Resource"); + if (jakartaResourceType != null) { + resourceAnnotationTypes.add(jakartaResourceType); + } - ejbClass = loadAnnotationType("jakarta.ejb.EJB"); - if (ejbClass != null) { - resourceAnnotationTypes.add(ejbClass); + javaxResourceType = loadAnnotationType("javax.annotation.Resource"); + if (javaxResourceType != null) { + 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() { 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+? if (jndiPresent) { @@ -338,13 +358,13 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean final List currElements = new ArrayList<>(); ReflectionUtils.doWithLocalFields(targetClass, field -> { - if (ejbClass != null && field.isAnnotationPresent(ejbClass)) { + if (ejbAnnotationType != null && field.isAnnotationPresent(ejbAnnotationType)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@EJB annotation is not supported on static fields"); } 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())) { 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)); } } + 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 -> { @@ -360,7 +388,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean return; } if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { - if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) { + if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) { if (Modifier.isStatic(method.getModifiers())) { 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); 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())) { 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)); } } + 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) { 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(); Class resourceType = resource.type(); this.isDefaultName = !StringUtils.hasLength(resourceName); @@ -630,7 +719,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean public EjbRefElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor 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 resourceName = resource.name(); this.isDefaultName = !StringUtils.hasLength(resourceName); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java index d68fa893e2..af0b72325f 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java @@ -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"); * you may not use this file except in compliance with the License. @@ -90,6 +90,18 @@ public class CommonAnnotationBeanPostProcessorTests { 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 public void testPostConstructAndPreDestroyWithManualConfiguration() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -200,6 +212,30 @@ public class CommonAnnotationBeanPostProcessorTests { 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 public void testResourceInjectionWithResolvableDependencyType() { 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 { @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 extends ResourceInjectionBean { @Resource(name="testBean4", type=TestBean.class) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java b/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java index 42ea7d19f3..fe78bd41de 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/InitDestroyMethodLifecycleTests.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.DisposableBean; 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.RootBeanDefinition; import org.springframework.context.ApplicationContextInitializer; @@ -254,18 +253,11 @@ class InitDestroyMethodLifecycleTests { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); 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); beanDefinition.setInitMethodName(initMethodName); beanDefinition.setDestroyMethodName(destroyMethodName); beanFactory.registerBeanDefinition("lifecycleTestBean", beanDefinition); + return beanFactory; } @@ -275,6 +267,7 @@ class InitDestroyMethodLifecycleTests { GenericApplicationContext context = new GenericApplicationContext(); initializer.initialize(context); context.refresh(); + return context; } @@ -309,6 +302,7 @@ class InitDestroyMethodLifecycleTests { } } + static class CustomInitDestroyBean { final List initMethods = new ArrayList<>(); @@ -323,6 +317,7 @@ class InitDestroyMethodLifecycleTests { } } + static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean { @PostConstruct @@ -336,6 +331,7 @@ class InitDestroyMethodLifecycleTests { } } + static class CustomAnnotatedPrivateSameNameInitDestroyBean extends CustomAnnotatedPrivateInitDestroyBean { @PostConstruct @@ -351,6 +347,7 @@ class InitDestroyMethodLifecycleTests { } } + static class CustomInitializingDisposableBean extends CustomInitDestroyBean implements InitializingBean, DisposableBean { @@ -365,6 +362,7 @@ class InitDestroyMethodLifecycleTests { } } + static class CustomAnnotatedInitDestroyBean extends CustomInitializingDisposableBean { @PostConstruct @@ -378,6 +376,7 @@ class InitDestroyMethodLifecycleTests { } } + static class CustomAnnotatedInitDestroyWithShadowedMethodsBean extends CustomInitializingDisposableBean { @PostConstruct @@ -393,6 +392,7 @@ class InitDestroyMethodLifecycleTests { } } + static class AllInOneBean implements InitializingBean, DisposableBean { final List initMethods = new ArrayList<>(); @@ -411,8 +411,9 @@ class InitDestroyMethodLifecycleTests { } } + static class SubPackagePrivateInitDestroyBean extends PackagePrivateInitDestroyBean - implements InitializingBean, DisposableBean { + implements InitializingBean, DisposableBean { @Override public void afterPropertiesSet() { @@ -433,7 +434,6 @@ class InitDestroyMethodLifecycleTests { void preDestroy() { this.destroyMethods.add("SubPackagePrivateInitDestroyBean.preDestroy"); } - } }