From 6718b10fa9298e7cd187b526c75fdbd9df6483f3 Mon Sep 17 00:00:00 2001 From: dugenkui Date: Tue, 29 Mar 2022 12:59:54 +0800 Subject: [PATCH 1/2] Order ExitCodeGenerators and return first non-zero exit code See gh-30457 --- .../asciidoc/features/spring-application.adoc | 3 ++- .../boot/ExitCodeGenerators.java | 14 ++++++++++++-- .../boot/ExitCodeGeneratorsTests.java | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) 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 52f7c34750..fa2546775e 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 @@ -354,7 +354,8 @@ 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 several `ExitCodeGenerator` are registered in a `ExitCodeGenerators`, they can be called in a specific order by using `org.springframework.core.annotation.Order` annotation or by implementing `org.springframework.core.Ordered`, +and `ExitCodeGenerators#getExitCode()` will return the first non-zero value. [[features.spring-application.admin]] === Admin Features 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..5fdbe92c66 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 @@ -21,14 +21,22 @@ 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. * + *

If several {@code ExitCodeGenerator} are registered in {@code ExitCodeGenerators}, + * they can be called in a specific order by using {@link Order @Order} or by implementing {@link Ordered}, + * and {@link #getExitCode()} will return the first non-zero value. + * * @author Dave Syer * @author Phillip Webb + * @author GenKui Du * @see #getExitCode() * @see ExitCodeGenerator */ @@ -84,15 +92,17 @@ class ExitCodeGenerators implements Iterable { */ int getExitCode() { int exitCode = 0; + AnnotationAwareOrderComparator.sort(this.generators); 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/test/java/org/springframework/boot/ExitCodeGeneratorsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ExitCodeGeneratorsTests.java index 9a81e71626..e6252fa757 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 @@ -20,11 +20,13 @@ import java.io.IOException; 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}. @@ -89,12 +91,29 @@ class ExitCodeGeneratorsTests { assertThat(generators.getExitCode()).isEqualTo(2); } + @Test + void getExitCodeWithOrderedGenerator() { + ExitCodeGenerators generators = new ExitCodeGenerators(); + generators.add(mockGenerator(0, 1)); + generators.add(mockGenerator(1, 3)); + generators.add(mockGenerator(2, 2)); + generators.add(mockGenerator(3, 4)); + assertThat(generators.getExitCode()).isEqualTo(2); + } + private ExitCodeGenerator mockGenerator(int exitCode) { ExitCodeGenerator generator = mock(ExitCodeGenerator.class); given(generator.getExitCode()).willReturn(exitCode); return generator; } + private ExitCodeGenerator mockGenerator(int exitCode, int orderValue) { + ExitCodeGenerator generator = mock(ExitCodeGenerator.class, withSettings().extraInterfaces(Ordered.class)); + given(generator.getExitCode()).willReturn(exitCode); + given(((Ordered) generator).getOrder()).willReturn(orderValue); + return generator; + } + private ExitCodeExceptionMapper mockMapper(Class exceptionType, int exitCode) { return (exception) -> { if (exceptionType.isInstance(exception)) { From 87273612215aa0f1dfbd252b913192a48fe2bf00 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 7 Apr 2022 16:31:19 +0100 Subject: [PATCH 2/2] Polish "Order ExitCodeGenerators and return first non-zero exit code" See gh-30457 --- .../asciidoc/features/spring-application.adoc | 6 ++-- .../boot/ExitCodeGenerators.java | 16 +++++----- .../boot/SpringApplication.java | 9 ++++-- .../boot/ExitCodeGeneratorsTests.java | 30 +++++++------------ 4 files changed, 28 insertions(+), 33 deletions(-) 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 fa2546775e..f49bebf2c4 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 @@ -354,8 +354,10 @@ 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 several `ExitCodeGenerator` are registered in a `ExitCodeGenerators`, they can be called in a specific order by using `org.springframework.core.annotation.Order` annotation or by implementing `org.springframework.core.Ordered`, -and `ExitCodeGenerators#getExitCode()` will return the first non-zero value. +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]] === Admin Features 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 5fdbe92c66..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. @@ -27,12 +27,9 @@ 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. - * - *

If several {@code ExitCodeGenerator} are registered in {@code ExitCodeGenerators}, - * they can be called in a specific order by using {@link Order @Order} or by implementing {@link Ordered}, - * and {@link #getExitCode()} will return the first non-zero value. + * 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 @@ -79,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 @@ -87,12 +85,12 @@ 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() { int exitCode = 0; - AnnotationAwareOrderComparator.sort(this.generators); for (ExitCodeGenerator generator : this.generators) { try { int value = generator.getExitCode(); 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 e6252fa757..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. @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.List; import org.junit.jupiter.api.Test; + import org.springframework.core.Ordered; import static org.assertj.core.api.Assertions.assertThat; @@ -64,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); @@ -92,12 +84,12 @@ class ExitCodeGeneratorsTests { } @Test - void getExitCodeWithOrderedGenerator() { + void getExitCodeWithOrderedGeneratorsReturnsFirstNonZeroExitCode() { ExitCodeGenerators generators = new ExitCodeGenerators(); - generators.add(mockGenerator(0, 1)); - generators.add(mockGenerator(1, 3)); - generators.add(mockGenerator(2, 2)); - generators.add(mockGenerator(3, 4)); + generators.add(orderedMockGenerator(0, 1)); + generators.add(orderedMockGenerator(1, 3)); + generators.add(orderedMockGenerator(2, 2)); + generators.add(mockGenerator(3)); assertThat(generators.getExitCode()).isEqualTo(2); } @@ -107,10 +99,10 @@ class ExitCodeGeneratorsTests { return generator; } - private ExitCodeGenerator mockGenerator(int exitCode, int orderValue) { + 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(orderValue); + given(((Ordered) generator).getOrder()).willReturn(order); return generator; }