I have a JPMS application with layer tree. On Layer C
I need to create Spring context while Spring framework is located on Layer B
:
boot layer
|- Layer B with Spring framework.
|- Layer C where I need to create my context
Component:
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ConfigurableApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
public static ConfigurableApplicationContext getApplicationContext() {
return applicationContext;
}
}
Config:
@Configuration
@ComponentScan(basePackageClasses = {
ApplicationContextHolder.class
})
public class ContextConfig {
}
Creating context:
System.out.println("ThreadContextClassLoadere:" + Thread.currentThread().getContextClassLoader());
System.out.println("This classLoader:" + this.getClass().getClassLoader());
var clazz = this.getClass().getClassLoader().loadClass("org.springframework.context.ApplicationContextAware");
System.out.println("RESULT:" + clazz);
iocContext = new AnnotationConfigApplicationContext();
iocContext.register(ContextConfig.class);
iocContext.setClassLoader(this.getClass().getClassLoader());
iocContext.refresh();
Result:
ThreadContextClassLoadere:jdk.internal.loader.Loader@63fcbf75
This classLoader:jdk.internal.loader.Loader@63fcbf75
RESULT:interface org.springframework.context.ApplicationContextAware
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.foo.ContextConfig]
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:178)
at [email protected]/org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:415)
at [email protected]/org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:287)
at [email protected]/org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:344)
at [email protected]/org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:115)
at [email protected]/org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:779)
at [email protected]/org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:597)
....
Caused by: java.io.FileNotFoundException: class path resource [org/springframework/context/ApplicationContextAware.class] cannot be opened because it does not exist
at [email protected]/org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:211)
at [email protected]/org.springframework.core.type.classreading.SimpleMetadataReader.getClassReader(SimpleMetadataReader.java:54)
at [email protected]/org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:48)
at [email protected]/org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103)
at [email protected]/org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:122)
at [email protected]/org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:81)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.asSourceClass(ConfigurationClassParser.java:613)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser$SourceClass.getInterfaces(ConfigurationClassParser.java:939)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.processInterfaces(ConfigurationClassParser.java:379)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:325)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:243)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:188)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:297)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:243)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:196)
at [email protected]/org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:164)
... 16 more
As I understand (I may be wrong) Spring uses some other class loader. Could anyone say how to fix it?
I solved the problem.
Spring loads classes via getResourceAsStream()
and by default uses ClassPathResource
with classLoader.getResourceAsStream(this.absolutePath)
. However, it wont work for the JPMS application. To make it work it is required to use ModuleResource
(added in Spring 6.1) instead of ClassPathResource
.
So, it is necessary to create custom ResourceLoader
that will use ModuleResource
add set it to context:
var iocContext = new AnnotationConfigApplicationContext();
...
var myModuleResourceLoader = ....
iocContext.setResourceLoader(myModuleResourceLoader);
iocContext.refresh();