Browse Source

Support direct matching against exceptions in ExceptionTypeFilter

Prior to this commit, ExceptionTypeFilter only supported matching
against an exception type. However, most use cases involve matching
against an exception instance. Moreover, every use case within the core
Spring Framework uses ExceptionTypeFilter to match against concrete
exception instances.

This commit therefore introduces an overloaded match(Throwable) method
in ExceptionTypeFilter in order to provide support for the most common
use cases.

See gh-35109
Closes gh-35160
pull/34709/head
Sam Brannen 6 months ago
parent
commit
17df4b4c38
  1. 2
      spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutInterceptor.java
  2. 2
      spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java
  3. 8
      spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java
  4. 2
      spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java
  5. 2
      spring-context/src/main/java/org/springframework/resilience/retry/MethodRetrySpec.java
  6. 2
      spring-core/src/main/java/org/springframework/core/retry/DefaultRetryPolicy.java
  7. 10
      spring-core/src/main/java/org/springframework/util/ExceptionTypeFilter.java
  8. 74
      spring-core/src/test/java/org/springframework/util/ExceptionTypeFilterTests.java

2
spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CachePutInterceptor.java vendored

@ -62,7 +62,7 @@ class CachePutInterceptor extends AbstractKeyCacheInterceptor<CachePutOperation, @@ -62,7 +62,7 @@ class CachePutInterceptor extends AbstractKeyCacheInterceptor<CachePutOperation,
}
catch (CacheOperationInvoker.ThrowableWrapper ex) {
Throwable original = ex.getOriginal();
if (!earlyPut && operation.getExceptionTypeFilter().match(original.getClass())) {
if (!earlyPut && operation.getExceptionTypeFilter().match(original)) {
cacheValue(context, value);
}
throw ex;

2
spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java vendored

@ -58,7 +58,7 @@ class CacheRemoveAllInterceptor extends AbstractCacheInterceptor<CacheRemoveAllO @@ -58,7 +58,7 @@ class CacheRemoveAllInterceptor extends AbstractCacheInterceptor<CacheRemoveAllO
}
catch (CacheOperationInvoker.ThrowableWrapper ex) {
Throwable original = ex.getOriginal();
if (!earlyRemove && operation.getExceptionTypeFilter().match(original.getClass())) {
if (!earlyRemove && operation.getExceptionTypeFilter().match(original)) {
removeAll(context);
}
throw ex;

8
spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java vendored

@ -56,12 +56,12 @@ class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor<CacheRemov @@ -56,12 +56,12 @@ class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor<CacheRemov
}
return result;
}
catch (CacheOperationInvoker.ThrowableWrapper wrapperException) {
Throwable ex = wrapperException.getOriginal();
if (!earlyRemove && operation.getExceptionTypeFilter().match(ex.getClass())) {
catch (CacheOperationInvoker.ThrowableWrapper ex) {
Throwable original = ex.getOriginal();
if (!earlyRemove && operation.getExceptionTypeFilter().match(original)) {
removeValue(context);
}
throw wrapperException;
throw ex;
}
}

2
spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheResultInterceptor.java vendored

@ -92,7 +92,7 @@ class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheResultOper @@ -92,7 +92,7 @@ class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheResultOper
if (exceptionCache == null) {
return;
}
if (filter.match(ex.getClass())) {
if (filter.match(ex)) {
doPut(exceptionCache, cacheKey, ex);
}
}

2
spring-context/src/main/java/org/springframework/resilience/retry/MethodRetrySpec.java

@ -65,7 +65,7 @@ public record MethodRetrySpec( @@ -65,7 +65,7 @@ public record MethodRetrySpec(
MethodRetryPredicate combinedPredicate() {
ExceptionTypeFilter exceptionFilter = new ExceptionTypeFilter(this.includes, this.excludes);
return (method, throwable) -> exceptionFilter.match(throwable.getClass()) &&
return (method, throwable) -> exceptionFilter.match(throwable) &&
this.predicate.shouldRetry(method, throwable);
}

2
spring-core/src/main/java/org/springframework/core/retry/DefaultRetryPolicy.java

@ -59,7 +59,7 @@ class DefaultRetryPolicy implements RetryPolicy { @@ -59,7 +59,7 @@ class DefaultRetryPolicy implements RetryPolicy {
@Override
public boolean shouldRetry(Throwable throwable) {
return this.exceptionFilter.match(throwable.getClass()) &&
return this.exceptionFilter.match(throwable) &&
(this.predicate == null || this.predicate.test(throwable));
}

10
spring-core/src/main/java/org/springframework/util/ExceptionTypeFilter.java

@ -64,6 +64,16 @@ public class ExceptionTypeFilter extends InstanceFilter<Class<? extends Throwabl @@ -64,6 +64,16 @@ public class ExceptionTypeFilter extends InstanceFilter<Class<? extends Throwabl
}
/**
* Determine if the type of the supplied {@code exception} matches this filter.
* @since 7.0
* @see InstanceFilter#match(Object)
*/
public boolean match(Throwable exception) {
return match(exception.getClass());
}
/**
* Determine if the specified {@code instance} matches the specified
* {@code candidate}.

74
spring-core/src/test/java/org/springframework/util/ExceptionTypeFilterTests.java

@ -33,81 +33,83 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -33,81 +33,83 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class ExceptionTypeFilterTests {
ExceptionTypeFilter filter;
@Test
void emptyFilter() {
var filter = new ExceptionTypeFilter(null, null);
filter = new ExceptionTypeFilter(null, null);
assertMatches(filter, Throwable.class);
assertMatches(filter, Error.class);
assertMatches(filter, Exception.class);
assertMatches(filter, RuntimeException.class);
assertMatches(new Throwable());
assertMatches(new Error());
assertMatches(new Exception());
assertMatches(new RuntimeException());
}
@Test
void includes() {
var filter = new ExceptionTypeFilter(List.of(FileNotFoundException.class, IllegalArgumentException.class), null);
filter = new ExceptionTypeFilter(List.of(FileNotFoundException.class, IllegalArgumentException.class), null);
assertMatches(filter, FileNotFoundException.class);
assertMatches(filter, IllegalArgumentException.class);
assertMatches(filter, NumberFormatException.class);
assertMatches(new FileNotFoundException());
assertMatches(new IllegalArgumentException());
assertMatches(new NumberFormatException());
assertDoesNotMatch(filter, Throwable.class);
assertDoesNotMatch(filter, FileSystemException.class);
assertDoesNotMatch(new Throwable());
assertDoesNotMatch(new FileSystemException("test"));
}
@Test
void includesSubtypeMatching() {
var filter = new ExceptionTypeFilter(List.of(RuntimeException.class), null);
filter = new ExceptionTypeFilter(List.of(RuntimeException.class), null);
assertMatches(filter, RuntimeException.class);
assertMatches(filter, IllegalStateException.class);
assertMatches(new RuntimeException());
assertMatches(new IllegalStateException());
assertDoesNotMatch(filter, Exception.class);
assertDoesNotMatch(new Exception());
}
@Test
void excludes() {
var filter = new ExceptionTypeFilter(null, List.of(FileNotFoundException.class, IllegalArgumentException.class));
filter = new ExceptionTypeFilter(null, List.of(FileNotFoundException.class, IllegalArgumentException.class));
assertDoesNotMatch(filter, FileNotFoundException.class);
assertDoesNotMatch(filter, IllegalArgumentException.class);
assertDoesNotMatch(new FileNotFoundException());
assertDoesNotMatch(new IllegalArgumentException());
assertMatches(filter, Throwable.class);
assertMatches(filter, AssertionError.class);
assertMatches(filter, FileSystemException.class);
assertMatches(new Throwable());
assertMatches(new AssertionError());
assertMatches(new FileSystemException("test"));
}
@Test
void excludesSubtypeMatching() {
var filter = new ExceptionTypeFilter(null, List.of(IllegalArgumentException.class));
filter = new ExceptionTypeFilter(null, List.of(IllegalArgumentException.class));
assertDoesNotMatch(filter, IllegalArgumentException.class);
assertDoesNotMatch(filter, NumberFormatException.class);
assertDoesNotMatch(new IllegalArgumentException());
assertDoesNotMatch(new NumberFormatException());
assertMatches(filter, Throwable.class);
assertMatches(new Throwable());
}
@Test
void includesAndExcludes() {
var filter = new ExceptionTypeFilter(List.of(IOException.class), List.of(FileNotFoundException.class));
filter = new ExceptionTypeFilter(List.of(IOException.class), List.of(FileNotFoundException.class));
assertMatches(filter, IOException.class);
assertMatches(filter, FileSystemException.class);
assertMatches(new IOException());
assertMatches(new FileSystemException("test"));
assertDoesNotMatch(filter, FileNotFoundException.class);
assertDoesNotMatch(filter, Throwable.class);
assertDoesNotMatch(new FileNotFoundException());
assertDoesNotMatch(new Throwable());
}
private static void assertMatches(ExceptionTypeFilter filter, Class<? extends Throwable> candidate) {
assertThat(filter.match(candidate))
.as("filter '" + filter + "' should match " + candidate.getSimpleName())
private void assertMatches(Throwable candidate) {
assertThat(this.filter.match(candidate))
.as("filter '" + this.filter + "' should match " + candidate.getClass().getSimpleName())
.isTrue();
}
private static void assertDoesNotMatch(ExceptionTypeFilter filter, Class<? extends Throwable> candidate) {
assertThat(filter.match(candidate))
.as("filter '" + filter + "' should not match " + candidate.getSimpleName())
private void assertDoesNotMatch(Throwable candidate) {
assertThat(this.filter.match(candidate))
.as("filter '" + this.filter + "' should not match " + candidate.getClass().getSimpleName())
.isFalse();
}

Loading…
Cancel
Save