Skip to content

Commit c27de92

Browse files
authored
ExceptionClassifier Reverse Option
Option to not retry by default and retry specific. * Polishing and Tests In preparation for #2124
1 parent b198c5b commit c27de92

File tree

4 files changed

+119
-8
lines changed

4 files changed

+119
-8
lines changed

Diff for: spring-kafka/src/main/java/org/springframework/kafka/listener/ExceptionClassifier.java

+59-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 the original author or authors.
2+
* Copyright 2021-2022 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.
@@ -55,6 +55,16 @@ private static ExtendedBinaryExceptionClassifier configureDefaultClassifier() {
5555
return new ExtendedBinaryExceptionClassifier(classified, true);
5656
}
5757

58+
/**
59+
* By default, unmatched types classify as true. Call this method to make the default
60+
* false, and remove types explicitly classified as false. This should be called before
61+
* calling any of the classification modification methods.
62+
* @since 2.8.4
63+
*/
64+
public void defaultFalse() {
65+
this.classifier = new ExtendedBinaryExceptionClassifier(new HashMap<>(), false);
66+
}
67+
5868
/**
5969
* Return the exception classifier.
6070
* @return the classifier.
@@ -97,20 +107,39 @@ public void setClassifications(Map<Class<? extends Throwable>, Boolean> classifi
97107
* <li>{@link NoSuchMethodException}</li>
98108
* <li>{@link ClassCastException}</li>
99109
* </ul>
100-
* All others will be retried.
110+
* All others will be retried, unless {@link #defaultFalse()} has been called.
101111
* @param exceptionTypes the exception types.
102-
* @see #removeNotRetryableException(Class)
112+
* @see #removeClassification(Class)
103113
* @see #setClassifications(Map, boolean)
104114
*/
105115
@SafeVarargs
106116
@SuppressWarnings("varargs")
107117
public final void addNotRetryableExceptions(Class<? extends Exception>... exceptionTypes) {
118+
add(false, exceptionTypes);
119+
}
120+
121+
/**
122+
* Add exception types that can be retried. Call this after {@link #defaultFalse()} to
123+
* specify those exception types that should be classified as true.
124+
* All others will be retried, unless {@link #defaultFalse()} has been called.
125+
* @param exceptionTypes the exception types.
126+
* @since 2.8.4
127+
* @see #removeClassification(Class)
128+
* @see #setClassifications(Map, boolean)
129+
*/
130+
@SafeVarargs
131+
@SuppressWarnings("varargs")
132+
public final void addRetryableExceptions(Class<? extends Exception>... exceptionTypes) {
133+
add(true, exceptionTypes);
134+
}
135+
136+
private void add(boolean classified, Class<? extends Exception>... exceptionTypes) {
108137
Assert.notNull(exceptionTypes, "'exceptionTypes' cannot be null");
109138
Assert.noNullElements(exceptionTypes, "'exceptionTypes' cannot contain nulls");
110139
for (Class<? extends Exception> exceptionType : exceptionTypes) {
111140
Assert.isTrue(Exception.class.isAssignableFrom(exceptionType),
112141
() -> "exceptionType " + exceptionType + " must be an Exception");
113-
this.classifier.getClassified().put(exceptionType, false);
142+
this.classifier.getClassified().put(exceptionType, classified);
114143
}
115144
}
116145

@@ -125,13 +154,38 @@ public final void addNotRetryableExceptions(Class<? extends Exception>... except
125154
* <li>{@link NoSuchMethodException}</li>
126155
* <li>{@link ClassCastException}</li>
127156
* </ul>
128-
* All others will be retried.
157+
* All others will be retried, unless {@link #defaultFalse()} has been called.
129158
* @param exceptionType the exception type.
130159
* @return true if the removal was successful.
160+
* @deprecated in favor of {@link #removeClassification(Class)}
131161
* @see #addNotRetryableExceptions(Class...)
132162
* @see #setClassifications(Map, boolean)
163+
* @see #defaultFalse()
133164
*/
165+
@Deprecated
134166
public boolean removeNotRetryableException(Class<? extends Exception> exceptionType) {
167+
return this.removeClassification(exceptionType);
168+
}
169+
170+
/**
171+
* Remove an exception type from the configured list. By default, the following
172+
* exceptions will not be retried:
173+
* <ul>
174+
* <li>{@link DeserializationException}</li>
175+
* <li>{@link MessageConversionException}</li>
176+
* <li>{@link ConversionException}</li>
177+
* <li>{@link MethodArgumentResolutionException}</li>
178+
* <li>{@link NoSuchMethodException}</li>
179+
* <li>{@link ClassCastException}</li>
180+
* </ul>
181+
* All others will be retried, unless {@link #defaultFalse()} has been called.
182+
* @param exceptionType the exception type.
183+
* @return true if the removal was successful.
184+
* @since 2.8.4
185+
* @see #addNotRetryableExceptions(Class...)
186+
* @see #setClassifications(Map, boolean)
187+
*/
188+
public boolean removeClassification(Class<? extends Exception> exceptionType) {
135189
return this.classifier.getClassified().remove(exceptionType);
136190
}
137191

Diff for: spring-kafka/src/main/java/org/springframework/kafka/retrytopic/DeadLetterPublishingRecovererFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ protected DeadLetterPublishingRecoverer.HeaderNames getHeaderNames() {
143143
recoverer.setSkipSameTopicFatalExceptions(false);
144144
this.recovererCustomizer.accept(recoverer);
145145
this.fatalExceptions.forEach(recoverer::addNotRetryableExceptions);
146-
this.nonFatalExceptions.forEach(recoverer::removeNotRetryableException);
146+
this.nonFatalExceptions.forEach(recoverer::removeClassification);
147147
return recoverer;
148148
}
149149

Diff for: spring-kafka/src/test/java/org/springframework/kafka/listener/DeadLetterPublishingRecovererTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ void noCircularRoutingIfFatal() {
559559
recoverer.addNotRetryableExceptions(IllegalStateException.class);
560560
recoverer.accept(record, new IllegalStateException());
561561
verify(template, never()).send(any(ProducerRecord.class));
562-
recoverer.removeNotRetryableException(IllegalStateException.class);
562+
recoverer.removeClassification(IllegalStateException.class);
563563
recoverer.setFailIfSendResultIsError(false);
564564
recoverer.accept(record, new IllegalStateException());
565565
verify(template).send(any(ProducerRecord.class));
@@ -580,7 +580,7 @@ void doNotSkipCircularFatalIfSet() {
580580
recoverer.addNotRetryableExceptions(IllegalStateException.class);
581581
recoverer.accept(record, new IllegalStateException());
582582
verify(template, times(2)).send(any(ProducerRecord.class));
583-
recoverer.removeNotRetryableException(IllegalStateException.class);
583+
recoverer.removeClassification(IllegalStateException.class);
584584
recoverer.setFailIfSendResultIsError(false);
585585
recoverer.accept(record, new IllegalStateException());
586586
verify(template, times(3)).send(any(ProducerRecord.class));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2022 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.kafka.listener;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
/**
24+
* @author Gary Russell
25+
* @since 2.8.4
26+
*
27+
*/
28+
public class ExceptionClassifierTests {
29+
30+
@Test
31+
void testDefault() {
32+
ExceptionClassifier ec = new ExceptionClassifier() {
33+
};
34+
assertThat(ec.getClassifier().classify(new Exception())).isTrue();
35+
assertThat(ec.getClassifier().classify(new ClassCastException())).isFalse();
36+
ec.removeClassification(ClassCastException.class);
37+
assertThat(ec.getClassifier().classify(new ClassCastException())).isTrue();
38+
assertThat(ec.getClassifier().classify(new IllegalStateException())).isTrue();
39+
ec.addNotRetryableExceptions(IllegalStateException.class);
40+
assertThat(ec.getClassifier().classify(new IllegalStateException())).isFalse();
41+
}
42+
43+
@Test
44+
void testDefaultFalse() {
45+
ExceptionClassifier ec = new ExceptionClassifier() {
46+
};
47+
assertThat(ec.getClassifier().classify(new Exception())).isTrue();
48+
ec.defaultFalse();
49+
assertThat(ec.getClassifier().classify(new Exception())).isFalse();
50+
assertThat(ec.getClassifier().classify(new IllegalStateException())).isFalse();
51+
ec.addRetryableExceptions(IllegalStateException.class);
52+
assertThat(ec.getClassifier().classify(new IllegalStateException())).isTrue();
53+
ec.removeClassification(IllegalStateException.class);
54+
assertThat(ec.getClassifier().classify(new IllegalStateException())).isFalse();
55+
}
56+
57+
}

0 commit comments

Comments
 (0)