Skip to content

Commit 236dbc9

Browse files
committed
Fix annotated commands registration in GraalVM native image
Before this commit, annotated commands where fetched by scanning the classpath for component types at runtime, which native image can't do. This commit fixes that by looking for candidate components as beans from the application context. Resolves #1229
1 parent 0e955ec commit 236dbc9

2 files changed

Lines changed: 14 additions & 35 deletions

File tree

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

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,17 @@
2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
2424

25-
import org.springframework.aop.support.AopUtils;
26-
import org.springframework.beans.factory.config.BeanDefinition;
2725
import org.springframework.boot.autoconfigure.AutoConfiguration;
28-
import org.springframework.boot.autoconfigure.SpringBootApplication;
2926
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3027
import org.springframework.context.ApplicationContext;
3128
import org.springframework.context.annotation.Bean;
32-
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
3329
import org.springframework.core.MethodIntrospector;
3430
import org.springframework.core.annotation.AnnotatedElementUtils;
35-
import org.springframework.shell.core.ShellConfigurationException;
3631
import org.springframework.shell.core.command.Command;
3732
import org.springframework.shell.core.command.CommandRegistry;
3833
import org.springframework.shell.core.command.annotation.support.CommandFactoryBean;
3934
import org.springframework.shell.core.utils.Utils;
40-
import org.springframework.util.ClassUtils;
35+
import org.springframework.stereotype.Component;
4136
import org.springframework.util.ReflectionUtils;
4237

4338
@AutoConfiguration
@@ -61,33 +56,21 @@ private void registerProgrammaticCommands(ApplicationContext applicationContext,
6156
}
6257

6358
private void registerAnnotatedCommands(ApplicationContext applicationContext, CommandRegistry commandRegistry) {
64-
Map<String, Object> springBootApps = applicationContext.getBeansWithAnnotation(SpringBootApplication.class);
65-
Class<?> mainClass = AopUtils.getTargetClass(springBootApps.values().iterator().next());
66-
String mainPackage = ClassUtils.getPackageName(mainClass);
67-
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
68-
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(mainPackage);
69-
for (BeanDefinition candidateComponent : candidateComponents) {
70-
String className = candidateComponent.getBeanClassName();
71-
if (className == null) {
72-
log.warn(String.format("Skipping candidate component %s with null class name", candidateComponent));
59+
Map<String, Object> components = applicationContext.getBeansWithAnnotation(Component.class);
60+
for (Object candidateComponent : components.values()) {
61+
Class<?> type = candidateComponent.getClass();
62+
String className = type.getName();
63+
if (className.startsWith("org.springframework.boot")) {
7364
continue;
7465
}
75-
else {
76-
log.debug("Registering commands from component: " + className);
77-
}
78-
try {
79-
Class<?> cls = ClassUtils.forName(className, applicationContext.getClassLoader());
80-
ReflectionUtils.MethodFilter filter = method -> AnnotatedElementUtils.hasAnnotation(method,
81-
org.springframework.shell.core.command.annotation.Command.class);
82-
Set<Method> methods = MethodIntrospector.selectMethods(cls, filter);
83-
for (Method method : methods) {
84-
CommandFactoryBean factoryBean = new CommandFactoryBean(method);
85-
factoryBean.setApplicationContext(applicationContext);
86-
commandRegistry.registerCommand(factoryBean.getObject());
87-
}
88-
}
89-
catch (ClassNotFoundException e) {
90-
throw new ShellConfigurationException("Unable to configure commands from class " + className, e);
66+
log.debug("Registering commands from component: " + className);
67+
ReflectionUtils.MethodFilter filter = method -> AnnotatedElementUtils.hasAnnotation(method,
68+
org.springframework.shell.core.command.annotation.Command.class);
69+
Set<Method> methods = MethodIntrospector.selectMethods(type, filter);
70+
for (Method method : methods) {
71+
CommandFactoryBean factoryBean = new CommandFactoryBean(method);
72+
factoryBean.setApplicationContext(applicationContext);
73+
commandRegistry.registerCommand(factoryBean.getObject());
9174
}
9275
}
9376
}

spring-shell-docs/modules/ROOT/pages/building.adoc

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,3 @@ under `target` directory.
160160
If everything went well this binary can be run as is instead of executing
161161
boot application jar via jvm.
162162

163-
IMPORTANT: As of v4.0.0, the declarative annotation-based commands registration is not supported
164-
for native compilation, only the programmatic approach of defining commands is supported at
165-
this time.
166-

0 commit comments

Comments
 (0)