Perform failure analysis of NoSuchMethodErrors

Closes gh-14040
This commit is contained in:
Andy Wilkinson 2018-08-10 12:33:13 +01:00
parent 2135f7fda4
commit 37bd286985
3 changed files with 173 additions and 0 deletions

View File

@ -0,0 +1,108 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.diagnostics.analyzer;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.util.ClassUtils;
/**
* An {@link AbstractFailureAnalyzer} that analyzes {@link NoSuchMethodError
* NoSuchMethodErrors}.
*
* @author Andy Wilkinson
*/
class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodError> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, NoSuchMethodError cause) {
String className = extractClassName(cause);
if (className == null) {
return null;
}
List<URL> candidates = findCandidates(className);
if (candidates == null) {
return null;
}
URL actual = getActual(className);
if (actual == null) {
return null;
}
StringWriter description = new StringWriter();
PrintWriter writer = new PrintWriter(description);
writer.print("An attempt was made to call the method ");
writer.print(cause.getMessage());
writer.print(" but it does not exist. Its class, ");
writer.print(className);
writer.println(", is available from the following locations:");
writer.println();
for (URL candidate : candidates) {
writer.print(" ");
writer.println(candidate);
}
writer.println();
writer.println("It was loaded from the following location:");
writer.println();
writer.print(" ");
writer.println(actual);
return new FailureAnalysis(description.toString(),
"Correct the classpath of your application so that it contains a single,"
+ " compatible version of " + className,
cause);
}
private String extractClassName(NoSuchMethodError cause) {
int descriptorIndex = cause.getMessage().indexOf('(');
if (descriptorIndex == -1) {
return null;
}
String classAndMethodName = cause.getMessage().substring(0, descriptorIndex);
int methodNameIndex = classAndMethodName.lastIndexOf('.');
if (methodNameIndex == -1) {
return null;
}
return classAndMethodName.substring(0, methodNameIndex);
}
private List<URL> findCandidates(String className) {
try {
return Collections.list((NoSuchMethodFailureAnalyzer.class.getClassLoader()
.getResources(ClassUtils.convertClassNameToResourcePath(className)
+ ".class")));
}
catch (Throwable ex) {
return null;
}
}
private URL getActual(String className) {
try {
return getClass().getClassLoader().loadClass(className).getProtectionDomain()
.getCodeSource().getLocation();
}
catch (Throwable ex) {
return null;
}
}
}

View File

@ -37,6 +37,7 @@ org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnal
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer

View File

@ -0,0 +1,64 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.diagnostics.analyzer;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.junit.runner.classpath.ClassPathOverrides;
import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* @author awilkinson
*/
@RunWith(ModifiedClassPathRunner.class)
@ClassPathOverrides("javax.servlet:servlet-api:2.5")
public class NoSuchMethodFailureAnalyzerTests {
@Test
public void noSuchMethodErrorIsAnalyzed() {
Throwable failure = createFailure();
assertThat(failure).isNotNull();
FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure);
assertThat(analysis).isNotNull();
assertThat(analysis.getDescription())
.contains("the method javax.servlet.ServletContext.addServlet"
+ "(Ljava/lang/String;Ljavax/servlet/Servlet;)"
+ "Ljavax/servlet/ServletRegistration$Dynamic;")
.contains("class, javax.servlet.ServletContext,");
}
private Throwable createFailure() {
try {
ServletContext servletContext = mock(ServletContext.class);
servletContext.addServlet("example", new HttpServlet() {
});
return null;
}
catch (Throwable ex) {
return ex;
}
}
}