Skip to content

Commit f64fc4b

Browse files
committed
Generate appropriate bean registration code for scoped proxies
Closes gh-28383
1 parent 7ea0cc3 commit f64fc4b

File tree

4 files changed

+250
-0
lines changed

4 files changed

+250
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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.aop.scope;
18+
19+
import java.lang.reflect.Executable;
20+
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
24+
import org.springframework.aot.generator.CodeContribution;
25+
import org.springframework.aot.generator.DefaultCodeContribution;
26+
import org.springframework.aot.generator.ProtectedAccess.Options;
27+
import org.springframework.aot.hint.RuntimeHints;
28+
import org.springframework.beans.factory.config.BeanDefinition;
29+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
30+
import org.springframework.beans.factory.generator.BeanFactoryContribution;
31+
import org.springframework.beans.factory.generator.BeanInstantiationGenerator;
32+
import org.springframework.beans.factory.generator.BeanRegistrationBeanFactoryContribution;
33+
import org.springframework.beans.factory.generator.BeanRegistrationContributionProvider;
34+
import org.springframework.beans.factory.support.RootBeanDefinition;
35+
import org.springframework.javapoet.support.MultiStatement;
36+
import org.springframework.lang.Nullable;
37+
38+
/**
39+
* {@link BeanRegistrationContributionProvider} for {@link ScopedProxyFactoryBean}.
40+
*
41+
* @author Stephane Nicoll
42+
*/
43+
class ScopedProxyBeanRegistrationContributionProvider implements BeanRegistrationContributionProvider {
44+
45+
private static final Log logger = LogFactory.getLog(ScopedProxyBeanRegistrationContributionProvider.class);
46+
47+
48+
private final ConfigurableBeanFactory beanFactory;
49+
50+
ScopedProxyBeanRegistrationContributionProvider(ConfigurableBeanFactory beanFactory) {
51+
this.beanFactory = beanFactory;
52+
}
53+
54+
@Nullable
55+
@Override
56+
public BeanFactoryContribution getContributionFor(String beanName, RootBeanDefinition beanDefinition) {
57+
Class<?> beanType = beanDefinition.getResolvableType().toClass();
58+
return (beanType.equals(ScopedProxyFactoryBean.class))
59+
? createScopedProxyBeanFactoryContribution(beanName, beanDefinition) : null;
60+
}
61+
62+
@Nullable
63+
private BeanFactoryContribution createScopedProxyBeanFactoryContribution(String beanName, RootBeanDefinition beanDefinition) {
64+
String targetBeanName = getTargetBeanName(beanDefinition);
65+
BeanDefinition targetBeanDefinition = getTargetBeanDefinition(targetBeanName);
66+
if (targetBeanDefinition == null) {
67+
logger.warn("Could not handle " + ScopedProxyFactoryBean.class.getSimpleName() +
68+
": no target bean definition found with name " + targetBeanName);
69+
return null;
70+
}
71+
RootBeanDefinition processedBeanDefinition = new RootBeanDefinition(beanDefinition);
72+
processedBeanDefinition.setTargetType(targetBeanDefinition.getResolvableType());
73+
processedBeanDefinition.getPropertyValues().removePropertyValue("targetBeanName");
74+
return new BeanRegistrationBeanFactoryContribution(beanName, processedBeanDefinition,
75+
getBeanInstantiationGenerator(targetBeanName));
76+
}
77+
78+
private BeanInstantiationGenerator getBeanInstantiationGenerator(String targetBeanName) {
79+
return new BeanInstantiationGenerator() {
80+
81+
@Override
82+
public Executable getInstanceCreator() {
83+
return ScopedProxyFactoryBean.class.getDeclaredConstructors()[0];
84+
}
85+
86+
@Override
87+
public CodeContribution generateBeanInstantiation(RuntimeHints runtimeHints) {
88+
CodeContribution codeContribution = new DefaultCodeContribution(runtimeHints);
89+
codeContribution.protectedAccess().analyze(getInstanceCreator(), Options.defaults().build());
90+
MultiStatement statements = new MultiStatement();
91+
statements.addStatement("$T factory = new $T()", ScopedProxyFactoryBean.class, ScopedProxyFactoryBean.class);
92+
statements.addStatement("factory.setTargetBeanName($S)", targetBeanName);
93+
statements.addStatement("factory.setBeanFactory(beanFactory)");
94+
statements.addStatement("return factory.getObject()");
95+
codeContribution.statements().add(statements.toLambdaBody("() ->"));
96+
return codeContribution;
97+
}
98+
};
99+
}
100+
101+
@Nullable
102+
private String getTargetBeanName(BeanDefinition beanDefinition) {
103+
Object value = beanDefinition.getPropertyValues().get("targetBeanName");
104+
return (value instanceof String targetBeanName) ? targetBeanName : null;
105+
}
106+
107+
@Nullable
108+
private BeanDefinition getTargetBeanDefinition(@Nullable String targetBeanName) {
109+
if (targetBeanName != null && this.beanFactory.containsBean(targetBeanName)) {
110+
return this.beanFactory.getMergedBeanDefinition(targetBeanName);
111+
}
112+
return null;
113+
}
114+
115+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.beans.factory.generator.BeanRegistrationContributionProvider= \
2+
org.springframework.aop.scope.ScopedProxyBeanRegistrationContributionProvider
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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.aop.scope;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.aop.testfixture.scope.SimpleTarget;
22+
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
23+
import org.springframework.aot.generator.GeneratedType;
24+
import org.springframework.beans.factory.config.BeanDefinition;
25+
import org.springframework.beans.factory.config.PropertiesFactoryBean;
26+
import org.springframework.beans.factory.generator.BeanFactoryContribution;
27+
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
28+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
29+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
30+
import org.springframework.beans.factory.support.RootBeanDefinition;
31+
import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolder;
32+
import org.springframework.core.ResolvableType;
33+
import org.springframework.javapoet.ClassName;
34+
import org.springframework.javapoet.support.CodeSnippet;
35+
import org.springframework.lang.Nullable;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
39+
/**
40+
* Tests for {@link ScopedProxyBeanRegistrationContributionProvider}.
41+
*
42+
* @author Stephane Nicoll
43+
*/
44+
class ScopedProxyBeanRegistrationContributionProviderTests {
45+
46+
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
47+
48+
@Test
49+
void getWithNonScopedProxy() {
50+
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(PropertiesFactoryBean.class)
51+
.getBeanDefinition();
52+
assertThat(getBeanFactoryContribution("test", beanDefinition)).isNull();
53+
}
54+
55+
@Test
56+
void getWithScopedProxyWithoutTargetBeanName() {
57+
BeanDefinition scopeBean = BeanDefinitionBuilder.rootBeanDefinition(ScopedProxyFactoryBean.class)
58+
.getBeanDefinition();
59+
assertThat(getBeanFactoryContribution("test", scopeBean)).isNull();
60+
}
61+
62+
@Test
63+
void getWithScopedProxyWithInvalidTargetBeanName() {
64+
BeanDefinition scopeBean = BeanDefinitionBuilder.rootBeanDefinition(ScopedProxyFactoryBean.class)
65+
.addPropertyValue("targetBeanName", "testDoesNotExist").getBeanDefinition();
66+
assertThat(getBeanFactoryContribution("test", scopeBean)).isNull();
67+
}
68+
69+
@Test
70+
void getWithScopedProxyWithTargetBeanName() {
71+
BeanDefinition targetBean = BeanDefinitionBuilder.rootBeanDefinition(SimpleTarget.class)
72+
.getBeanDefinition();
73+
beanFactory.registerBeanDefinition("simpleTarget", targetBean);
74+
BeanDefinition scopeBean = BeanDefinitionBuilder.rootBeanDefinition(ScopedProxyFactoryBean.class)
75+
.addPropertyValue("targetBeanName", "simpleTarget").getBeanDefinition();
76+
assertThat(getBeanFactoryContribution("test", scopeBean)).isNotNull();
77+
}
78+
79+
@Test
80+
void writeBeanRegistrationForScopedProxy() {
81+
RootBeanDefinition targetBean = new RootBeanDefinition();
82+
targetBean.setTargetType(ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
83+
targetBean.setScope("custom");
84+
this.beanFactory.registerBeanDefinition("numberHolder", targetBean);
85+
BeanDefinition scopeBean = BeanDefinitionBuilder.rootBeanDefinition(ScopedProxyFactoryBean.class)
86+
.addPropertyValue("targetBeanName", "numberHolder").getBeanDefinition();
87+
assertThat(writeBeanRegistration("test", scopeBean).getSnippet()).isEqualTo("""
88+
BeanDefinitionRegistrar.of("test", ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class))
89+
.instanceSupplier(() -> {
90+
ScopedProxyFactoryBean factory = new ScopedProxyFactoryBean();
91+
factory.setTargetBeanName("numberHolder");
92+
factory.setBeanFactory(beanFactory);
93+
return factory.getObject();
94+
}).register(beanFactory);
95+
""");
96+
}
97+
98+
private CodeSnippet writeBeanRegistration(String beanName, BeanDefinition beanDefinition) {
99+
BeanFactoryContribution contribution = getBeanFactoryContribution(beanName, beanDefinition);
100+
assertThat(contribution).isNotNull();
101+
BeanFactoryInitialization initialization = new BeanFactoryInitialization(new DefaultGeneratedTypeContext("comp.example", packageName -> GeneratedType.of(ClassName.get(packageName, "Test"))));
102+
contribution.applyTo(initialization);
103+
return CodeSnippet.of(initialization.toCodeBlock());
104+
}
105+
106+
@Nullable
107+
BeanFactoryContribution getBeanFactoryContribution(String beanName, BeanDefinition beanDefinition) {
108+
ScopedProxyBeanRegistrationContributionProvider provider = new ScopedProxyBeanRegistrationContributionProvider(this.beanFactory);
109+
return provider.getContributionFor(beanName, (RootBeanDefinition) beanDefinition);
110+
}
111+
112+
}
113+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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.aop.testfixture.scope;
18+
19+
public class SimpleTarget {
20+
}

0 commit comments

Comments
 (0)