diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle index 502f707260..d5641f0cc8 100644 --- a/spring-boot-project/spring-boot/build.gradle +++ b/spring-boot-project/spring-boot/build.gradle @@ -100,6 +100,7 @@ dependencies { testImplementation("org.postgresql:postgresql") testImplementation("org.springframework:spring-context-support") testImplementation("org.springframework.data:spring-data-redis") + testImplementation("org.springframework.data:spring-data-r2dbc") testImplementation("org.xerial:sqlite-jdbc") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzer.java index d1bef42354..ad73fc21ff 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzer.java @@ -19,6 +19,7 @@ package org.springframework.boot.diagnostics.analyzer; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URL; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -58,11 +59,15 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer type = load(className); + if (type == null) { return null; } - return new NoSuchMethodDescriptor(message, className, candidates, actual); + List typeHierarchy = getTypeHierarchy(type); + if (typeHierarchy == null) { + return null; + } + return new NoSuchMethodDescriptor(message, className, candidates, typeHierarchy); } private String cleanMessage(String message) { @@ -104,10 +109,24 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer load(String className) { try { - return Class.forName(className, false, getClass().getClassLoader()).getProtectionDomain().getCodeSource() - .getLocation(); + return Class.forName(className, false, getClass().getClassLoader()); + } + catch (Throwable ex) { + return null; + } + } + + private List getTypeHierarchy(Class type) { + try { + List typeHierarchy = new ArrayList<>(); + while (type != null && !type.equals(Object.class)) { + typeHierarchy.add(new ClassDescriptor(type.getCanonicalName(), + type.getProtectionDomain().getCodeSource().getLocation())); + type = type.getSuperclass(); + } + return typeHierarchy; } catch (Throwable ex) { return null; @@ -136,10 +155,15 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer candidateLocations; - private final URL actualLocation; + private final List typeHierarchy; public NoSuchMethodDescriptor(String errorMessage, String className, List candidateLocations, - URL actualLocation) { + List typeHierarchy) { this.errorMessage = errorMessage; this.className = className; this.candidateLocations = candidateLocations; - this.actualLocation = actualLocation; + this.typeHierarchy = typeHierarchy; } public String getErrorMessage() { @@ -173,8 +197,29 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer getTypeHierarchy() { + return this.typeHierarchy; + } + + } + + protected static class ClassDescriptor { + + private final String name; + + private final URL location; + + public ClassDescriptor(String name, URL location) { + this.name = name; + this.location = location; + } + + public String getName() { + return this.name; + } + + public URL getLocation() { + return this.location; } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzerTests.java index 5b44c5f161..a165ca3305 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -16,14 +16,18 @@ package org.springframework.boot.diagnostics.analyzer; +import java.util.List; + import javax.servlet.ServletContext; import javax.servlet.http.HttpServlet; import org.junit.jupiter.api.Test; import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer.ClassDescriptor; import org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer.NoSuchMethodDescriptor; import org.springframework.boot.testsupport.classpath.ClassPathOverrides; +import org.springframework.data.r2dbc.mapping.R2dbcMappingContext; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -34,7 +38,8 @@ import static org.mockito.Mockito.mock; * @author Andy Wilkinson * @author Stephane Nicoll */ -@ClassPathOverrides("javax.servlet:servlet-api:2.5") +@ClassPathOverrides({ "javax.servlet:servlet-api:2.5", + "org.springframework.data:spring-data-relational:1.1.7.RELEASE" }) class NoSuchMethodFailureAnalyzerTests { @Test @@ -48,7 +53,9 @@ class NoSuchMethodFailureAnalyzerTests { + "Ljavax/servlet/ServletRegistration$Dynamic;"); assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext"); assertThat(descriptor.getCandidateLocations().size()).isGreaterThan(1); - assertThat(descriptor.getActualLocation()).asString().contains("servlet-api-2.5.jar"); + List typeHierarchy = descriptor.getTypeHierarchy(); + assertThat(typeHierarchy).hasSize(1); + assertThat(typeHierarchy.get(0).getLocation()).asString().contains("servlet-api-2.5.jar"); } @Test @@ -62,7 +69,9 @@ class NoSuchMethodFailureAnalyzerTests { + "Ljavax/servlet/ServletRegistration$Dynamic;"); assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext"); assertThat(descriptor.getCandidateLocations().size()).isGreaterThan(1); - assertThat(descriptor.getActualLocation()).asString().contains("servlet-api-2.5.jar"); + List typeHierarchy = descriptor.getTypeHierarchy(); + assertThat(typeHierarchy).hasSize(1); + assertThat(typeHierarchy.get(0).getLocation()).asString().contains("servlet-api-2.5.jar"); } @Test @@ -76,11 +85,13 @@ class NoSuchMethodFailureAnalyzerTests { + "java.lang.String, javax.servlet.Servlet)'"); assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext"); assertThat(descriptor.getCandidateLocations().size()).isGreaterThan(1); - assertThat(descriptor.getActualLocation()).asString().contains("servlet-api-2.5.jar"); + List typeHierarchy = descriptor.getTypeHierarchy(); + assertThat(typeHierarchy).hasSize(1); + assertThat(typeHierarchy.get(0).getLocation()).asString().contains("servlet-api-2.5.jar"); } @Test - void noSuchMethodErrorIsAnalyzed() { + void whenAMethodOnAnInterfaceIsMissingThenNoSuchMethodErrorIsAnalyzed() { Throwable failure = createFailure(); assertThat(failure).isNotNull(); FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure); @@ -90,6 +101,20 @@ class NoSuchMethodFailureAnalyzerTests { .contains("class, javax.servlet.ServletContext,"); } + @Test + void whenAnInheritedMethodIsMissingThenNoSuchMethodErrorIsAnalyzed() { + Throwable failure = createFailureForMissingInheritedMethod(); + assertThat(failure).isNotNull(); + FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure); + assertThat(analysis).isNotNull(); + assertThat(analysis.getDescription()).contains(R2dbcMappingContext.class.getName() + ".(") + .contains("setForceQuote(Z)V") + .contains("class, org.springframework.data.r2dbc.mapping.R2dbcMappingContext,") + .contains(" org.springframework.data.r2dbc.mapping.R2dbcMappingContext") + .contains(" org.springframework.data.relational.core.mapping.RelationalMappingContext") + .contains(" org.springframework.data.mapping.context.AbstractMappingContext"); + } + private Throwable createFailure() { try { ServletContext servletContext = mock(ServletContext.class); @@ -102,4 +127,14 @@ class NoSuchMethodFailureAnalyzerTests { } } + private Throwable createFailureForMissingInheritedMethod() { + try { + new R2dbcMappingContext(); + return null; + } + catch (Throwable ex) { + return ex; + } + } + }