我正在使用Java 11和Spring开发JavaFX应用程序。应用模块与使用jlink
的自定义JRE捆绑在一起,该JRE仅允许将命名模块包含在捆绑中。由于Spring不提供命名模块,而是依靠自动模块来实现对Java 9 Module System的支持,因此我使用moditect向Spring JAR添加模块描述符(module-info.java
)。
编译,jlink
并执行该应用程序不会出现任何问题。但是,尽管我的@Component
类使用AppConfig
注释,Spring仍未检测到我的应用程序中任何用@ComponentScan
注释的类:
@Configuration
@ComponentScan
public class AppConfig {
}
在Main
中,我基于AnnotationConfigApplicationContext
创建一个AppConfig
并打印所有已注册的bean以及类路径上可用的资源:
public class Main extends Application {
private ConfigurableApplicationContext applicationContext;
public static void main(String[] args) {
launch(args);
}
@Override
public void init() {
applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
}
@Override
public void start(Stage mainWindow) throws IOException {
System.out.println("Printing beans: " + applicationContext.getBeanDefinitionNames().length);
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
System.out.println(Arrays.toString(resolver.getResources("classpath*:com/myapp/**/*.class")));
}
@Override
public void stop() {
applicationContext.stop();
}
}
如果我使用IntelliJ运行应用程序,PathMatchingResourcePatternResolver
会在类路径上找到我的所有类(我猜是因为IntelliJ使用类路径而不是模块路径来运行应用程序)。因此,将通过组件扫描检测所有组件,并创建相应的bean:
Printing beans: 8
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalPersistenceAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
appConfig
com.myapp.services.UserServiceImpl
com.myapp.services.BookingServiceImpl
[file [/Users/user/myapp/target/classes/com/myapp/AppConfig.class], file [/Users/user/myapp/target/classes/com/myapp/Main.class], file [/Users/user/myapp/target/classes/com/myapp/services/UserService.class], file [/Users/user/myapp/target/classes/com/myapp/services/UserServiceImpl.class], file [/Users/user/myapp/target/classes/com/myapp/services/BookingService.class], file [/Users/user/myapp/target/classes/com/myapp/services/BookingServiceImpl.class]]
但是,如果我通过jlink
捆绑包(即使用模块路径在自定义JRE上)运行应用程序,则Spring无法检测到我的任何类:
Printing beans: 5
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
appConfig
[]
PathMatchingResourcePatternResolver
找不到任何类(因为现在所有内容都位于模块路径上),并且组件扫描没有实例化单个bean。
如果我将组件类手动导入到AppConfig
中,则将正确创建Bean并通过@Autowired
进行注入:
@Configuration
@Import({
com.myapp.service.UserServiceImpl.class,
com.myapp.service.BookingServiceImpl.class
})
public class AppConfig {
}
为什么在使用@Import
时Spring能够创建bean,但是不能通过@ComponentScan
检测到bean?如何通过@ComponentScan
解析组件?
答案 0 :(得分:0)
问题注释中@IggyBlob提供的解决方案之外的另一种可能的解决方案是修补PathMatchingResourcePatternResolver
,以至少在完全兼容模块的Spring版本发布之前将资源搜索到模块路径中。
一种可能的实现方式:
public class PathMatchingResourcePatternResolverJigsaw extends PathMatchingResourcePatternResolver {
public PathMatchingResourcePatternResolverJigsaw() {
}
public PathMatchingResourcePatternResolverJigsaw(ResourceLoader resourceLoader) {
super(resourceLoader);
}
public PathMatchingResourcePatternResolverJigsaw(ClassLoader classLoader) {
super(classLoader);
}
public List<Resource> getResourcesFromModules(String locationPattern) throws IOException {
String pattern = locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length());
List<Resource> list = new ArrayList<>();
ModuleLayer.boot().configuration().modules().stream()
.map(ResolvedModule::reference)
.forEach(mref -> {
try (ModuleReader reader = mref.open()) {
list.addAll(reader.list()
.filter(p -> getPathMatcher().match(pattern, p))
.map(p -> {
try {
return convertClassLoaderURL(reader.find(p).get().toURL());
} catch (Exception e) {
return null;
}
})
.collect(Collectors.toList()));
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
});
return list;
}
@Override
public Resource[] getResources(String locationPattern) throws IOException {
boolean addModSearch = true;
Resource[] result = super.getResources(locationPattern);
if (addModSearch && locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
List<Resource> list = getResourcesFromModules(locationPattern);
list.addAll(Arrays.asList(result));
result = list.toArray(new Resource[0]);
}
return result;
}
}
并使用以下命令初始化您的春季应用上下文:
private ConfigurableApplicationContext context;
@Override
public void init() throws Exception {
ApplicationContextInitializer<GenericApplicationContext> initializer = new ApplicationContextInitializer<GenericApplicationContext>() {
@Override
public void initialize(GenericApplicationContext genericApplicationContext) {
genericApplicationContext.setResourceLoader(new PathMatchingResourcePatternResolverJigsaw());
}
};
this.context = new SpringApplicationBuilder().sources(MyApplication.class)
.initializers(initializer)
.build().run(new String[0]);
}
这只是一个丑陋的解决方法,请谨慎使用