Skip to content

Commit 37bd286

Browse files
committed
Perform failure analysis of NoSuchMethodErrors
Closes gh-14040
1 parent 2135f7f commit 37bd286

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2012-2018 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+
* http://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.boot.diagnostics.analyzer;
18+
19+
import java.io.PrintWriter;
20+
import java.io.StringWriter;
21+
import java.net.URL;
22+
import java.util.Collections;
23+
import java.util.List;
24+
25+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
26+
import org.springframework.boot.diagnostics.FailureAnalysis;
27+
import org.springframework.util.ClassUtils;
28+
29+
/**
30+
* An {@link AbstractFailureAnalyzer} that analyzes {@link NoSuchMethodError
31+
* NoSuchMethodErrors}.
32+
*
33+
* @author Andy Wilkinson
34+
*/
35+
class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodError> {
36+
37+
@Override
38+
protected FailureAnalysis analyze(Throwable rootFailure, NoSuchMethodError cause) {
39+
String className = extractClassName(cause);
40+
if (className == null) {
41+
return null;
42+
}
43+
List<URL> candidates = findCandidates(className);
44+
if (candidates == null) {
45+
return null;
46+
}
47+
URL actual = getActual(className);
48+
if (actual == null) {
49+
return null;
50+
}
51+
StringWriter description = new StringWriter();
52+
PrintWriter writer = new PrintWriter(description);
53+
writer.print("An attempt was made to call the method ");
54+
writer.print(cause.getMessage());
55+
writer.print(" but it does not exist. Its class, ");
56+
writer.print(className);
57+
writer.println(", is available from the following locations:");
58+
writer.println();
59+
for (URL candidate : candidates) {
60+
writer.print(" ");
61+
writer.println(candidate);
62+
}
63+
writer.println();
64+
writer.println("It was loaded from the following location:");
65+
writer.println();
66+
writer.print(" ");
67+
writer.println(actual);
68+
return new FailureAnalysis(description.toString(),
69+
"Correct the classpath of your application so that it contains a single,"
70+
+ " compatible version of " + className,
71+
cause);
72+
}
73+
74+
private String extractClassName(NoSuchMethodError cause) {
75+
int descriptorIndex = cause.getMessage().indexOf('(');
76+
if (descriptorIndex == -1) {
77+
return null;
78+
}
79+
String classAndMethodName = cause.getMessage().substring(0, descriptorIndex);
80+
int methodNameIndex = classAndMethodName.lastIndexOf('.');
81+
if (methodNameIndex == -1) {
82+
return null;
83+
}
84+
return classAndMethodName.substring(0, methodNameIndex);
85+
}
86+
87+
private List<URL> findCandidates(String className) {
88+
try {
89+
return Collections.list((NoSuchMethodFailureAnalyzer.class.getClassLoader()
90+
.getResources(ClassUtils.convertClassNameToResourcePath(className)
91+
+ ".class")));
92+
}
93+
catch (Throwable ex) {
94+
return null;
95+
}
96+
}
97+
98+
private URL getActual(String className) {
99+
try {
100+
return getClass().getClassLoader().loadClass(className).getProtectionDomain()
101+
.getCodeSource().getLocation();
102+
}
103+
catch (Throwable ex) {
104+
return null;
105+
}
106+
}
107+
108+
}

spring-boot/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnal
3737
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
3838
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
3939
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
40+
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
4041
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
4142
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
4243
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2012-2018 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+
* http://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.boot.diagnostics.analyzer;
18+
19+
import javax.servlet.ServletContext;
20+
import javax.servlet.http.HttpServlet;
21+
22+
import org.junit.Test;
23+
import org.junit.runner.RunWith;
24+
25+
import org.springframework.boot.diagnostics.FailureAnalysis;
26+
import org.springframework.boot.junit.runner.classpath.ClassPathOverrides;
27+
import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.mockito.Mockito.mock;
31+
32+
/**
33+
* @author awilkinson
34+
*/
35+
@RunWith(ModifiedClassPathRunner.class)
36+
@ClassPathOverrides("javax.servlet:servlet-api:2.5")
37+
public class NoSuchMethodFailureAnalyzerTests {
38+
39+
@Test
40+
public void noSuchMethodErrorIsAnalyzed() {
41+
Throwable failure = createFailure();
42+
assertThat(failure).isNotNull();
43+
FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure);
44+
assertThat(analysis).isNotNull();
45+
assertThat(analysis.getDescription())
46+
.contains("the method javax.servlet.ServletContext.addServlet"
47+
+ "(Ljava/lang/String;Ljavax/servlet/Servlet;)"
48+
+ "Ljavax/servlet/ServletRegistration$Dynamic;")
49+
.contains("class, javax.servlet.ServletContext,");
50+
}
51+
52+
private Throwable createFailure() {
53+
try {
54+
ServletContext servletContext = mock(ServletContext.class);
55+
servletContext.addServlet("example", new HttpServlet() {
56+
});
57+
return null;
58+
}
59+
catch (Throwable ex) {
60+
return ex;
61+
}
62+
}
63+
64+
}

0 commit comments

Comments
 (0)