Skip to content

Commit 672555a

Browse files
committed
Add support of init and destroy methods
This commit updates InitDestroyBeanPostProcessor so that it contributes init or destroy method names to the `RootBeanDefinition`. This is then used by the generator to provide these methods to the optimized AOT context. Invocation of those init methods still happen using reflection so dedicated hints are contributed for them. Closes gh-28151
1 parent 1b7892c commit 672555a

File tree

10 files changed

+458
-18
lines changed

10 files changed

+458
-18
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,24 @@
3232
import java.util.Map;
3333
import java.util.Set;
3434
import java.util.concurrent.ConcurrentHashMap;
35+
import java.util.stream.Stream;
3536

3637
import org.apache.commons.logging.Log;
3738
import org.apache.commons.logging.LogFactory;
3839

3940
import org.springframework.beans.BeansException;
4041
import org.springframework.beans.factory.BeanCreationException;
4142
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
43+
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
44+
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
4245
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
4346
import org.springframework.beans.factory.support.RootBeanDefinition;
4447
import org.springframework.core.Ordered;
4548
import org.springframework.core.PriorityOrdered;
4649
import org.springframework.core.annotation.AnnotationUtils;
4750
import org.springframework.lang.Nullable;
4851
import org.springframework.util.ClassUtils;
52+
import org.springframework.util.CollectionUtils;
4953
import org.springframework.util.ReflectionUtils;
5054

5155
/**
@@ -72,13 +76,14 @@
7276
* for annotation-driven injection of named beans.
7377
*
7478
* @author Juergen Hoeller
79+
* @author Stephane Nicoll
7580
* @since 2.5
7681
* @see #setInitAnnotationType
7782
* @see #setDestroyAnnotationType
7883
*/
7984
@SuppressWarnings("serial")
80-
public class InitDestroyAnnotationBeanPostProcessor
81-
implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, Serializable {
85+
public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor,
86+
MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, PriorityOrdered, Serializable {
8287

8388
private final transient LifecycleMetadata emptyLifecycleMetadata =
8489
new LifecycleMetadata(Object.class, Collections.emptyList(), Collections.emptyList()) {
@@ -146,8 +151,36 @@ public int getOrder() {
146151

147152
@Override
148153
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
154+
findInjectionMetadata(beanDefinition, beanType);
155+
}
156+
157+
@Override
158+
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
159+
LifecycleMetadata metadata = findInjectionMetadata(beanDefinition, beanType);
160+
if (!CollectionUtils.isEmpty(metadata.initMethods)) {
161+
String[] initMethodNames = safeMerge(
162+
beanDefinition.getInitMethodNames(), metadata.initMethods);
163+
beanDefinition.setInitMethodNames(initMethodNames);
164+
}
165+
if (!CollectionUtils.isEmpty(metadata.destroyMethods)) {
166+
String[] destroyMethodNames = safeMerge(
167+
beanDefinition.getDestroyMethodNames(), metadata.destroyMethods);
168+
beanDefinition.setDestroyMethodNames(destroyMethodNames);
169+
}
170+
return null;
171+
}
172+
173+
private LifecycleMetadata findInjectionMetadata(RootBeanDefinition beanDefinition, Class<?> beanType) {
149174
LifecycleMetadata metadata = findLifecycleMetadata(beanType);
150175
metadata.checkConfigMembers(beanDefinition);
176+
return metadata;
177+
}
178+
179+
private String[] safeMerge(@Nullable String[] existingNames, Collection<LifecycleElement> detectedElements) {
180+
Stream<String> detectedNames = detectedElements.stream().map(LifecycleElement::getIdentifier);
181+
Stream<String> mergedNames = (existingNames != null
182+
? Stream.concat(Stream.of(existingNames), detectedNames) : detectedNames);
183+
return mergedNames.distinct().toArray(String[]::new);
151184
}
152185

153186
@Override

spring-beans/src/main/java/org/springframework/beans/factory/generator/BeanRegistrationBeanFactoryContribution.java

+46
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.springframework.lang.Nullable;
5656
import org.springframework.util.ClassUtils;
5757
import org.springframework.util.ObjectUtils;
58+
import org.springframework.util.ReflectionUtils;
5859
import org.springframework.util.StringUtils;
5960

6061
/**
@@ -121,6 +122,14 @@ public void applyTo(BeanFactoryInitialization initialization) {
121122
* @param runtimeHints the runtime hints to use
122123
*/
123124
void registerRuntimeHints(RuntimeHints runtimeHints) {
125+
String[] initMethodNames = this.beanDefinition.getInitMethodNames();
126+
if (!ObjectUtils.isEmpty(initMethodNames)) {
127+
registerInitDestroyMethodsRuntimeHints(initMethodNames, runtimeHints);
128+
}
129+
String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames();
130+
if (!ObjectUtils.isEmpty(destroyMethodNames)) {
131+
registerInitDestroyMethodsRuntimeHints(destroyMethodNames, runtimeHints);
132+
}
124133
registerPropertyValuesRuntimeHints(runtimeHints);
125134
}
126135

@@ -191,6 +200,15 @@ protected CodeContribution generateBeanInstance(RuntimeHints runtimeHints) {
191200
return this.beanInstantiationGenerator.generateBeanInstantiation(runtimeHints);
192201
}
193202

203+
private void registerInitDestroyMethodsRuntimeHints(String[] methodNames, RuntimeHints runtimeHints) {
204+
for (String methodName : methodNames) {
205+
Method method = ReflectionUtils.findMethod(getUserBeanClass(), methodName);
206+
if (method != null) {
207+
runtimeHints.reflection().registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE));
208+
}
209+
}
210+
}
211+
194212
private void registerPropertyValuesRuntimeHints(RuntimeHints runtimeHints) {
195213
if (!this.beanDefinition.hasPropertyValues()) {
196214
return;
@@ -357,6 +375,14 @@ private boolean hasUnresolvedGenerics(ResolvableType resolvableType) {
357375
private void handleBeanDefinitionMetadata(Builder code) {
358376
String bdVariable = determineVariableName("bd");
359377
MultiStatement statements = new MultiStatement();
378+
String[] initMethodNames = this.beanDefinition.getInitMethodNames();
379+
if (!ObjectUtils.isEmpty(initMethodNames)) {
380+
handleInitMethodNames(statements, bdVariable, initMethodNames);
381+
}
382+
String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames();
383+
if (!ObjectUtils.isEmpty(destroyMethodNames)) {
384+
handleDestroyMethodNames(statements, bdVariable, destroyMethodNames);
385+
}
360386
if (this.beanDefinition.isPrimary()) {
361387
statements.addStatement("$L.setPrimary(true)", bdVariable);
362388
}
@@ -399,6 +425,26 @@ private void handleBeanDefinitionMetadata(Builder code) {
399425
code.add(")");
400426
}
401427

428+
private void handleInitMethodNames(MultiStatement statements, String bdVariable, String[] initMethodNames) {
429+
if (initMethodNames.length == 1) {
430+
statements.addStatement("$L.setInitMethodName($S)", bdVariable, initMethodNames[0]);
431+
}
432+
else {
433+
statements.addStatement("$L.setInitMethodNames($L)", bdVariable,
434+
this.parameterGenerator.generateParameterValue(initMethodNames));
435+
}
436+
}
437+
438+
private void handleDestroyMethodNames(MultiStatement statements, String bdVariable, String[] destroyMethodNames) {
439+
if (destroyMethodNames.length == 1) {
440+
statements.addStatement("$L.setDestroyMethodName($S)", bdVariable, destroyMethodNames[0]);
441+
}
442+
else {
443+
statements.addStatement("$L.setDestroyMethodNames($L)", bdVariable,
444+
this.parameterGenerator.generateParameterValue(destroyMethodNames));
445+
}
446+
}
447+
402448
private void handleArgumentValues(MultiStatement statements, String bdVariable,
403449
Map<Integer, ValueHolder> indexedArgumentValues) {
404450
if (indexedArgumentValues.size() == 1) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory.annotation;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
22+
import org.springframework.beans.factory.support.RootBeanDefinition;
23+
import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.Destroy;
24+
import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.Init;
25+
import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.InitDestroyBean;
26+
import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.MultiInitDestroyBean;
27+
import org.springframework.lang.Nullable;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.mockito.Mockito.mock;
31+
import static org.mockito.Mockito.verifyNoInteractions;
32+
33+
/**
34+
* Tests for {@link InitDestroyAnnotationBeanPostProcessor}.
35+
*
36+
* @author Stephane Nicoll
37+
*/
38+
class InitDestroyAnnotationBeanPostProcessorTests {
39+
40+
@Test
41+
void contributeWithNoCallbackDoesNotMutateRootBeanDefinition() {
42+
RootBeanDefinition beanDefinition = mock(RootBeanDefinition.class);
43+
assertThat(createAotContributingBeanPostProcessor().contribute(
44+
beanDefinition, String.class, "test")).isNull();
45+
verifyNoInteractions(beanDefinition);
46+
}
47+
48+
@Test
49+
void contributeWithInitDestroyCallback() {
50+
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class);
51+
assertThat(createContribution(beanDefinition)).isNull();
52+
assertThat(beanDefinition.getInitMethodNames()).containsExactly("initMethod");
53+
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod");
54+
}
55+
56+
@Test
57+
void contributeWithInitDestroyCallbackRetainCustomMethods() {
58+
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class);
59+
beanDefinition.setInitMethodName("customInitMethod");
60+
beanDefinition.setDestroyMethodNames("customDestroyMethod");
61+
assertThat(createContribution(beanDefinition)).isNull();
62+
assertThat(beanDefinition.getInitMethodNames())
63+
.containsExactly("customInitMethod", "initMethod");
64+
assertThat(beanDefinition.getDestroyMethodNames())
65+
.containsExactly("customDestroyMethod", "destroyMethod");
66+
}
67+
68+
@Test
69+
void contributeWithInitDestroyCallbackFilterDuplicates() {
70+
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class);
71+
beanDefinition.setInitMethodName("initMethod");
72+
beanDefinition.setDestroyMethodNames("destroyMethod");
73+
assertThat(createContribution(beanDefinition)).isNull();
74+
assertThat(beanDefinition.getInitMethodNames()).containsExactly("initMethod");
75+
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod");
76+
}
77+
78+
@Test
79+
void contributeWithMultipleInitDestroyCallbacks() {
80+
RootBeanDefinition beanDefinition = new RootBeanDefinition(MultiInitDestroyBean.class);
81+
assertThat(createContribution(beanDefinition)).isNull();
82+
assertThat(beanDefinition.getInitMethodNames())
83+
.containsExactly("initMethod", "anotherInitMethod");
84+
assertThat(beanDefinition.getDestroyMethodNames())
85+
.containsExactly("anotherDestroyMethod", "destroyMethod");
86+
}
87+
88+
@Nullable
89+
private BeanInstantiationContribution createContribution(RootBeanDefinition beanDefinition) {
90+
InitDestroyAnnotationBeanPostProcessor bpp = createAotContributingBeanPostProcessor();
91+
return bpp.contribute(beanDefinition, beanDefinition.getResolvableType().toClass(), "test");
92+
}
93+
94+
private InitDestroyAnnotationBeanPostProcessor createAotContributingBeanPostProcessor() {
95+
InitDestroyAnnotationBeanPostProcessor bpp = new InitDestroyAnnotationBeanPostProcessor();
96+
bpp.setInitAnnotationType(Init.class);
97+
bpp.setDestroyAnnotationType(Destroy.class);
98+
return bpp;
99+
}
100+
101+
}

0 commit comments

Comments
 (0)