Skip to content

Commit 03646dd

Browse files
committed
Fix eager instantiation of scoped beans during command registration #1351
Signed-off-by: David Pilar <david@czpilar.net>
1 parent d91b1b9 commit 03646dd

2 files changed

Lines changed: 86 additions & 8 deletions

File tree

spring-shell-core-autoconfigure/src/main/java/org/springframework/shell/core/autoconfigure/CommandRegistryAutoConfiguration.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.springframework.shell.core.command.CommandRegistry;
3333
import org.springframework.shell.core.command.annotation.support.CommandFactoryBean;
3434
import org.springframework.shell.core.utils.Utils;
35-
import org.springframework.stereotype.Component;
3635
import org.springframework.util.ReflectionUtils;
3736

3837
import static org.springframework.shell.core.utils.Utils.isProfileActive;
@@ -58,18 +57,26 @@ private void registerProgrammaticCommands(ApplicationContext applicationContext,
5857
}
5958

6059
private void registerAnnotatedCommands(ApplicationContext applicationContext, CommandRegistry commandRegistry) {
61-
Map<String, Object> components = applicationContext.getBeansWithAnnotation(Component.class);
62-
for (Object candidateComponent : components.values()) {
63-
Class<?> type = candidateComponent.getClass();
60+
ReflectionUtils.MethodFilter filter = method -> AnnotatedElementUtils.hasAnnotation(method,
61+
org.springframework.shell.core.command.annotation.Command.class)
62+
&& isProfileActive(method, applicationContext.getEnvironment());
63+
64+
for (String beanName : applicationContext.getBeanDefinitionNames()) {
65+
Class<?> type = applicationContext.getType(beanName, false);
66+
if (type == null) {
67+
continue;
68+
}
6469
String className = type.getName();
6570
if (className.startsWith("org.springframework.boot")) {
6671
continue;
6772
}
68-
log.debug("Registering commands from component: " + className);
69-
ReflectionUtils.MethodFilter filter = method -> AnnotatedElementUtils.hasAnnotation(method,
70-
org.springframework.shell.core.command.annotation.Command.class)
71-
&& isProfileActive(method, applicationContext.getEnvironment());
73+
7274
Set<Method> methods = MethodIntrospector.selectMethods(type, filter);
75+
if (methods.isEmpty()) {
76+
continue;
77+
}
78+
79+
log.debug("Registering commands from component: " + className);
7380
for (Method method : methods) {
7481
CommandFactoryBean factoryBean = new CommandFactoryBean(method);
7582
factoryBean.setApplicationContext(applicationContext);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2026-present 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+
package org.springframework.shell.core.autoconfigure;
17+
18+
import org.junit.jupiter.api.Test;
19+
20+
import org.springframework.boot.autoconfigure.AutoConfigurations;
21+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.context.annotation.Scope;
24+
import org.springframework.shell.core.command.CommandRegistry;
25+
import org.springframework.shell.core.command.annotation.Command;
26+
import org.springframework.stereotype.Component;
27+
28+
import static org.junit.jupiter.api.Assertions.assertNull;
29+
import static org.junit.jupiter.api.Assertions.assertTrue;
30+
31+
/**
32+
* Tests for {@link CommandRegistryAutoConfiguration} command discovery.
33+
*
34+
* @author David Pilar
35+
*/
36+
class CommandRegistryAutoConfigurationTests {
37+
38+
/**
39+
* Regression test for
40+
* <a href= "https://github.com/spring-projects/spring-shell/issues/1351">gh-1351</a>:
41+
* registering annotated commands must not eagerly instantiate beans whose scope (e.g.
42+
* {@code request}) is not available in a non-web shell application.
43+
*/
44+
@Test
45+
void requestScopedBeanDoesNotPreventCommandRegistration() {
46+
new ApplicationContextRunner().withUserConfiguration(TestConfiguration.class, RequestScopedBean.class)
47+
.withConfiguration(AutoConfigurations.of(SpringShellAutoConfiguration.class))
48+
.run(context -> {
49+
assertNull(context.getStartupFailure(), "Context should start without errors");
50+
CommandRegistry registry = context.getBean(CommandRegistry.class);
51+
assertTrue(registry.getCommands().stream().anyMatch(c -> c.getName().equals("greet")),
52+
"Annotated command should be registered");
53+
});
54+
}
55+
56+
@Configuration
57+
static class TestConfiguration {
58+
59+
@Command(name = "greet", description = "Say hello")
60+
public void sayHello() {
61+
}
62+
63+
}
64+
65+
@Component
66+
@Scope("request")
67+
static class RequestScopedBean {
68+
69+
}
70+
71+
}

0 commit comments

Comments
 (0)