Skip to content

Commit 4e1b9f1

Browse files
committed
Replace deep exception message nesting with custom inclusion of cause messages
Includes deprecation of NestedServletException, whereas NestedCheckedException and NestedRuntimeException remain as base classes with several convenience methods. Closes gh-25162
1 parent 933965b commit 4e1b9f1

File tree

31 files changed

+78
-245
lines changed

31 files changed

+78
-245
lines changed

Diff for: spring-beans/src/main/java/org/springframework/beans/TypeMismatchException.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-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.
@@ -71,7 +71,8 @@ public TypeMismatchException(PropertyChangeEvent propertyChangeEvent, @Nullable
7171
(requiredType != null ?
7272
" to required type '" + ClassUtils.getQualifiedName(requiredType) + "'" : "") +
7373
(propertyChangeEvent.getPropertyName() != null ?
74-
" for property '" + propertyChangeEvent.getPropertyName() + "'" : ""),
74+
" for property '" + propertyChangeEvent.getPropertyName() + "'" : "") +
75+
(cause != null ? "; " + cause.getMessage() : ""),
7576
cause);
7677
this.propertyName = propertyChangeEvent.getPropertyName();
7778
this.value = propertyChangeEvent.getNewValue();
@@ -97,7 +98,8 @@ public TypeMismatchException(@Nullable Object value, @Nullable Class<?> required
9798
*/
9899
public TypeMismatchException(@Nullable Object value, @Nullable Class<?> requiredType, @Nullable Throwable cause) {
99100
super("Failed to convert value of type '" + ClassUtils.getDescriptiveType(value) + "'" +
100-
(requiredType != null ? " to required type '" + ClassUtils.getQualifiedName(requiredType) + "'" : ""),
101+
(requiredType != null ? " to required type '" + ClassUtils.getQualifiedName(requiredType) + "'" : "") +
102+
(cause != null ? "; " + cause.getMessage() : ""),
101103
cause);
102104
this.value = value;
103105
this.requiredType = requiredType;

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-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.
@@ -62,7 +62,7 @@ public UnsatisfiedDependencyException(
6262
public UnsatisfiedDependencyException(
6363
@Nullable String resourceDescription, @Nullable String beanName, String propertyName, BeansException ex) {
6464

65-
this(resourceDescription, beanName, propertyName, "");
65+
this(resourceDescription, beanName, propertyName, ex.getMessage());
6666
initCause(ex);
6767
}
6868

@@ -94,7 +94,7 @@ public UnsatisfiedDependencyException(
9494
public UnsatisfiedDependencyException(
9595
@Nullable String resourceDescription, @Nullable String beanName, @Nullable InjectionPoint injectionPoint, BeansException ex) {
9696

97-
this(resourceDescription, beanName, injectionPoint, "");
97+
this(resourceDescription, beanName, injectionPoint, ex.getMessage());
9898
initCause(ex);
9999
}
100100

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyResourceConfigurer.java

+2-2
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-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.
@@ -86,7 +86,7 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
8686
processProperties(beanFactory, mergedProps);
8787
}
8888
catch (IOException ex) {
89-
throw new BeanInitializationException("Could not load properties", ex);
89+
throw new BeanInitializationException("Could not load properties: " + ex.getMessage(), ex);
9090
}
9191
}
9292

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java

+4-8
Original file line numberDiff line numberDiff line change
@@ -608,8 +608,7 @@ protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable
608608
throw (BeanCreationException) ex;
609609
}
610610
else {
611-
throw new BeanCreationException(
612-
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
611+
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
613612
}
614613
}
615614

@@ -1302,8 +1301,7 @@ protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
13021301
return bw;
13031302
}
13041303
catch (Throwable ex) {
1305-
throw new BeanCreationException(
1306-
mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
1304+
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
13071305
}
13081306
}
13091307

@@ -1699,8 +1697,7 @@ else if (convertible && originalValue instanceof TypedStringValue &&
16991697
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
17001698
}
17011699
catch (BeansException ex) {
1702-
throw new BeanCreationException(
1703-
mbd.getResourceDescription(), beanName, "Error setting property values", ex);
1700+
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
17041701
}
17051702
}
17061703

@@ -1752,8 +1749,7 @@ protected Object initializeBean(String beanName, Object bean, @Nullable RootBean
17521749
}
17531750
catch (Throwable ex) {
17541751
throw new BeanCreationException(
1755-
(mbd != null ? mbd.getResourceDescription() : null),
1756-
beanName, "Invocation of init method failed", ex);
1752+
(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
17571753
}
17581754
if (mbd == null || !mbd.isSynthetic()) {
17591755
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,7 @@ private Object instantiate(
303303
return strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
304304
}
305305
catch (Throwable ex) {
306-
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
307-
"Bean instantiation via constructor failed", ex);
306+
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
308307
}
309308
}
310309

@@ -631,8 +630,7 @@ private Object instantiate(String beanName, RootBeanDefinition mbd,
631630
mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args);
632631
}
633632
catch (Throwable ex) {
634-
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
635-
"Bean instantiation via factory method failed", ex);
633+
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
636634
}
637635
}
638636

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-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.
@@ -152,7 +152,7 @@ public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, Bean
152152
"Cannot access factory method '" + factoryMethod.getName() + "'; is it public?", ex);
153153
}
154154
catch (InvocationTargetException ex) {
155-
String msg = "Factory method '" + factoryMethod.getName() + "' threw exception";
155+
String msg = ex.getTargetException().getMessage();
156156
if (bd.getFactoryBeanName() != null && owner instanceof ConfigurableBeanFactory &&
157157
((ConfigurableBeanFactory) owner).isCurrentlyInCreation(bd.getFactoryBeanName())) {
158158
msg = "Circular reference involving containing bean '" + bd.getFactoryBeanName() + "' - consider " +

Diff for: spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
606606
catch (Throwable ex) {
607607
throw new BeanDefinitionStoreException(
608608
"Failed to process import candidates for configuration class [" +
609-
configClass.getMetadata().getClassName() + "]", ex);
609+
configClass.getMetadata().getClassName() + "]: " + ex.getMessage(), ex);
610610
}
611611
finally {
612612
this.importStack.pop();

Diff for: spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-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.
@@ -155,7 +155,7 @@ public void afterSingletonsInstantiated() {
155155
}
156156
catch (Throwable ex) {
157157
throw new BeanInitializationException("Failed to process @EventListener " +
158-
"annotation on bean with name '" + beanName + "'", ex);
158+
"annotation on bean with name '" + beanName + "': " + ex.getMessage(), ex);
159159
}
160160
}
161161
}

Diff for: spring-context/src/test/java/org/springframework/beans/factory/xml/XmlBeanFactoryTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -944,8 +944,8 @@ void relatedCausesFromConstructorResolution() {
944944
xbf.getBean("rod2Accessor");
945945
}
946946
catch (BeanCreationException ex) {
947-
assertThat(ex.toString().contains("touchy")).isTrue();
948947
ex.printStackTrace();
948+
assertThat(ex.toString().contains("touchy")).isTrue();
949949
assertThat((Object) ex.getRelatedCauses()).isNull();
950950
}
951951
}
@@ -1370,7 +1370,7 @@ void rejectsOverrideOfBogusMethodName() {
13701370
reader.loadBeanDefinitions(INVALID_NO_SUCH_METHOD_CONTEXT);
13711371
assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() ->
13721372
xbf.getBean("constructorOverrides"))
1373-
.withMessageContaining("bogusMethod");
1373+
.satisfies(ex -> ex.getCause().getMessage().contains("bogusMethod"));
13741374
}
13751375

13761376
@Test

Diff for: spring-context/src/test/java/org/springframework/context/annotation/Spr12278Tests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-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.
@@ -56,7 +56,7 @@ public void componentTwoConstructorsNoHint() {
5656
public void componentTwoSpecificConstructorsNoHint() {
5757
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
5858
new AnnotationConfigApplicationContext(BaseConfiguration.class, TwoSpecificConstructorsComponent.class))
59-
.withMessageContaining(NoSuchMethodException.class.getName());
59+
.withMessageContaining("No default constructor found");
6060
}
6161

6262

Diff for: spring-core/src/main/java/org/springframework/core/NestedCheckedException.java

-18
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,13 @@
3333
* @author Rod Johnson
3434
* @author Juergen Hoeller
3535
* @see #getMessage
36-
* @see #printStackTrace
3736
* @see NestedRuntimeException
3837
*/
3938
public abstract class NestedCheckedException extends Exception {
4039

4140
/** Use serialVersionUID from Spring 1.2 for interoperability. */
4241
private static final long serialVersionUID = 7100714597678207546L;
4342

44-
static {
45-
// Eagerly load the NestedExceptionUtils class to avoid classloader deadlock
46-
// issues on OSGi when calling getMessage(). Reported by Don Brown; SPR-5607.
47-
NestedExceptionUtils.class.getName();
48-
}
49-
5043

5144
/**
5245
* Construct a {@code NestedCheckedException} with the specified detail message.
@@ -67,17 +60,6 @@ public NestedCheckedException(@Nullable String msg, @Nullable Throwable cause) {
6760
}
6861

6962

70-
/**
71-
* Return the detail message, including the message from the nested exception
72-
* if there is one.
73-
*/
74-
@Override
75-
@Nullable
76-
public String getMessage() {
77-
return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
78-
}
79-
80-
8163
/**
8264
* Retrieve the innermost cause of this exception, if any.
8365
* @return the innermost exception, or {@code null} if none

Diff for: spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-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.
@@ -29,8 +29,6 @@
2929
* @since 2.0
3030
* @see NestedRuntimeException
3131
* @see NestedCheckedException
32-
* @see NestedIOException
33-
* @see org.springframework.web.util.NestedServletException
3432
*/
3533
public abstract class NestedExceptionUtils {
3634

@@ -39,7 +37,10 @@ public abstract class NestedExceptionUtils {
3937
* @param message the base message
4038
* @param cause the root cause
4139
* @return the full exception message
40+
* @deprecated as of 6.0, in favor of custom exception messages
41+
* with selective inclusion of cause messages
4242
*/
43+
@Deprecated
4344
@Nullable
4445
public static String buildMessage(@Nullable String message, @Nullable Throwable cause) {
4546
if (cause == null) {

Diff for: spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java

-18
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,13 @@
3333
* @author Rod Johnson
3434
* @author Juergen Hoeller
3535
* @see #getMessage
36-
* @see #printStackTrace
3736
* @see NestedCheckedException
3837
*/
3938
public abstract class NestedRuntimeException extends RuntimeException {
4039

4140
/** Use serialVersionUID from Spring 1.2 for interoperability. */
4241
private static final long serialVersionUID = 5439915454935047936L;
4342

44-
static {
45-
// Eagerly load the NestedExceptionUtils class to avoid classloader deadlock
46-
// issues on OSGi when calling getMessage(). Reported by Don Brown; SPR-5607.
47-
NestedExceptionUtils.class.getName();
48-
}
49-
5043

5144
/**
5245
* Construct a {@code NestedRuntimeException} with the specified detail message.
@@ -67,17 +60,6 @@ public NestedRuntimeException(@Nullable String msg, @Nullable Throwable cause) {
6760
}
6861

6962

70-
/**
71-
* Return the detail message, including the message from the nested exception
72-
* if there is one.
73-
*/
74-
@Override
75-
@Nullable
76-
public String getMessage() {
77-
return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
78-
}
79-
80-
8163
/**
8264
* Retrieve the innermost cause of this exception, if any.
8365
* @return the innermost exception, or {@code null} if none

0 commit comments

Comments
 (0)