-
Notifications
You must be signed in to change notification settings - Fork 38.9k
Description
I've spotted inconsistent behaviour when running the attached code example.tar.gz on java 21 and 25.
Note that the class in the scanned directory is not used by the spring.
The class itself has annotated method, the used annotation and annotation value classes are "provided" scope - meaning they will be unavailable at runtime.
Our use case is that we are using spring in osgi environment where the annotated class is run in multiple environments - in some it's instantiated using custom code in other it's not used.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>spring7test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>7.0.5</version>
</dependency>
<dependency>
<groupId>com.atlassian.struts2</groupId>
<artifactId>struts-support</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>SpringTestMain.java
package org.springtest;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTestMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
System.out.println(classPathXmlApplicationContext.getBeanDefinitionCount());
}
}TestClass.java
package org.springtest;
import com.atlassian.struts.httpmethod.HttpMethod;
import com.atlassian.struts.httpmethod.PermittedMethods;
public class TestClass {
@PermittedMethods(HttpMethod.GET)
public void executeIt() {
System.out.println("Execute!");
}
}spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.springtest"/>
</beans>When running the org.springtest.SpringTestMain#main on java 21 it executes without throwing an exception.
When running on the java 25 it throws
Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [/Users/tkanafa/git/spring7test/target/classes/org/springtest/TestClass.class]
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:504)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:321)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:276)
at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:90)
at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:73)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1379)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1360)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:176)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:147)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:94)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:518)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:398)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:345)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:317)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:216)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:187)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:249)
at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:131)
at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:96)
at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:720)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:592)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
at org.springtest.SpringTestMain.main(SpringTestMain.java:7)
Caused by: java.lang.IllegalArgumentException: Could not find class [com.atlassian.struts.httpmethod.HttpMethod]
at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:353)
at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.loadClass(ClassFileAnnotationMetadata.java:116)
at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.resolveArrayElementType(ClassFileAnnotationMetadata.java:168)
at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.parseArrayValue(ClassFileAnnotationMetadata.java:135)
at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.readAnnotationValue(ClassFileAnnotationMetadata.java:103)
at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.createMergedAnnotation(ClassFileAnnotationMetadata.java:72)
at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.lambda$createMergedAnnotations$0(ClassFileAnnotationMetadata.java:55)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.Collections$2.tryAdvance(Collections.java:5182)
at java.base/java.util.Collections$2.forEachRemaining(Collections.java:5190)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:723)
at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.createMergedAnnotations(ClassFileAnnotationMetadata.java:57)
at org.springframework.core.type.classreading.ClassFileMethodMetadata.lambda$of$2(ClassFileMethodMetadata.java:151)
at java.base/java.util.Optional.map(Optional.java:260)
at org.springframework.core.type.classreading.ClassFileMethodMetadata.of(ClassFileMethodMetadata.java:151)
at org.springframework.core.type.classreading.ClassFileClassMetadata$Builder.method(ClassFileClassMetadata.java:295)
at org.springframework.core.type.classreading.ClassFileClassMetadata.lambda$of$0(ClassFileClassMetadata.java:213)
at java.base/java.util.ArrayList$Itr.forEachRemaining(ArrayList.java:1086)
at java.base/java.util.Collections$UnmodifiableCollection$1.forEachRemaining(Collections.java:1087)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:803)
at org.springframework.core.type.classreading.ClassFileClassMetadata.of(ClassFileClassMetadata.java:191)
at org.springframework.core.type.classreading.ClassFileMetadataReader.<init>(ClassFileMetadataReader.java:45)
at org.springframework.core.type.classreading.ClassFileMetadataReaderFactory.getMetadataReader(ClassFileMetadataReaderFactory.java:62)
at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:127)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:464)
... 25 more
Caused by: java.lang.ClassNotFoundException: com.atlassian.struts.httpmethod.HttpMethod
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:547)
at org.springframework.util.ClassUtils.forName(ClassUtils.java:302)
at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:343)
... 54 more
From my debugging on java 21 this classes are used:
abstract class MetadataReaderFactoryDelegate {
static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) {
return new SimpleMetadataReaderFactory(resourceLoader);
}
static MetadataReaderFactory create(@Nullable ClassLoader classLoader) {
return new SimpleMetadataReaderFactory(classLoader);
}
}On java 25 these classes are used (from the versions in META-INF):
org.springframework.core.type.classreading.MetadataReaderFactoryDelegate
abstract class MetadataReaderFactoryDelegate {
static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) {
return new ClassFileMetadataReaderFactory(resourceLoader);
}
static MetadataReaderFactory create(@Nullable ClassLoader classLoader) {
return new ClassFileMetadataReaderFactory(classLoader);
}
}Is it expected behaviour that processing the classes with annotations that are not present on the classpath results in an exception thrown?