Skip to content
This repository was archived by the owner on Feb 23, 2023. It is now read-only.

Commit e886a67

Browse files
committed
Handle event listener registration for package private classes
Closes gh-1089
1 parent 6db14c9 commit e886a67

File tree

7 files changed

+247
-49
lines changed

7 files changed

+247
-49
lines changed

spring-aot/src/main/java/org/springframework/context/bootstrap/generator/event/EventListenerMetadataGenerator.java

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ class EventListenerMetadataGenerator {
5353
this.eventListenerFactoryBeanName = eventListenerFactoryBeanName;
5454
}
5555

56+
Class<?> getBeanType() {
57+
return this.beanType;
58+
}
59+
5660
/**
5761
* Write an {@link EventListenerMetadata} registration.
5862
*/

spring-aot/src/main/java/org/springframework/context/bootstrap/generator/event/EventListenerMethodRegistrationGenerator.java

+80-8
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,21 @@
1818

1919
import java.lang.reflect.Method;
2020
import java.util.ArrayList;
21+
import java.util.Collection;
2122
import java.util.Collections;
2223
import java.util.Iterator;
2324
import java.util.LinkedHashMap;
2425
import java.util.List;
2526
import java.util.Map;
2627
import java.util.Map.Entry;
28+
import java.util.stream.Collectors;
29+
30+
import javax.lang.model.element.Modifier;
2731

28-
import com.squareup.javapoet.ClassName;
2932
import com.squareup.javapoet.CodeBlock;
33+
import com.squareup.javapoet.CodeBlock.Builder;
34+
import com.squareup.javapoet.MethodSpec;
35+
import com.squareup.javapoet.ParameterizedTypeName;
3036
import org.apache.commons.logging.Log;
3137
import org.apache.commons.logging.LogFactory;
3238

@@ -36,15 +42,20 @@
3642
import org.springframework.beans.factory.BeanInitializationException;
3743
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3844
import org.springframework.context.annotation.AnnotationConfigUtils;
45+
import org.springframework.context.bootstrap.generator.infrastructure.BootstrapClass;
3946
import org.springframework.context.bootstrap.generator.infrastructure.BootstrapWriterContext;
4047
import org.springframework.context.event.EventListener;
4148
import org.springframework.context.event.EventListenerFactory;
49+
import org.springframework.context.event.EventListenerMetadata;
4250
import org.springframework.context.event.EventListenerMethodProcessor;
51+
import org.springframework.context.event.EventListenerRegistrar;
4352
import org.springframework.core.MethodIntrospector;
4453
import org.springframework.core.annotation.AnnotatedElementUtils;
4554
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
4655
import org.springframework.core.annotation.AnnotationUtils;
4756
import org.springframework.util.CollectionUtils;
57+
import org.springframework.util.LinkedMultiValueMap;
58+
import org.springframework.util.MultiValueMap;
4859

4960
/**
5061
* Detect beans with methods annotated with {@link EventListener} and write the code
@@ -55,9 +66,6 @@
5566
*/
5667
public class EventListenerMethodRegistrationGenerator {
5768

58-
private static final ClassName REGISTRAR = ClassName.get("org.springframework.context.event",
59-
"EventListenerRegistrar");
60-
6169
private static final String REGISTRAR_BEAN_NAME = "org.springframework.aot.EventListenerRegistrar";
6270

6371
private static final Log logger = LogFactory.getLog(EventListenerMethodRegistrationGenerator.class);
@@ -100,16 +108,71 @@ public void writeEventListenersRegistration(BootstrapWriterContext context, Code
100108
return; // No listener detected
101109
}
102110
eventGenerators.forEach((eventGenerator) -> eventGenerator.registerReflectionMetadata(context.getNativeConfigurationRegistry()));
103-
code.add("context.registerBean($S, $T.class, () -> new $L(context,\n", REGISTRAR_BEAN_NAME, REGISTRAR, REGISTRAR.simpleName());
104-
code.indent().indent();
105-
Iterator<EventListenerMetadataGenerator> it = eventGenerators.iterator();
111+
code.add("context.registerBean($S, $T.class, () -> new $L(context, ", REGISTRAR_BEAN_NAME,
112+
EventListenerRegistrar.class, EventListenerRegistrar.class.getSimpleName());
113+
code.add(writeEventListenersMetadataRegistration(context, eventGenerators));
114+
code.addStatement("))");
115+
}
116+
117+
private CodeBlock writeEventListenersMetadataRegistration(BootstrapWriterContext context,
118+
List<EventListenerMetadataGenerator> eventGenerators) {
119+
String targetPackageName = context.getPackageName();
120+
MultiValueMap<String, EventListenerMetadataGenerator> generatorsPerPackage =
121+
indexEventListeners(eventGenerators, targetPackageName);
122+
Builder registrations = CodeBlock.builder();
123+
boolean multipleMethods = generatorsPerPackage.size() > 1;
124+
if (multipleMethods) {
125+
registrations.add("$T.of(\n", List.class).indent().indent();
126+
}
127+
Iterator<Entry<String, List<EventListenerMetadataGenerator>>> it =
128+
generatorsPerPackage.entrySet().iterator();
129+
while (it.hasNext()) {
130+
Entry<String, List<EventListenerMetadataGenerator>> entry = it.next();
131+
MethodSpec method = createEventListenersMetadataMethod(entry.getValue());
132+
BootstrapClass bootstrapClass = context.getBootstrapClass(entry.getKey());
133+
bootstrapClass.addMethod(method);
134+
registrations.add("$T.$N()", bootstrapClass.getClassName(), method);
135+
if (it.hasNext()) {
136+
registrations.add(",\n");
137+
}
138+
}
139+
if (multipleMethods) {
140+
registrations.add("\n").unindent().unindent();
141+
registrations.add(").stream().flatMap($T::stream).collect($T.toList())",
142+
Collection.class, Collectors.class);
143+
}
144+
return registrations.build();
145+
}
146+
147+
private MultiValueMap<String, EventListenerMetadataGenerator> indexEventListeners(List<EventListenerMetadataGenerator> eventGenerators,
148+
String targetPackageName) {
149+
MultiValueMap<String, EventListenerMetadataGenerator> generatorsPerPackage = new LinkedMultiValueMap<>();
150+
for (EventListenerMetadataGenerator eventGenerator : eventGenerators) {
151+
if (isAccessibleFrom(eventGenerator.getBeanType(), targetPackageName)) {
152+
generatorsPerPackage.add(targetPackageName, eventGenerator);
153+
}
154+
else {
155+
generatorsPerPackage.add(eventGenerator.getBeanType().getPackageName(), eventGenerator);
156+
}
157+
}
158+
return generatorsPerPackage;
159+
}
160+
161+
private MethodSpec createEventListenersMetadataMethod(List<EventListenerMetadataGenerator> generators) {
162+
Builder code = CodeBlock.builder();
163+
code.add("return $T.of(", List.class);
164+
code.add("\n").indent();
165+
Iterator<EventListenerMetadataGenerator> it = generators.iterator();
106166
while (it.hasNext()) {
107167
it.next().writeEventListenerMetadata(code); ;
108168
if (it.hasNext()) {
109169
code.add(",\n");
110170
}
111171
}
112-
code.add("\n").unindent().unindent().addStatement("))");
172+
code.add("\n").unindent().addStatement(")");
173+
return MethodSpec.methodBuilder("getEventListenersMetadata")
174+
.returns(ParameterizedTypeName.get(List.class, EventListenerMetadata.class))
175+
.addModifiers(Modifier.PUBLIC, Modifier.STATIC).addCode(code.build()).build();
113176
}
114177

115178
public List<EventListenerMetadataGenerator> process(String beanName) {
@@ -198,4 +261,13 @@ private List<EventListenerMetadataGenerator> processBean(String beanName, Class<
198261
return result;
199262
}
200263

264+
private static boolean isAccessibleFrom(Class<?> beanType, String packageName) {
265+
return isAccessible(packageName, beanType.getModifiers(),
266+
beanType.getPackageName());
267+
}
268+
269+
private static boolean isAccessible(String packageName, int modifiers, String actualPackageName) {
270+
return java.lang.reflect.Modifier.isPublic(modifiers) || packageName.equals(actualPackageName);
271+
}
272+
201273
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package org.springframework.context.bootstrap.generator.event;
22

3+
import java.io.IOException;
4+
import java.io.StringWriter;
35
import java.util.List;
6+
import java.util.function.BiConsumer;
47

58
import com.squareup.javapoet.CodeBlock;
9+
import com.squareup.javapoet.CodeBlock.Builder;
610
import org.junit.jupiter.api.Test;
711

812
import org.springframework.beans.factory.config.BeanDefinition;
@@ -21,7 +25,10 @@
2125
import org.springframework.context.bootstrap.generator.sample.event.SingleEventListener;
2226
import org.springframework.context.bootstrap.generator.sample.event.SingleTransactionalEventListener;
2327
import org.springframework.context.bootstrap.generator.sample.scope.SimpleServiceImpl;
28+
import org.springframework.context.bootstrap.generator.sample.visibility.ProtectedEventListenerConfiguration;
2429
import org.springframework.context.bootstrap.generator.test.CodeSnippet;
30+
import org.springframework.context.bootstrap.generator.test.TextAssert;
31+
import org.springframework.context.support.GenericApplicationContext;
2532
import org.springframework.transaction.event.TransactionalEventListenerFactory;
2633
import org.springframework.util.ReflectionUtils;
2734

@@ -39,16 +46,24 @@ void writeEventListenersRegistrationWithNoEventListener() {
3946
DefaultListableBeanFactory beanFactory = prepareBeanFactory();
4047
beanFactory.registerBeanDefinition("test", BeanDefinitionBuilder.rootBeanDefinition(SimpleConfiguration.class)
4148
.getBeanDefinition());
42-
assertThat(generateCode(beanFactory)).isEmpty();
49+
assertGeneratedCode(beanFactory, (code, writerContext) -> assertThat(code).isEmpty());
4350
}
4451

4552
@Test
4653
void writeEventListenersRegistrationWithSingleEventListener() {
4754
DefaultListableBeanFactory beanFactory = prepareBeanFactory();
4855
beanFactory.registerBeanDefinition("single", BeanDefinitionBuilder.rootBeanDefinition(SingleEventListener.class)
4956
.getBeanDefinition());
50-
assertThat(generateCode(beanFactory)).lines().containsExactly("context.registerBean(\"org.springframework.aot.EventListenerRegistrar\", EventListenerRegistrar.class, () -> new EventListenerRegistrar(context,",
51-
" EventListenerMetadata.forBean(\"single\", SingleEventListener.class).annotatedMethod(\"onStartup\", ApplicationStartedEvent.class)", "));");
57+
assertGeneratedCode(beanFactory, (code, writerContext) -> {
58+
assertThat(code).lines().contains("context.registerBean(\"org.springframework.aot.EventListenerRegistrar\", EventListenerRegistrar.class, "
59+
+ "() -> new EventListenerRegistrar(context, ContextBootstrapInitializer.getEventListenersMetadata()));");
60+
assertGeneratedCode(writerContext.getMainBootstrapClass()).removeIndent(1).lines().containsSequence(
61+
"public static List<EventListenerMetadata> getEventListenersMetadata() {",
62+
" return List.of(",
63+
" EventListenerMetadata.forBean(\"single\", SingleEventListener.class).annotatedMethod(\"onStartup\", ApplicationStartedEvent.class)",
64+
" );",
65+
"}");
66+
});
5267
}
5368

5469
@Test
@@ -58,9 +73,35 @@ void writeEventListenersRegistrationWithEventListeners() {
5873
.getBeanDefinition());
5974
beanFactory.registerBeanDefinition("another", BeanDefinitionBuilder.rootBeanDefinition(AnotherEventListener.class)
6075
.getBeanDefinition());
61-
assertThat(generateCode(beanFactory)).lines().containsExactly("context.registerBean(\"org.springframework.aot.EventListenerRegistrar\", EventListenerRegistrar.class, () -> new EventListenerRegistrar(context,",
62-
" EventListenerMetadata.forBean(\"test\", SingleEventListener.class).annotatedMethod(\"onStartup\", ApplicationStartedEvent.class),",
63-
" EventListenerMetadata.forBean(\"another\", AnotherEventListener.class).annotatedMethod(\"onRefresh\")", "));");
76+
assertGeneratedCode(beanFactory, (code, writerContext) -> assertGeneratedCode(writerContext.getMainBootstrapClass())
77+
.removeIndent(1).lines().containsSequence(
78+
"public static List<EventListenerMetadata> getEventListenersMetadata() {",
79+
" return List.of(",
80+
" EventListenerMetadata.forBean(\"test\", SingleEventListener.class).annotatedMethod(\"onStartup\", ApplicationStartedEvent.class),",
81+
" EventListenerMetadata.forBean(\"another\", AnotherEventListener.class).annotatedMethod(\"onRefresh\")",
82+
" );",
83+
"}"));
84+
}
85+
86+
@Test
87+
void writeEventListenersRegistrationWithPackageProtectedEventListener() {
88+
GenericApplicationContext context = new GenericApplicationContext(prepareBeanFactory());
89+
context.registerBeanDefinition("configuration", BeanDefinitionBuilder.rootBeanDefinition(ProtectedEventListenerConfiguration.class)
90+
.getBeanDefinition());
91+
BuildTimeBeanDefinitionsRegistrar registrar = new BuildTimeBeanDefinitionsRegistrar();
92+
ConfigurableListableBeanFactory beanFactory = registrar.processBeanDefinitions(context);
93+
assertGeneratedCode(beanFactory, (code, writerContext) -> {
94+
assertThat(code).lines().containsExactly("context.registerBean(\"org.springframework.aot.EventListenerRegistrar\", "
95+
+ "EventListenerRegistrar.class, () -> new EventListenerRegistrar(context, ContextBootstrapInitializer.getEventListenersMetadata()));");
96+
BootstrapClass bootstrapClass = writerContext.getBootstrapClass(ProtectedEventListenerConfiguration.class.getPackageName());
97+
assertGeneratedCode(bootstrapClass).removeIndent(1).lines().containsSequence(
98+
"public static List<EventListenerMetadata> getEventListenersMetadata() {",
99+
" return List.of(",
100+
" EventListenerMetadata.forBean(\"protectedEventListener\", ProtectedEventListener.class).annotatedMethod(\"onStartup\", ApplicationStartedEvent.class)",
101+
" );",
102+
"}"
103+
);
104+
});
64105
}
65106

66107
@Test
@@ -72,10 +113,15 @@ void writeEventListenersRegistrationWithCustomEventListenerFactory() {
72113
.getBeanDefinition());
73114
beanFactory.registerBeanDefinition("transactional", BeanDefinitionBuilder.rootBeanDefinition(SingleTransactionalEventListener.class)
74115
.getBeanDefinition());
75-
assertThat(generateCode(beanFactory)).lines().containsExactly("context.registerBean(\"org.springframework.aot.EventListenerRegistrar\", EventListenerRegistrar.class, () -> new EventListenerRegistrar(context,",
76-
" EventListenerMetadata.forBean(\"simple\", SingleEventListener.class).annotatedMethod(\"onStartup\", ApplicationStartedEvent.class),",
77-
" EventListenerMetadata.forBean(\"transactional\", SingleTransactionalEventListener.class).eventListenerFactoryBeanName(\"internalTxEventListenerFactory\").annotatedMethod(\"onEvent\", ApplicationEvent.class)",
78-
"));");
116+
assertGeneratedCode(beanFactory, (code, writerContext) -> {
117+
assertGeneratedCode(writerContext.getMainBootstrapClass()).removeIndent(1).lines().containsSequence(
118+
"public static List<EventListenerMetadata> getEventListenersMetadata() {",
119+
" return List.of(",
120+
" EventListenerMetadata.forBean(\"simple\", SingleEventListener.class).annotatedMethod(\"onStartup\", ApplicationStartedEvent.class),",
121+
" EventListenerMetadata.forBean(\"transactional\", SingleTransactionalEventListener.class).eventListenerFactoryBeanName(\"internalTxEventListenerFactory\").annotatedMethod(\"onEvent\", ApplicationEvent.class)",
122+
" );",
123+
"}");
124+
});
79125
}
80126

81127
@Test
@@ -84,10 +130,14 @@ void writeEventListenersRegistrationWithScopedProxy() {
84130
context.register(SimpleServiceImpl.class);
85131
BuildTimeBeanDefinitionsRegistrar registrar = new BuildTimeBeanDefinitionsRegistrar();
86132
ConfigurableListableBeanFactory beanFactory = registrar.processBeanDefinitions(context);
87-
assertThat(generateCode(beanFactory)).lines().containsExactly(
88-
"context.registerBean(\"org.springframework.aot.EventListenerRegistrar\", EventListenerRegistrar.class, () -> new EventListenerRegistrar(context,",
89-
" EventListenerMetadata.forBean(\"simpleServiceImpl\", SimpleServiceImpl.class).annotatedMethod(\"onContextRefresh\")",
90-
"));");
133+
assertGeneratedCode(beanFactory, (code, writerContext) -> {
134+
assertGeneratedCode(writerContext.getMainBootstrapClass()).removeIndent(1).lines().containsSequence(
135+
"public static List<EventListenerMetadata> getEventListenersMetadata() {",
136+
" return List.of(",
137+
" EventListenerMetadata.forBean(\"simpleServiceImpl\", SimpleServiceImpl.class).annotatedMethod(\"onContextRefresh\")",
138+
" );",
139+
"}");
140+
});
91141
}
92142

93143
@Test
@@ -97,22 +147,21 @@ void writeEventListenersRegistrationRegisterReflectionMetadata() {
97147
.getBeanDefinition());
98148
beanFactory.registerBeanDefinition("another", BeanDefinitionBuilder.rootBeanDefinition(AnotherEventListener.class)
99149
.getBeanDefinition());
100-
EventListenerMethodRegistrationGenerator processor = new EventListenerMethodRegistrationGenerator(beanFactory);
101-
BootstrapWriterContext context = creteBootstrapContext();
102-
processor.writeEventListenersRegistration(context, CodeBlock.builder());
103-
List<NativeReflectionEntry> entries = context.getNativeConfigurationRegistry().reflection().getEntries();
104-
assertThat(entries).hasSize(2);
105-
assertThat(entries).anySatisfy((entry) -> {
106-
assertThat(entry.getType()).isEqualTo(SingleEventListener.class);
107-
assertThat(entry.getMethods()).containsOnly(
108-
ReflectionUtils.findMethod(SingleEventListener.class, "onStartup", ApplicationStartedEvent.class));
109-
assertThat(entry.getFields()).isEmpty();
110-
});
111-
assertThat(entries).anySatisfy((entry) -> {
112-
assertThat(entry.getType()).isEqualTo(AnotherEventListener.class);
113-
assertThat(entry.getMethods()).containsOnly(
114-
ReflectionUtils.findMethod(AnotherEventListener.class, "onRefresh"));
115-
assertThat(entry.getFields()).isEmpty();
150+
assertGeneratedCode(beanFactory, (code, writerContext) -> {
151+
List<NativeReflectionEntry> entries = writerContext.getNativeConfigurationRegistry().reflection().getEntries();
152+
assertThat(entries).hasSize(2);
153+
assertThat(entries).anySatisfy((entry) -> {
154+
assertThat(entry.getType()).isEqualTo(SingleEventListener.class);
155+
assertThat(entry.getMethods()).containsOnly(
156+
ReflectionUtils.findMethod(SingleEventListener.class, "onStartup", ApplicationStartedEvent.class));
157+
assertThat(entry.getFields()).isEmpty();
158+
});
159+
assertThat(entries).anySatisfy((entry) -> {
160+
assertThat(entry.getType()).isEqualTo(AnotherEventListener.class);
161+
assertThat(entry.getMethods()).containsOnly(
162+
ReflectionUtils.findMethod(AnotherEventListener.class, "onRefresh"));
163+
assertThat(entry.getFields()).isEmpty();
164+
});
116165
});
117166
}
118167

@@ -122,13 +171,23 @@ private DefaultListableBeanFactory prepareBeanFactory() {
122171
return beanFactory;
123172
}
124173

125-
private CodeSnippet generateCode(ConfigurableListableBeanFactory beanFactory) {
174+
void assertGeneratedCode(ConfigurableListableBeanFactory beanFactory, BiConsumer<CodeSnippet, BootstrapWriterContext> generatedContent) {
126175
EventListenerMethodRegistrationGenerator processor = new EventListenerMethodRegistrationGenerator(beanFactory);
127-
return CodeSnippet.of((code) -> processor.writeEventListenersRegistration(creteBootstrapContext(), code));
176+
BootstrapWriterContext writerContext = new BootstrapWriterContext(BootstrapClass.of("com.example"));
177+
Builder code = CodeBlock.builder();
178+
processor.writeEventListenersRegistration(writerContext, code);
179+
generatedContent.accept(CodeSnippet.of(code.build()), writerContext);
128180
}
129181

130-
private static BootstrapWriterContext creteBootstrapContext() {
131-
return new BootstrapWriterContext(BootstrapClass.of("com.example"));
182+
private TextAssert assertGeneratedCode(BootstrapClass bootstrapClass) {
183+
try {
184+
StringWriter out = new StringWriter();
185+
bootstrapClass.toJavaFile().writeTo(out);
186+
return new TextAssert(out.toString());
187+
}
188+
catch (IOException ex) {
189+
throw new IllegalStateException(ex);
190+
}
132191
}
133192

134193
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2019-2021 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.context.bootstrap.generator.sample.visibility;
18+
19+
import org.springframework.boot.context.event.ApplicationStartedEvent;
20+
import org.springframework.context.event.EventListener;
21+
22+
class ProtectedEventListener {
23+
24+
@EventListener
25+
void onStartup(ApplicationStartedEvent event) {
26+
27+
}
28+
29+
}

0 commit comments

Comments
 (0)