Search code examples
javaspringclassloaderjava-modulespring-context

Problem reading classes from JPMS module in Spring 6


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?


Solution

  • 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();