diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc index c46dd12979..a717c74463 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/spring-application.adoc @@ -352,6 +352,9 @@ include::code:MyApplication[] Also, the `ExitCodeGenerator` interface may be implemented by exceptions. When such an exception is encountered, Spring Boot returns the exit code provided by the implemented `getExitCode()` method. +If there is more than `ExitCodeGenerator`, the first non-zero exit code that is generated is used. +To control the order in which the generators are called, additionally implement the `org.springframework.core.Ordered` interface or use the `org.springframework.core.annotation.Order` annotation. + [[features.spring-application.admin]] diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ExitCodeGenerators.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ExitCodeGenerators.java index f26f16ea16..489f23ec87 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ExitCodeGenerators.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ExitCodeGenerators.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 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. @@ -21,14 +21,19 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.Order; import org.springframework.util.Assert; /** - * Maintains a collection of {@link ExitCodeGenerator} instances and allows the final exit - * code to be calculated. + * Maintains an ordered collection of {@link ExitCodeGenerator} instances and allows the + * final exit code to be calculated. Generators are ordered by {@link Order @Order} and + * {@link Ordered}. * * @author Dave Syer * @author Phillip Webb + * @author GenKui Du * @see #getExitCode() * @see ExitCodeGenerator */ @@ -71,6 +76,7 @@ class ExitCodeGenerators implements Iterable { void add(ExitCodeGenerator generator) { Assert.notNull(generator, "Generator must not be null"); this.generators.add(generator); + AnnotationAwareOrderComparator.sort(this.generators); } @Override @@ -79,7 +85,8 @@ class ExitCodeGenerators implements Iterable { } /** - * Get the final exit code that should be returned based on all contained generators. + * Get the final exit code that should be returned. The final exit code is the first + * non-zero exit code that is {@link ExitCodeGenerator#getExitCode generated}. * @return the final exit code. */ int getExitCode() { @@ -87,12 +94,13 @@ class ExitCodeGenerators implements Iterable { for (ExitCodeGenerator generator : this.generators) { try { int value = generator.getExitCode(); - if (value > 0 && value > exitCode || value < 0 && value < exitCode) { + if (value != 0) { exitCode = value; + break; } } catch (Exception ex) { - exitCode = (exitCode != 0) ? exitCode : 1; + exitCode = 1; ex.printStackTrace(); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 7f1910c611..e3075cad8b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -61,7 +61,9 @@ import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.GenericTypeResolver; +import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.Order; import org.springframework.core.env.CommandLinePropertySource; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; @@ -1320,9 +1322,10 @@ public class SpringApplication { * Static helper that can be used to exit a {@link SpringApplication} and obtain a * code indicating success (0) or otherwise. Does not throw exceptions but should * print stack traces of any encountered. Applies the specified - * {@link ExitCodeGenerator} in addition to any Spring beans that implement - * {@link ExitCodeGenerator}. In the case of multiple exit codes the highest value - * will be used (or if all values are negative, the lowest value will be used) + * {@link ExitCodeGenerator ExitCodeGenerators} in addition to any Spring beans that + * implement {@link ExitCodeGenerator}. When multiple generators are available, the + * first non-zero exit code is used. Generators ordered based on their {@link Ordered} + * implementation and {@link Order @Order} annotation. * @param context the context to close if possible * @param exitCodeGenerators exit code generators * @return the outcome (0 if successful) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ExitCodeGeneratorsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ExitCodeGeneratorsTests.java index 9a81e71626..0512785283 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ExitCodeGeneratorsTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ExitCodeGeneratorsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 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. @@ -21,10 +21,13 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.springframework.core.Ordered; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; /** * Tests for {@link ExitCodeGenerators}. @@ -62,18 +65,9 @@ class ExitCodeGeneratorsTests { } @Test - void getExitCodeWhenAllNegativeShouldReturnLowestValue() { - ExitCodeGenerators generators = new ExitCodeGenerators(); - generators.add(mockGenerator(-1)); - generators.add(mockGenerator(-3)); - generators.add(mockGenerator(-2)); - assertThat(generators.getExitCode()).isEqualTo(-3); - } - - @Test - void getExitCodeWhenAllPositiveShouldReturnHighestValue() { + void getExitCodeWithUnorderedGeneratorsReturnsFirstNonZeroExitCode() { ExitCodeGenerators generators = new ExitCodeGenerators(); - generators.add(mockGenerator(1)); + generators.add(mockGenerator(0)); generators.add(mockGenerator(3)); generators.add(mockGenerator(2)); assertThat(generators.getExitCode()).isEqualTo(3); @@ -89,12 +83,29 @@ class ExitCodeGeneratorsTests { assertThat(generators.getExitCode()).isEqualTo(2); } + @Test + void getExitCodeWithOrderedGeneratorsReturnsFirstNonZeroExitCode() { + ExitCodeGenerators generators = new ExitCodeGenerators(); + generators.add(orderedMockGenerator(0, 1)); + generators.add(orderedMockGenerator(1, 3)); + generators.add(orderedMockGenerator(2, 2)); + generators.add(mockGenerator(3)); + assertThat(generators.getExitCode()).isEqualTo(2); + } + private ExitCodeGenerator mockGenerator(int exitCode) { ExitCodeGenerator generator = mock(ExitCodeGenerator.class); given(generator.getExitCode()).willReturn(exitCode); return generator; } + private ExitCodeGenerator orderedMockGenerator(int exitCode, int order) { + ExitCodeGenerator generator = mock(ExitCodeGenerator.class, withSettings().extraInterfaces(Ordered.class)); + given(generator.getExitCode()).willReturn(exitCode); + given(((Ordered) generator).getOrder()).willReturn(order); + return generator; + } + private ExitCodeExceptionMapper mockMapper(Class exceptionType, int exitCode) { return (exception) -> { if (exceptionType.isInstance(exception)) {