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..e028c84cd1 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,12 +18,14 @@ 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.WebServerGracefulShutdownLifecycle; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.server.context.MissingWebServerFactoryBeanException; import org.springframework.context.ApplicationContextException; import org.springframework.core.metrics.StartupStep; import org.springframework.http.server.reactive.HttpHandler; @@ -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/server/context/MissingWebServerFactoryBeanException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/context/MissingWebServerFactoryBeanException.java new file mode 100644 index 0000000000..cd9a0707da --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/context/MissingWebServerFactoryBeanException.java @@ -0,0 +1,58 @@ +/* + * 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.server.context; + +import org.springframework.boot.WebApplicationType; +import org.springframework.context.ApplicationContextException; +import org.springframework.lang.NonNull; + +/** + * Throws exception when web server factory bean is missing. + * + * @author Guirong Hu + * @since 2.7.0 + */ +@SuppressWarnings("serial") +public class MissingWebServerFactoryBeanException extends ApplicationContextException { + + private final WebApplicationType webApplicationType; + + /** + * Create a new {@code MissingWebServerFactoryBeanException} with the given web + * application context class and the given web server factory class and the given type + * of web application. + * @param webApplicationContextClass the web application context class + * @param webServerFactoryClass the web server factory class + * @param webApplicationType the type of web application + */ + public MissingWebServerFactoryBeanException(@NonNull Class webApplicationContextClass, + @NonNull Class webServerFactoryClass, @NonNull WebApplicationType webApplicationType) { + super(String.format("Unable to start %s due to missing %s bean.", webApplicationContextClass.getSimpleName(), + webServerFactoryClass.getSimpleName())); + this.webApplicationType = webApplicationType; + } + + /** + * Returns the type of web application that is being run. + * @return the type of web application + * @since 2.7.0 + */ + public WebApplicationType getWebApplicationType() { + return this.webApplicationType; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/context/MissingWebServerFactoryBeanFailureAnalyzer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/context/MissingWebServerFactoryBeanFailureAnalyzer.java new file mode 100644 index 0000000000..164a8c7d19 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/context/MissingWebServerFactoryBeanFailureAnalyzer.java @@ -0,0 +1,49 @@ +/* + * 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.server.context; + +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.diagnostics.FailureAnalyzer; +import org.springframework.context.ApplicationContextException; + +/** + * A {@link FailureAnalyzer} that performs analysis of failures caused by an + * {@link MissingWebServerFactoryBeanException}. + * + * @author Guirong Hu + */ +class MissingWebServerFactoryBeanFailureAnalyzer extends AbstractFailureAnalyzer { + + private static final String ACTION = "Check your application's dependencies on supported web servers " + + "or configuration of web application type."; + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, ApplicationContextException cause) { + Throwable rootCause = cause.getCause(); + if (rootCause instanceof MissingWebServerFactoryBeanException) { + WebApplicationType webApplicationType = ((MissingWebServerFactoryBeanException) rootCause) + .getWebApplicationType(); + return new FailureAnalysis(String.format( + "Reason: The running web application is of type %s, but the dependent class is missing.", + webApplicationType.name().toLowerCase()), ACTION, cause); + } + return null; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/context/package-info.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/context/package-info.java new file mode 100644 index 0000000000..8381001256 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/context/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Web server integrations with Spring's + * {@link org.springframework.context.ApplicationContext ApplicationContext}. + */ +package org.springframework.boot.web.server.context; 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 40dd4f99b9..c2315b6540 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 @@ -37,11 +37,13 @@ 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.WebServerGracefulShutdownLifecycle; import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.server.context.MissingWebServerFactoryBeanException; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.ServletContextInitializerBeans; @@ -207,8 +209,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 aab10a331b..4de66425d1 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.embedded.tomcat.ConnectorStartFailureAnalyzer,\ +org.springframework.boot.web.server.context.MissingWebServerFactoryBeanFailureAnalyzer # Failure Analysis Reporters org.springframework.boot.diagnostics.FailureAnalysisReporter=\ 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/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/context/MissingWebServerFactoryBeanFailureAnalyzerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/context/MissingWebServerFactoryBeanFailureAnalyzerTests.java new file mode 100644 index 0000000000..2583f468b3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/server/context/MissingWebServerFactoryBeanFailureAnalyzerTests.java @@ -0,0 +1,70 @@ +/* + * 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.server.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.servlet.context.ServletWebServerApplicationContext; +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 + */ +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( + "Reason: The running web application is of type servlet, but the dependent class is missing."); + assertThat(analysis.getAction()).isEqualTo( + "Check your application's dependencies on supported web servers or configuration of web application type."); + } + + @Test + void missingReactiveWebServerFactoryBeanFailure() { + ApplicationContextException failure = createFailure(new ReactiveWebServerApplicationContext()); + FailureAnalysis analysis = new MissingWebServerFactoryBeanFailureAnalyzer().analyze(failure); + assertThat(analysis).isNotNull(); + assertThat(analysis.getDescription()).isEqualTo( + "Reason: The running web application is of type reactive, but the dependent class is missing."); + assertThat(analysis.getAction()).isEqualTo( + "Check your application's dependencies on supported web servers or configuration of web application type."); + } + + private ApplicationContextException createFailure(ConfigurableApplicationContext context) { + try { + context.refresh(); + context.close(); + return null; + } + catch (ApplicationContextException ex) { + return ex; + } + } + +} diff --git a/src/checkstyle/import-control.xml b/src/checkstyle/import-control.xml index 445d18591c..b334a2ece5 100644 --- a/src/checkstyle/import-control.xml +++ b/src/checkstyle/import-control.xml @@ -93,6 +93,9 @@ + + +