diff --git a/core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderChildContextInitializer.java b/core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderChildContextInitializer.java index 59941b1d01..db9897645a 100644 --- a/core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderChildContextInitializer.java +++ b/core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderChildContextInitializer.java @@ -183,11 +183,12 @@ public void applyTo(GenerationContext generationContext, BeanRegistrationCode be ApplicationContextInitializer.class, ConfigurableApplicationContext.class, HashMap.class); this.childContexts.forEach((name, context) -> { this.logger.info(() -> "Generating AOT child context initializer for " + name); - GenerationContext childGenerationContext = generationContext.withName(name + "Binder"); + String aotName = toAotGenerationName(name); + GenerationContext childGenerationContext = generationContext.withName(aotName + "Binder"); ClassName initializerClassName = aotGenerator.processAheadOfTime(context, childGenerationContext); - method.addStatement("$T " + name + "Initializer = new $L()", ApplicationContextInitializer.class, + method.addStatement("$T " + aotName + "Initializer = new $L()", ApplicationContextInitializer.class, ConfigurableApplicationContext.class, initializerClassName); - method.addStatement("initializers.put($S," + name + "Initializer)", name); + method.addStatement("initializers.put($S," + aotName + "Initializer)", name); }); method.addStatement("return instance.withChildContextInitializers(initializers)"); }); @@ -195,6 +196,24 @@ public void applyTo(GenerationContext generationContext, BeanRegistrationCode be } } + /** + * Converts a binder name to a valid Java identifier for AOT generated code. Runtime binder + * lookup continues to use the original configured name. + */ + private static String toAotGenerationName(String binderName) { + StringBuilder sanitized = new StringBuilder(binderName.length()); + for (int i = 0; i < binderName.length(); i++) { + char character = binderName.charAt(i); + if (i == 0) { + sanitized.append(Character.isJavaIdentifierStart(character) ? character : '_'); + } + else { + sanitized.append(Character.isJavaIdentifierPart(character) ? character : '_'); + } + } + return sanitized.isEmpty() ? "_" : sanitized.toString(); + } + private static class DeclaredBinders { Map binders = new HashMap<>(); diff --git a/core/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderChildContextInitializerTests.java b/core/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderChildContextInitializerTests.java index 774f52e359..9aa83545e9 100644 --- a/core/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderChildContextInitializerTests.java +++ b/core/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderChildContextInitializerTests.java @@ -125,6 +125,45 @@ void shouldStartDefaultBinderChildContextFromAotContributions(CapturedOutput out }); } + @Test + @CompileWithForkedClassLoader + @SuppressWarnings("unchecked") + void shouldStartHyphenatedBinderChildContextsFromAotContributions(CapturedOutput output) { + + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(BinderFactoryAutoConfiguration.class, + BindingServiceConfiguration.class, FunctionConfiguration.class)) + .withInitializer(new ConfigDataApplicationContextInitializer()) + .withPropertyValues("spring.config.location=classpath:binder-aot-hyphen-test/") + .withConfiguration(UserConfigurations.of(TestFooBinderAppConfiguration.class)); + + contextRunner.prepare(context -> { + TestGenerationContext generationContext = new TestGenerationContext(TestTarget.class); + ClassName className = new ApplicationContextAotGenerator().processAheadOfTime( + (GenericApplicationContext) context.getSourceApplicationContext(), generationContext); + generationContext.writeGeneratedContent(); + TestCompiler compiler = TestCompiler.forSystem(); + compiler.with(generationContext).compile(compiled -> { + GenericApplicationContext freshApplicationContext = new GenericApplicationContext(); + ApplicationContextInitializer initializer = compiled + .getInstance(ApplicationContextInitializer.class, className.toString()); + initializer.initialize(freshApplicationContext); + assertThat(output).contains("Beginning AOT processing for binder child contexts"); + assertThat(output).contains("Pre-creating binder child context (AOT) for mock-binder-2"); + assertThat(output).contains("Pre-creating binder child context (AOT) for mock-binder-1"); + assertThat(output).contains("Generating AOT child context initializer for mock-binder-2"); + assertThat(output).contains("Generating AOT child context initializer for mock-binder-1"); + + TestPropertyValues.of(AotDetector.AOT_ENABLED + "=true") + .applyToSystemProperties(freshApplicationContext::refresh); + + DefaultBinderFactory binderFactory = freshApplicationContext.getBean(DefaultBinderFactory.class); + assertThat(binderFactory.getBinder("mock-binder-1", MessageChannel.class)).isNotNull(); + assertThat(binderFactory.getBinder("mock-binder-2", MessageChannel.class)).isNotNull(); + }); + }); + } + @Test @CompileWithForkedClassLoader @SuppressWarnings("unchecked") diff --git a/core/spring-cloud-stream/src/test/resources/binder-aot-hyphen-test/application.yml b/core/spring-cloud-stream/src/test/resources/binder-aot-hyphen-test/application.yml new file mode 100644 index 0000000000..d3801a0c26 --- /dev/null +++ b/core/spring-cloud-stream/src/test/resources/binder-aot-hyphen-test/application.yml @@ -0,0 +1,21 @@ +spring.cloud: + function: + definition: fooSource;fooSink + stream: + default-binder: mock-binder-1 + binders: + mock-binder-1: + type: mock + environment: + foo: bar1 + mock-binder-2: + type: mock + environment: + foo: bar2 + bindings: + fooSource-out-0: + destination: fooSink-in-0 + binder: mock-binder-2 + fooSink-in-0: + destination: fooSource-out-0 + binder: mock-binder-2