Skip to content

Commit c57b7e8

Browse files
committed
Introduce ClassFormatException and spring.classformat.ignore property
Closes gh-27691
1 parent 77b0382 commit c57b7e8

File tree

6 files changed

+124
-9
lines changed

6 files changed

+124
-9
lines changed

spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.context.ResourceLoaderAware;
3737
import org.springframework.context.index.CandidateComponentsIndex;
3838
import org.springframework.context.index.CandidateComponentsIndexLoader;
39+
import org.springframework.core.SpringProperties;
3940
import org.springframework.core.annotation.AnnotationUtils;
4041
import org.springframework.core.env.Environment;
4142
import org.springframework.core.env.EnvironmentCapable;
@@ -47,6 +48,7 @@
4748
import org.springframework.core.io.support.ResourcePatternUtils;
4849
import org.springframework.core.type.AnnotationMetadata;
4950
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
51+
import org.springframework.core.type.classreading.ClassFormatException;
5052
import org.springframework.core.type.classreading.MetadataReader;
5153
import org.springframework.core.type.classreading.MetadataReaderFactory;
5254
import org.springframework.core.type.filter.AnnotationTypeFilter;
@@ -93,6 +95,18 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
9395

9496
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
9597

98+
/**
99+
* System property that instructs Spring to ignore class format exceptions during
100+
* classpath scanning, in particular for unsupported class file versions.
101+
* By default, such a class format mismatch leads to a classpath scanning failure.
102+
* @since 6.1.2
103+
* @see ClassFormatException
104+
*/
105+
public static final String IGNORE_CLASSFORMAT_PROPERTY_NAME = "spring.classformat.ignore";
106+
107+
private static final boolean shouldIgnoreClassFormatException =
108+
SpringProperties.getFlag(IGNORE_CLASSFORMAT_PROPERTY_NAME);
109+
96110

97111
protected final Log logger = LogFactory.getLog(getClass());
98112

@@ -480,9 +494,20 @@ private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
480494
logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
481495
}
482496
}
497+
catch (ClassFormatException ex) {
498+
if (shouldIgnoreClassFormatException) {
499+
if (debugEnabled) {
500+
logger.debug("Ignored incompatible class format in " + resource + ": " + ex.getMessage());
501+
}
502+
}
503+
else {
504+
throw new BeanDefinitionStoreException("Incompatible class format in " + resource +
505+
": set system property 'spring.classformat.ignore' to 'true' " +
506+
"if you mean to ignore such files during classpath scanning", ex);
507+
}
508+
}
483509
catch (Throwable ex) {
484-
throw new BeanDefinitionStoreException(
485-
"Failed to read candidate component class: " + resource, ex);
510+
throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);
486511
}
487512
}
488513
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.type.classreading;
18+
19+
import java.io.IOException;
20+
21+
import org.springframework.core.io.Resource;
22+
23+
/**
24+
* Exception that indicates an incompatible class format encountered
25+
* in a class file during metadata reading.
26+
*
27+
* @author Juergen Hoeller
28+
* @since 6.1.2
29+
* @see MetadataReaderFactory#getMetadataReader(Resource)
30+
* @see ClassFormatError
31+
*/
32+
@SuppressWarnings("serial")
33+
public class ClassFormatException extends IOException {
34+
35+
/**
36+
* Construct a new {@code ClassFormatException} with the
37+
* supplied message.
38+
* @param message the detail message
39+
*/
40+
public ClassFormatException(String message) {
41+
super(message);
42+
}
43+
44+
/**
45+
* Construct a new {@code ClassFormatException} with the
46+
* supplied message and cause.
47+
* @param message the detail message
48+
* @param cause the root cause
49+
*/
50+
public ClassFormatException(String message, Throwable cause) {
51+
super(message, cause);
52+
}
53+
54+
}

spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ public interface MetadataReaderFactory {
3535
* Obtain a MetadataReader for the given class name.
3636
* @param className the class name (to be resolved to a ".class" file)
3737
* @return a holder for the ClassReader instance (never {@code null})
38+
* @throws ClassFormatException in case of an incompatible class format
3839
* @throws IOException in case of I/O failure
3940
*/
4041
MetadataReader getMetadataReader(String className) throws IOException;
@@ -43,6 +44,7 @@ public interface MetadataReaderFactory {
4344
* Obtain a MetadataReader for the given resource.
4445
* @param resource the resource (pointing to a ".class" file)
4546
* @return a holder for the ClassReader instance (never {@code null})
47+
* @throws ClassFormatException in case of an incompatible class format
4648
* @throws IOException in case of I/O failure
4749
*/
4850
MetadataReader getMetadataReader(Resource resource) throws IOException;

spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,8 +35,8 @@
3535
*/
3636
final class SimpleMetadataReader implements MetadataReader {
3737

38-
private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG
39-
| ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES;
38+
private static final int PARSING_OPTIONS =
39+
(ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
4040

4141
private final Resource resource;
4242

@@ -56,8 +56,10 @@ private static ClassReader getClassReader(Resource resource) throws IOException
5656
return new ClassReader(is);
5757
}
5858
catch (IllegalArgumentException ex) {
59-
throw new IOException("ASM ClassReader failed to parse class file - " +
60-
"probably due to a new Java class file version that isn't supported yet: " + resource, ex);
59+
throw new ClassFormatException("ASM ClassReader failed to parse class file - " +
60+
"probably due to a new Java class file version that is not supported yet. " +
61+
"Consider compiling with a lower '-target' or upgrade your framework version. " +
62+
"Affected class: " + resource, ex);
6163
}
6264
}
6365
}

spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@
5252

5353
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
5454
import org.springframework.core.InfrastructureProxy;
55+
import org.springframework.core.SpringProperties;
5556
import org.springframework.core.io.Resource;
5657
import org.springframework.core.io.ResourceLoader;
5758
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
5859
import org.springframework.core.io.support.ResourcePatternResolver;
5960
import org.springframework.core.io.support.ResourcePatternUtils;
6061
import org.springframework.core.task.AsyncTaskExecutor;
6162
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
63+
import org.springframework.core.type.classreading.ClassFormatException;
6264
import org.springframework.core.type.classreading.MetadataReader;
6365
import org.springframework.core.type.classreading.MetadataReaderFactory;
6466
import org.springframework.core.type.filter.AnnotationTypeFilter;
@@ -110,6 +112,11 @@ public class LocalSessionFactoryBuilder extends Configuration {
110112

111113
private static final TypeFilter CONVERTER_TYPE_FILTER = new AnnotationTypeFilter(Converter.class, false);
112114

115+
private static final String IGNORE_CLASSFORMAT_PROPERTY_NAME = "spring.classformat.ignore";
116+
117+
private static final boolean shouldIgnoreClassFormatException =
118+
SpringProperties.getFlag(IGNORE_CLASSFORMAT_PROPERTY_NAME);
119+
113120

114121
private final ResourcePatternResolver resourcePatternResolver;
115122

@@ -335,6 +342,14 @@ else if (className.endsWith(PACKAGE_INFO_SUFFIX)) {
335342
catch (FileNotFoundException ex) {
336343
// Ignore non-readable resource
337344
}
345+
catch (ClassFormatException ex) {
346+
if (!shouldIgnoreClassFormatException) {
347+
throw new MappingException("Incompatible class format in " + resource, ex);
348+
}
349+
}
350+
catch (Throwable ex) {
351+
throw new MappingException("Failed to read candidate component class: " + resource, ex);
352+
}
338353
}
339354
}
340355
}

spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesScanner.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333

3434
import org.springframework.context.index.CandidateComponentsIndex;
3535
import org.springframework.context.index.CandidateComponentsIndexLoader;
36+
import org.springframework.core.SpringProperties;
3637
import org.springframework.core.io.Resource;
3738
import org.springframework.core.io.ResourceLoader;
3839
import org.springframework.core.io.support.ResourcePatternResolver;
3940
import org.springframework.core.io.support.ResourcePatternUtils;
4041
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
42+
import org.springframework.core.type.classreading.ClassFormatException;
4143
import org.springframework.core.type.classreading.MetadataReader;
4244
import org.springframework.core.type.classreading.MetadataReaderFactory;
4345
import org.springframework.core.type.filter.AnnotationTypeFilter;
@@ -59,6 +61,11 @@ public final class PersistenceManagedTypesScanner {
5961

6062
private static final String PACKAGE_INFO_SUFFIX = ".package-info";
6163

64+
private static final String IGNORE_CLASSFORMAT_PROPERTY_NAME = "spring.classformat.ignore";
65+
66+
private static final boolean shouldIgnoreClassFormatException =
67+
SpringProperties.getFlag(IGNORE_CLASSFORMAT_PROPERTY_NAME);
68+
6269
private static final Set<AnnotationTypeFilter> entityTypeFilters = new LinkedHashSet<>(4);
6370

6471
static {
@@ -79,6 +86,7 @@ public PersistenceManagedTypesScanner(ResourceLoader resourceLoader) {
7986
this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(resourceLoader.getClassLoader());
8087
}
8188

89+
8290
/**
8391
* Scan the specified packages and return a {@link PersistenceManagedTypes} that
8492
* represents the result of the scanning.
@@ -130,6 +138,14 @@ else if (className.endsWith(PACKAGE_INFO_SUFFIX)) {
130138
catch (FileNotFoundException ex) {
131139
// Ignore non-readable resource
132140
}
141+
catch (ClassFormatException ex) {
142+
if (!shouldIgnoreClassFormatException) {
143+
throw new PersistenceException("Incompatible class format in " + resource, ex);
144+
}
145+
}
146+
catch (Throwable ex) {
147+
throw new PersistenceException("Failed to read candidate component class: " + resource, ex);
148+
}
133149
}
134150
}
135151
catch (IOException ex) {
@@ -150,6 +166,7 @@ private boolean matchesFilter(MetadataReader reader, MetadataReaderFactory reade
150166
return false;
151167
}
152168

169+
153170
private static class ScanResult {
154171

155172
private final List<String> managedClassNames = new ArrayList<>();
@@ -163,6 +180,6 @@ PersistenceManagedTypes toJpaManagedTypes() {
163180
return new SimplePersistenceManagedTypes(this.managedClassNames,
164181
this.managedPackages, this.persistenceUnitRootUrl);
165182
}
166-
167183
}
184+
168185
}

0 commit comments

Comments
 (0)