diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/MissingWebServerFactoryBeanException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/MissingWebServerFactoryBeanException.java new file mode 100644 index 0000000000..5585fc49d4 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/MissingWebServerFactoryBeanException.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2022 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 + * + * https://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.web.context; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.web.server.WebServerFactory; + +/** + * Exception thrown when there is no {@link WebServerFactory} bean of the required type + * defined in a {@link WebServerApplicationContext}. + * + * @author Guirong Hu + * @author Andy Wilkinson + * @since 2.7.0 + */ +public class MissingWebServerFactoryBeanException extends NoSuchBeanDefinitionException { + + private final WebApplicationType webApplicationType; + + /** + * Create a new {@code MissingWebServerFactoryBeanException}. + * @param webServerApplicationContextClass the class of the + * WebServerApplicationContext that required the WebServerFactory + * @param webServerFactoryClass the class of the WebServerFactory that was missing + * @param webApplicationType the type of the web application + */ + public MissingWebServerFactoryBeanException( + Class webServerApplicationContextClass, + Class webServerFactoryClass, WebApplicationType webApplicationType) { + super(webServerFactoryClass, String.format("Unable to start %s due to missing %s bean", + webServerApplicationContextClass.getSimpleName(), webServerFactoryClass.getSimpleName())); + this.webApplicationType = webApplicationType; + } + + /** + * Returns the type of web application for which a {@link WebServerFactory} bean was + * missing. + * @return the type of web application + */ + public WebApplicationType getWebApplicationType() { + return this.webApplicationType; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/MissingWebServerFactoryBeanFailureAnalyzer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/MissingWebServerFactoryBeanFailureAnalyzer.java new file mode 100644 index 0000000000..bea77b2cfb --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/context/MissingWebServerFactoryBeanFailureAnalyzer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2022 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 + * + * https://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.web.context; + +import java.util.Locale; + +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.diagnostics.FailureAnalyzer; +import org.springframework.core.annotation.Order; + +/** + * A {@link FailureAnalyzer} that performs analysis of failures caused by an + * {@link MissingWebServerFactoryBeanException}. + * + * @author Guirong Hu + * @author Andy Wilkinson + */ +@Order(0) +class MissingWebServerFactoryBeanFailureAnalyzer extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, MissingWebServerFactoryBeanException cause) { + return new FailureAnalysis( + "Web application could not be started as there was no " + cause.getBeanType().getName() + + " bean defined in the context.", + "Check your application's dependencies for a supported " + + cause.getWebApplicationType().name().toLowerCase(Locale.ENGLISH) + " web server.\n" + + "Check the configured web application type.", + cause); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java index 5a088e802f..8871149c10 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -18,9 +18,11 @@ package org.springframework.boot.web.reactive.context; import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.ReadinessState; import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext; +import org.springframework.boot.web.context.MissingWebServerFactoryBeanException; import org.springframework.boot.web.context.WebServerGracefulShutdownLifecycle; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.boot.web.server.WebServer; @@ -105,8 +107,8 @@ public class ReactiveWebServerApplicationContext extends GenericReactiveWebAppli // Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory().getBeanNamesForType(ReactiveWebServerFactory.class); if (beanNames.length == 0) { - throw new ApplicationContextException( - "Unable to start ReactiveWebApplicationContext due to missing ReactiveWebServerFactory bean."); + throw new MissingWebServerFactoryBeanException(this.getClass(), ReactiveWebServerFactory.class, + WebApplicationType.REACTIVE); } if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ReactiveWebApplicationContext due to multiple " diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java index 9da658ccc9..8b5793c470 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java @@ -36,9 +36,11 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.Scope; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.ReadinessState; import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext; +import org.springframework.boot.web.context.MissingWebServerFactoryBeanException; import org.springframework.boot.web.context.WebServerGracefulShutdownLifecycle; import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -206,8 +208,8 @@ public class ServletWebServerApplicationContext extends GenericWebApplicationCon // Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { - throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " - + "ServletWebServerFactory bean."); + throw new MissingWebServerFactoryBeanException(this.getClass(), ServletWebServerFactory.class, + WebApplicationType.SERVLET); } if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index 95f7013b78..b4f0b3837e 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -74,7 +74,8 @@ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFa org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.PatternParseFailureAnalyzer,\ org.springframework.boot.liquibase.LiquibaseChangelogMissingFailureAnalyzer,\ -org.springframework.boot.web.embedded.tomcat.ConnectorStartFailureAnalyzer +org.springframework.boot.web.context.MissingWebServerFactoryBeanFailureAnalyzer,\ +org.springframework.boot.web.embedded.tomcat.ConnectorStartFailureAnalyzer,\ # Failure Analysis Reporters org.springframework.boot.diagnostics.FailureAnalysisReporter=\ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/context/MissingWebServerFactoryBeanFailureAnalyzerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/context/MissingWebServerFactoryBeanFailureAnalyzerTests.java new file mode 100644 index 0000000000..fb30be29ca --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/context/MissingWebServerFactoryBeanFailureAnalyzerTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2022 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 + * + * https://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.web.context; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; +import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; +import org.springframework.context.ApplicationContextException; +import org.springframework.context.ConfigurableApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MissingWebServerFactoryBeanFailureAnalyzer}. + * + * @author Guirong Hu + * @author Andy Wilkinson + */ +class MissingWebServerFactoryBeanFailureAnalyzerTests { + + @Test + void missingServletWebServerFactoryBeanFailure() { + ApplicationContextException failure = createFailure(new ServletWebServerApplicationContext()); + assertThat(failure).isNotNull(); + FailureAnalysis analysis = new MissingWebServerFactoryBeanFailureAnalyzer().analyze(failure); + assertThat(analysis).isNotNull(); + assertThat(analysis.getDescription()).isEqualTo("Web application could not be started as there was no " + + ServletWebServerFactory.class.getName() + " bean defined in the context."); + assertThat(analysis.getAction()).isEqualTo( + "Check your application's dependencies for a supported servlet web server.\nCheck the configured web " + + "application type."); + } + + @Test + void missingReactiveWebServerFactoryBeanFailure() { + ApplicationContextException failure = createFailure(new ReactiveWebServerApplicationContext()); + FailureAnalysis analysis = new MissingWebServerFactoryBeanFailureAnalyzer().analyze(failure); + assertThat(analysis).isNotNull(); + assertThat(analysis.getDescription()).isEqualTo("Web application could not be started as there was no " + + ReactiveWebServerFactory.class.getName() + " bean defined in the context."); + assertThat(analysis.getAction()).isEqualTo( + "Check your application's dependencies for a supported reactive web server.\nCheck the configured web " + + "application type."); + } + + private ApplicationContextException createFailure(ConfigurableApplicationContext context) { + try { + context.refresh(); + context.close(); + return null; + } + catch (ApplicationContextException ex) { + return ex; + } + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContextTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContextTests.java index 24ff9da399..13ae884265 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContextTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContextTests.java @@ -61,7 +61,7 @@ class ReactiveWebServerApplicationContextTests { void whenThereIsNoWebServerFactoryBeanThenContextRefreshWillFail() { assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() -> this.context.refresh()) .withMessageContaining( - "Unable to start ReactiveWebApplicationContext due to missing ReactiveWebServerFactory bean"); + "Unable to start ReactiveWebServerApplicationContext due to missing ReactiveWebServerFactory bean"); } @Test diff --git a/src/checkstyle/import-control.xml b/src/checkstyle/import-control.xml index 397dc03fb6..089b4371a9 100644 --- a/src/checkstyle/import-control.xml +++ b/src/checkstyle/import-control.xml @@ -93,6 +93,9 @@ + + +