diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 1490284a4e0..daeb1cd833e 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -36,6 +36,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.index.CandidateComponentsIndex; import org.springframework.context.index.CandidateComponentsIndexLoader; +import org.springframework.core.SpringProperties; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; @@ -47,6 +48,7 @@ import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.ClassFormatException; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AnnotationTypeFilter; @@ -93,6 +95,18 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + /** + * System property that instructs Spring to ignore class format exceptions during + * classpath scanning, in particular for unsupported class file versions. + * By default, such a class format mismatch leads to a classpath scanning failure. + * @since 6.1.2 + * @see ClassFormatException + */ + public static final String IGNORE_CLASSFORMAT_PROPERTY_NAME = "spring.classformat.ignore"; + + private static final boolean shouldIgnoreClassFormatException = + SpringProperties.getFlag(IGNORE_CLASSFORMAT_PROPERTY_NAME); + protected final Log logger = LogFactory.getLog(getClass()); @@ -480,9 +494,20 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage()); } } + catch (ClassFormatException ex) { + if (shouldIgnoreClassFormatException) { + if (debugEnabled) { + logger.debug("Ignored incompatible class format in " + resource + ": " + ex.getMessage()); + } + } + else { + throw new BeanDefinitionStoreException("Incompatible class format in " + resource + + ": set system property 'spring.classformat.ignore' to 'true' " + + "if you mean to ignore such files during classpath scanning", ex); + } + } catch (Throwable ex) { - throw new BeanDefinitionStoreException( - "Failed to read candidate component class: " + resource, ex); + throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex); } } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/ClassFormatException.java b/spring-core/src/main/java/org/springframework/core/type/classreading/ClassFormatException.java new file mode 100644 index 00000000000..f45b8117997 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/ClassFormatException.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.type.classreading; + +import java.io.IOException; + +import org.springframework.core.io.Resource; + +/** + * Exception that indicates an incompatible class format encountered + * in a class file during metadata reading. + * + * @author Juergen Hoeller + * @since 6.1.2 + * @see MetadataReaderFactory#getMetadataReader(Resource) + * @see ClassFormatError + */ +@SuppressWarnings("serial") +public class ClassFormatException extends IOException { + + /** + * Construct a new {@code ClassFormatException} with the + * supplied message. + * @param message the detail message + */ + public ClassFormatException(String message) { + super(message); + } + + /** + * Construct a new {@code ClassFormatException} with the + * supplied message and cause. + * @param message the detail message + * @param cause the root cause + */ + public ClassFormatException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java index 555b3acaa2e..4eddbfa6cd2 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ public interface MetadataReaderFactory { * Obtain a MetadataReader for the given class name. * @param className the class name (to be resolved to a ".class" file) * @return a holder for the ClassReader instance (never {@code null}) + * @throws ClassFormatException in case of an incompatible class format * @throws IOException in case of I/O failure */ MetadataReader getMetadataReader(String className) throws IOException; @@ -43,6 +44,7 @@ public interface MetadataReaderFactory { * Obtain a MetadataReader for the given resource. * @param resource the resource (pointing to a ".class" file) * @return a holder for the ClassReader instance (never {@code null}) + * @throws ClassFormatException in case of an incompatible class format * @throws IOException in case of I/O failure */ MetadataReader getMetadataReader(Resource resource) throws IOException; diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java index d89ae8f52e6..08150a51a47 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,8 +35,8 @@ import org.springframework.lang.Nullable; */ final class SimpleMetadataReader implements MetadataReader { - private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG - | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES; + private static final int PARSING_OPTIONS = + (ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); private final Resource resource; @@ -56,8 +56,10 @@ final class SimpleMetadataReader implements MetadataReader { return new ClassReader(is); } catch (IllegalArgumentException ex) { - throw new IOException("ASM ClassReader failed to parse class file - " + - "probably due to a new Java class file version that isn't supported yet: " + resource, ex); + throw new ClassFormatException("ASM ClassReader failed to parse class file - " + + "probably due to a new Java class file version that is not supported yet. " + + "Consider compiling with a lower '-target' or upgrade your framework version. " + + "Affected class: " + resource, ex); } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java index 6ab0a086025..4ae545255fd 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java @@ -52,6 +52,7 @@ import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.InfrastructureProxy; +import org.springframework.core.SpringProperties; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; @@ -59,6 +60,7 @@ import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.ClassFormatException; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AnnotationTypeFilter; @@ -110,6 +112,11 @@ public class LocalSessionFactoryBuilder extends Configuration { private static final TypeFilter CONVERTER_TYPE_FILTER = new AnnotationTypeFilter(Converter.class, false); + private static final String IGNORE_CLASSFORMAT_PROPERTY_NAME = "spring.classformat.ignore"; + + private static final boolean shouldIgnoreClassFormatException = + SpringProperties.getFlag(IGNORE_CLASSFORMAT_PROPERTY_NAME); + private final ResourcePatternResolver resourcePatternResolver; @@ -335,6 +342,14 @@ public class LocalSessionFactoryBuilder extends Configuration { catch (FileNotFoundException ex) { // Ignore non-readable resource } + catch (ClassFormatException ex) { + if (!shouldIgnoreClassFormatException) { + throw new MappingException("Incompatible class format in " + resource, ex); + } + } + catch (Throwable ex) { + throw new MappingException("Failed to read candidate component class: " + resource, ex); + } } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesScanner.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesScanner.java index 1686b9c2fb2..192db122fbf 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesScanner.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesScanner.java @@ -33,11 +33,13 @@ import jakarta.persistence.PersistenceException; import org.springframework.context.index.CandidateComponentsIndex; import org.springframework.context.index.CandidateComponentsIndexLoader; +import org.springframework.core.SpringProperties; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.ClassFormatException; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AnnotationTypeFilter; @@ -59,6 +61,11 @@ public final class PersistenceManagedTypesScanner { private static final String PACKAGE_INFO_SUFFIX = ".package-info"; + private static final String IGNORE_CLASSFORMAT_PROPERTY_NAME = "spring.classformat.ignore"; + + private static final boolean shouldIgnoreClassFormatException = + SpringProperties.getFlag(IGNORE_CLASSFORMAT_PROPERTY_NAME); + private static final Set entityTypeFilters = new LinkedHashSet<>(4); static { @@ -79,6 +86,7 @@ public final class PersistenceManagedTypesScanner { this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(resourceLoader.getClassLoader()); } + /** * Scan the specified packages and return a {@link PersistenceManagedTypes} that * represents the result of the scanning. @@ -130,6 +138,14 @@ public final class PersistenceManagedTypesScanner { catch (FileNotFoundException ex) { // Ignore non-readable resource } + catch (ClassFormatException ex) { + if (!shouldIgnoreClassFormatException) { + throw new PersistenceException("Incompatible class format in " + resource, ex); + } + } + catch (Throwable ex) { + throw new PersistenceException("Failed to read candidate component class: " + resource, ex); + } } } catch (IOException ex) { @@ -150,6 +166,7 @@ public final class PersistenceManagedTypesScanner { return false; } + private static class ScanResult { private final List managedClassNames = new ArrayList<>(); @@ -163,6 +180,6 @@ public final class PersistenceManagedTypesScanner { return new SimplePersistenceManagedTypes(this.managedClassNames, this.managedPackages, this.persistenceUnitRootUrl); } - } + }