This commit is contained in:
Phillip Webb 2020-03-23 20:03:44 -07:00
parent 9a33a723fe
commit 0717de723f
30 changed files with 252 additions and 198 deletions

View File

@ -182,25 +182,12 @@ public class CachingOperationInvoker implements OperationInvoker {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
CacheKey other = (CacheKey) obj;
if (this.apiVersion != other.apiVersion) {
return false;
}
if (this.principal == null) {
if (other.principal != null) {
return false;
}
}
else if (!this.principal.equals(other.principal)) {
return false;
}
return true;
return this.apiVersion.equals(other.apiVersion)
&& ObjectUtils.nullSafeEquals(this.principal, other.principal);
}
@Override
@ -208,7 +195,7 @@ public class CachingOperationInvoker implements OperationInvoker {
final int prime = 31;
int result = 1;
result = prime * result + this.apiVersion.hashCode();
result = prime * result + ((this.principal == null) ? 0 : this.principal.hashCode());
result = prime * result + ObjectUtils.nullSafeHashCode(this.principal);
return result;
}

View File

@ -181,6 +181,8 @@ See the {spring-boot-module-api}/cloud/CloudFoundryVcapEnvironmentPostProcessor.
TIP: The https://github.com/pivotal-cf/java-cfenv/[Java CFEnv] project is a better fit for tasks such as configuring a DataSource.
[[cloud-deployment-kubernetes]]
=== Kubernetes
Spring Boot auto-detects Kubernetes deployment environments by checking the environment for `"*_SERVICE_HOST"` and `"*_SERVICE_PORT"` variables.
@ -188,6 +190,8 @@ You can override this detection with the configprop:management.health.probes.ena
Spring Boot helps you to <<spring-boot-features.adoc#boot-features-application-availability-state,manage the state of your application>> and export it with <<production-ready-features.adoc#production-ready-kubernetes-probes, HTTP Kubernetes Probes using Actuator>>.
[[cloud-deployment-kubernetes-container-lifecycle]]
==== Kubernetes Container Lifecycle
When Kubernetes deletes an application instance, the shutdown process involves several subsystems concurrently: shutdown hooks, unregistering the service, removing the instance from the load-balancer...
@ -207,6 +211,7 @@ lifecycle:
Once the pre-stop hook has completed, SIGTERM will be sent to the container and <<spring-boot-features#boot-features-graceful-shutdown,graceful shutdown>> will begin, allowing any remaining in-flight requests to complete.
[[cloud-deployment-heroku]]
=== Heroku
Heroku is another popular PaaS platform.

View File

@ -870,6 +870,7 @@ It's also possible to override the `show-details` and `roles` properties if requ
TIP: You can use `@Qualifier("groupname")` if you need to register custom `StatusAggregator` or `HttpCodeStatusMapper` beans for use with the group.
[[production-ready-kubernetes-probes]]
=== Kubernetes Probes
Applications deployed on Kubernetes can provide information about their internal state with https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes[Container Probes].
@ -905,6 +906,8 @@ The `"startupProbe"` is not necessarily needed here as the `"readinessProbe"` fa
WARNING: If your Actuator endpoints are deployed on a separate management context, be aware that endpoints are then not using the same web infrastructure (port, connection pools, framework components) as the main application.
In this case, a Probe check could be successful even if the main application does not work properly (for example, it cannot accept new connections).
[[production-ready-kubernetes-probes-external-state]]
==== Checking external state with Kubernetes Probes
Actuator configures the "liveness" and "readiness" Probes as Health Groups; this means that all the <<production-ready-health-groups, Health Groups features>> are available for them.
@ -927,6 +930,7 @@ Some external systems might not be shared by application instances or not essent
Also, Kubernetes will react differently to applications being taken out of the load-balancer, depending on its https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/[autoscaling configuration].
[[production-ready-kubernetes-probes-lifecycle]]
==== Application lifecycle and Probes states
An important aspect of the Kubernetes Probes support is its consistency with the application lifecycle.
@ -952,7 +956,6 @@ When a Spring Boot application starts:
|live
|ready
|Startup tasks are finished. The application is receiving traffic.
|===
When a Spring Boot application shuts down:
@ -975,12 +978,12 @@ When a Spring Boot application shuts down:
|broken
|unready
|The application context is closed and the application cannot serve traffic.
|===
TIP: Check out the <<deployment.adoc#cloud-deployment-kubernetes-container-lifecycle,Kubernetes container lifecycle section>> for more information about Kubernetes deployment.
[[production-ready-application-info]]
=== Application Information
Application information exposes various information collected from all {spring-boot-actuator-module-code}/info/InfoContributor.java[`InfoContributor`] beans defined in your `ApplicationContext`.

View File

@ -191,12 +191,15 @@ NOTE: There are some restrictions when creating an `ApplicationContext` hierarch
For example, Web components *must* be contained within the child context, and the same `Environment` is used for both parent and child contexts.
See the {spring-boot-module-api}/builder/SpringApplicationBuilder.html[`SpringApplicationBuilder` Javadoc] for full details.
[[boot-features-application-availability-state]]
=== Application Availability State
When deployed on plaftorms, applications can provide information about their availability to the platform using infrastructure like https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/[Kubernetes Probes].
Spring Boot manages this application state with the `ApplicationAvailabilityProvider` and makes it available to application components and the platform itself.
[[boot-features-application-availability-liveness]]
==== Liveness State
The "Liveness" state of an application tells whether its internal state allows it to work correctly, or recover by itself if it's currently failing.
@ -209,6 +212,8 @@ The internal state of Spring Boot applications is mostly represented by the Spri
If the application context has started successfully, Spring Boot assumes that the application is in a valid state.
An application is considered live as soon as the context has been refreshed, see <<boot-features-application-events-and-listeners, Spring Boot application lifecycle and related Application Events>>.
[[boot-features-application-availability-readiness]]
==== Readiness State
The "Readiness" state of an application tells whether the application is ready to handle traffic.

View File

@ -150,8 +150,6 @@ class HttpClientHttp implements Http {
/**
* {@link HttpEntity} to send {@link Content} content.
*
* @author Phillip Webb
*/
private static class WritableHttpEntity extends AbstractHttpEntity {

View File

@ -306,6 +306,8 @@ include::../gradle/packaging/boot-jar-layered-exclude-tools.gradle[tags=layered]
include::../gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts[tags=layered]
----
[[packaging-layers-configuration]]
===== Custom Layers configuration
Depending on your application, you may want to tune how layers are created and add new ones.

View File

@ -24,6 +24,7 @@ import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar;
@ -91,15 +92,11 @@ public class SpringBootExtension {
* @param configurer the task configurer
*/
public void buildInfo(Action<BuildInfo> configurer) {
TaskProvider<BuildInfo> bootBuildInfo = this.project.getTasks().register("bootBuildInfo", BuildInfo.class,
(task) -> {
task.setGroup(BasePlugin.BUILD_GROUP);
task.setDescription("Generates a META-INF/build-info.properties file.");
task.getConventionMapping().map("destinationDir",
() -> new File(determineMainSourceSetResourcesOutputDir(), "META-INF"));
});
TaskContainer tasks = this.project.getTasks();
TaskProvider<BuildInfo> bootBuildInfo = tasks.register("bootBuildInfo", BuildInfo.class,
this::configureBuildInfoTask);
this.project.getPlugins().withType(JavaPlugin.class, (plugin) -> {
this.project.getTasks().getByName(JavaPlugin.CLASSES_TASK_NAME).dependsOn(bootBuildInfo.get());
tasks.getByName(JavaPlugin.CLASSES_TASK_NAME).dependsOn(bootBuildInfo.get());
this.project.afterEvaluate((evaluated) -> {
BuildInfoProperties properties = bootBuildInfo.get().getProperties();
if (properties.getArtifact() == null) {
@ -112,6 +109,13 @@ public class SpringBootExtension {
}
}
private void configureBuildInfoTask(BuildInfo task) {
task.setGroup(BasePlugin.BUILD_GROUP);
task.setDescription("Generates a META-INF/build-info.properties file.");
task.getConventionMapping().map("destinationDir",
() -> new File(determineMainSourceSetResourcesOutputDir(), "META-INF"));
}
private File determineMainSourceSetResourcesOutputDir() {
return this.project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getResourcesDir();

View File

@ -245,7 +245,7 @@ public class BootJar extends Jar implements BootArchive {
String coordinates = this.coordinatesByFileName.get(details.getName());
LibraryCoordinates libraryCoordinates = (coordinates != null) ? new LibraryCoordinates(coordinates)
: new LibraryCoordinates("?:?:?");
return this.layers.getLayer(new Library(null, details.getFile(), null, false, libraryCoordinates));
return this.layers.getLayer(new Library(null, details.getFile(), null, libraryCoordinates, false));
}
if (path.startsWith("BOOT-INF/classes/")) {
return this.layers.getLayer(details.getSourcePath());

View File

@ -153,12 +153,6 @@ public class LayerConfiguration {
private static final class StrategySpec {
private enum TYPE {
LIBRARIES, RESOURCES;
}
private final TYPE type;
private List<LibraryFilter> libraryFilters;
@ -232,6 +226,12 @@ public class LayerConfiguration {
return new StrategySpec(TYPE.RESOURCES);
}
private enum TYPE {
LIBRARIES, RESOURCES;
}
}
}

View File

@ -37,10 +37,10 @@ public class Library {
private final LibraryScope scope;
private final boolean unpackRequired;
private final LibraryCoordinates coordinates;
private final boolean unpackRequired;
/**
* Create a new {@link Library}.
* @param file the source file
@ -69,15 +69,24 @@ public class Library {
* @param unpackRequired if the library needs to be unpacked before it can be used
*/
public Library(String name, File file, LibraryScope scope, boolean unpackRequired) {
this(name, file, scope, unpackRequired, null);
this(name, file, scope, null, unpackRequired);
}
public Library(String name, File file, LibraryScope scope, boolean unpackRequired, LibraryCoordinates coordinates) {
/**
* Create a new {@link Library}.
* @param name the name of the library as it should be written or {@code null} to use
* the file name
* @param file the source file
* @param scope the scope of the library
* @param coordinates the library coordinates or {@code null}
* @param unpackRequired if the library needs to be unpacked before it can be used
*/
public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired) {
this.name = (name != null) ? name : file.getName();
this.file = file;
this.scope = scope;
this.unpackRequired = unpackRequired;
this.coordinates = coordinates;
this.unpackRequired = unpackRequired;
}
/**
@ -113,6 +122,14 @@ public class Library {
return this.scope;
}
/**
* Return the {@linkplain LibraryCoordinates coordinates} of the library.
* @return the coordinates
*/
public LibraryCoordinates getCoordinates() {
return this.coordinates;
}
/**
* Return if the file cannot be used directly as a nested jar and needs to be
* unpacked.
@ -122,14 +139,6 @@ public class Library {
return this.unpackRequired;
}
/**
* Return the {@linkplain LibraryCoordinates coordinates} of the library.
* @return the coordinates
*/
public LibraryCoordinates getCoordinates() {
return this.coordinates;
}
long getLastModified() {
return this.file.lastModified();
}

View File

@ -54,24 +54,45 @@ public final class LibraryCoordinates {
Assert.isTrue(elements.length >= 2, "Coordinates must contain at least 'groupId:artifactId'");
this.groupId = elements[0];
this.artifactId = elements[1];
if (elements.length > 2) {
this.version = elements[2];
}
else {
this.version = null;
}
this.version = (elements.length > 2) ? elements[2] : null;
}
/**
* Return the group ID of the coordinates.
* @return the group ID
*/
public String getGroupId() {
return this.groupId;
}
/**
* Return the artifact ID of the coordinates.
* @return the artifact ID
*/
public String getArtifactId() {
return this.artifactId;
}
/**
* Return the version of the coordinates.
* @return the version
*/
public String getVersion() {
return this.version;
}
/**
* Return the coordinates in the form {@code groupId:artifactId:version}.
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append((this.groupId != null) ? this.groupId : "");
builder.append(":");
builder.append((this.artifactId != null) ? this.artifactId : "");
builder.append(":");
builder.append((this.version != null) ? this.version : "");
return builder.toString();
}
}

View File

@ -16,9 +16,9 @@
package org.springframework.boot.loader.tools.layer.library;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.LibraryCoordinates;
@ -32,58 +32,21 @@ import org.springframework.boot.loader.tools.LibraryCoordinates;
*/
public class CoordinateFilter implements LibraryFilter {
private final List<String> includes = new ArrayList<>();
private static final String EMPTY_COORDINATES = "::";
private final List<String> excludes = new ArrayList<>();
private final List<Pattern> includes;
private final List<Pattern> excludes;
public CoordinateFilter(List<String> includes, List<String> excludes) {
this.includes.addAll(includes);
this.excludes.addAll(excludes);
this.includes = includes.stream().map(this::asPattern).collect(Collectors.toList());
this.excludes = excludes.stream().map(this::asPattern).collect(Collectors.toList());
}
@Override
public boolean isLibraryIncluded(Library library) {
return isMatch(library, this.includes);
}
@Override
public boolean isLibraryExcluded(Library library) {
return isMatch(library, this.excludes);
}
private boolean isMatch(Library library, List<String> toMatch) {
private Pattern asPattern(String string) {
StringBuilder builder = new StringBuilder();
LibraryCoordinates coordinates = library.getCoordinates();
if (coordinates != null) {
if (coordinates.getGroupId() != null) {
builder.append(coordinates.getGroupId());
}
builder.append(":");
if (coordinates.getArtifactId() != null) {
builder.append(coordinates.getArtifactId());
}
builder.append(":");
if (coordinates.getVersion() != null) {
builder.append(coordinates.getVersion());
}
}
else {
builder.append("::");
}
String input = builder.toString();
for (String patternString : toMatch) {
Pattern pattern = buildPatternForString(patternString);
if (pattern.matcher(input).matches()) {
return true;
}
}
return false;
}
private Pattern buildPatternForString(String pattern) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
if (c == '.') {
builder.append("\\.");
}
@ -97,4 +60,25 @@ public class CoordinateFilter implements LibraryFilter {
return Pattern.compile(builder.toString());
}
@Override
public boolean isLibraryIncluded(Library library) {
return isMatch(library, this.includes);
}
@Override
public boolean isLibraryExcluded(Library library) {
return isMatch(library, this.excludes);
}
private boolean isMatch(Library library, List<Pattern> patterns) {
LibraryCoordinates coordinates = library.getCoordinates();
String input = (coordinates != null) ? coordinates.toString() : EMPTY_COORDINATES;
for (Pattern pattern : patterns) {
if (pattern.matcher(input).matches()) {
return true;
}
}
return false;
}
}

View File

@ -65,4 +65,10 @@ class LibraryCoordinatesTests {
assertThatIllegalArgumentException().isThrownBy(() -> new LibraryCoordinates("com.acme"));
}
@Test
void toStringReturnsString() {
assertThat(new LibraryCoordinates("com.acme:my-library:1.0.0")).hasToString("com.acme:my-library:1.0.0");
assertThat(new LibraryCoordinates("com.acme:my-library")).hasToString("com.acme:my-library:");
}
}

View File

@ -82,7 +82,7 @@ public class ArtifactsLibraries implements Libraries {
}
LibraryCoordinates coordinates = new LibraryCoordinates(artifact.getGroupId(), artifact.getArtifactId(),
artifact.getVersion());
callback.library(new Library(name, artifact.getFile(), scope, isUnpackRequired(artifact), coordinates));
callback.library(new Library(name, artifact.getFile(), scope, coordinates, isUnpackRequired(artifact)));
}
}
}

View File

@ -35,6 +35,7 @@ import org.springframework.boot.loader.tools.layer.library.CoordinateFilter;
import org.springframework.boot.loader.tools.layer.library.FilteredLibraryStrategy;
import org.springframework.boot.loader.tools.layer.library.LibraryFilter;
import org.springframework.boot.loader.tools.layer.library.LibraryStrategy;
import org.springframework.util.Assert;
/**
* Produces a {@link CustomLayers} based on the given {@link Document}.
@ -46,12 +47,12 @@ public class CustomLayersProvider {
public CustomLayers getLayers(Document document) {
Element root = document.getDocumentElement();
NodeList nl = root.getChildNodes();
NodeList nodes = root.getChildNodes();
List<Layer> layers = new ArrayList<>();
List<LibraryStrategy> libraryStrategies = new ArrayList<>();
List<ResourceStrategy> resourceStrategies = new ArrayList<>();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
processNode(layers, libraryStrategies, resourceStrategies, (Element) node);
}
@ -80,14 +81,13 @@ public class CustomLayersProvider {
private List<Layer> getLayers(Element element) {
List<Layer> layers = new ArrayList<>();
NodeList nodes = element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
String nodeName = ele.getNodeName();
if ("layer".equals(nodeName)) {
layers.add(new Layer(ele.getTextContent()));
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);
if (childNode instanceof Element) {
Element childElement = (Element) childNode;
if ("layer".equals(childElement.getNodeName())) {
layers.add(new Layer(childElement.getTextContent()));
}
}
}
@ -98,26 +98,11 @@ public class CustomLayersProvider {
FilterFactory<E> filterFactory, Predicate<String> filterPredicate) {
List<T> contents = new ArrayList<>();
for (int i = 0; i < nodes.getLength(); i++) {
Node item = nodes.item(i);
if (item instanceof Element) {
Element element = (Element) item;
Node node = nodes.item(i);
if (node instanceof Element) {
Element element = (Element) node;
if ("layer-content".equals(element.getTagName())) {
NodeList filterList = item.getChildNodes();
if (filterList.getLength() == 0) {
throw new IllegalArgumentException("Filters for layer-content must not be empty.");
}
List<E> filters = new ArrayList<>();
for (int j = 0; j < filterList.getLength(); j++) {
Node filterNode = filterList.item(j);
if (filterNode instanceof Element) {
List<String> includeList = getPatterns((Element) filterNode, "include");
List<String> excludeList = getPatterns((Element) filterNode, "exclude");
if (filterPredicate.test(filterNode.getNodeName())) {
E filter = filterFactory.getFilter(includeList, excludeList);
filters.add(filter);
}
}
}
List<E> filters = getFilters(node, filterFactory, filterPredicate);
String layer = element.getAttribute("layer");
contents.add(strategyFactory.getStrategy(layer, filters));
}
@ -126,16 +111,33 @@ public class CustomLayersProvider {
return contents;
}
private List<String> getPatterns(Element element, String key) {
NodeList patterns = element.getElementsByTagName(key);
List<String> values = new ArrayList<>();
for (int j = 0; j < patterns.getLength(); j++) {
Node item = patterns.item(j);
if (item instanceof Element) {
values.add(item.getTextContent());
private <E> List<E> getFilters(Node node, FilterFactory<E> factory, Predicate<String> predicate) {
NodeList childNodes = node.getChildNodes();
Assert.state(childNodes.getLength() > 0, "Filters for layer-content must not be empty.");
List<E> filters = new ArrayList<>();
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);
if (childNode instanceof Element) {
List<String> include = getPatterns((Element) childNode, "include");
List<String> exclude = getPatterns((Element) childNode, "exclude");
if (predicate.test(childNode.getNodeName())) {
filters.add(factory.getFilter(include, exclude));
}
}
}
return values;
return filters;
}
private List<String> getPatterns(Element element, String key) {
List<String> patterns = new ArrayList<>();
NodeList nodes = element.getElementsByTagName(key);
for (int j = 0; j < nodes.getLength(); j++) {
Node node = nodes.item(j);
if (node instanceof Element) {
patterns.add(node.getTextContent());
}
}
return patterns;
}
interface StrategyFactory<E, T> {

View File

@ -186,14 +186,16 @@ public class RepackageMojo extends AbstractPackagerMojo {
if (this.outputTimestamp == null || this.outputTimestamp.length() < 2) {
return null;
}
long epochSeconds;
return FileTime.from(getOutputTimestampEpochSeconds(), TimeUnit.SECONDS);
}
private long getOutputTimestampEpochSeconds() {
try {
epochSeconds = Long.parseLong(this.outputTimestamp);
return Long.parseLong(this.outputTimestamp);
}
catch (NumberFormatException ex) {
epochSeconds = OffsetDateTime.parse(this.outputTimestamp).toInstant().getEpochSecond();
return OffsetDateTime.parse(this.outputTimestamp).toInstant().getEpochSecond();
}
return FileTime.from(epochSeconds, TimeUnit.SECONDS);
}
/**

View File

@ -30,7 +30,7 @@ import org.springframework.boot.loader.tools.layer.CustomLayers;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@ -74,14 +74,14 @@ public class CustomLayersProviderTests {
@Test
void getLayerResolverWhenDocumentContainsLibraryLayerWithNoFilters() {
assertThatIllegalArgumentException()
assertThatIllegalStateException()
.isThrownBy(() -> this.customLayersProvider.getLayers(getDocument("library-layer-no-filter.xml")))
.withMessage("Filters for layer-content must not be empty.");
}
@Test
void getLayerResolverWhenDocumentContainsResourceLayerWithNoFilters() {
assertThatIllegalArgumentException()
assertThatIllegalStateException()
.isThrownBy(() -> this.customLayersProvider.getLayers(getDocument("resource-layer-no-filter.xml")))
.withMessage("Filters for layer-content must not be empty.");
}

View File

@ -18,6 +18,7 @@ package org.springframework.boot.availability;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.util.Assert;
/**
* Holds the availability state of the application.
@ -36,19 +37,38 @@ public class ApplicationAvailabilityProvider implements ApplicationListener<Appl
private ReadinessState readinessState;
/**
* Create a new {@link ApplicationAvailabilityProvider} instance with
* {@link LivenessState#broken()} and {@link ReadinessState#unready()}.
*/
public ApplicationAvailabilityProvider() {
this(LivenessState.broken(), ReadinessState.unready());
}
/**
* Create a new {@link ApplicationAvailabilityProvider} with the given states.
* @param livenessState the liveness state
* @param readinessState the readiness state
*/
public ApplicationAvailabilityProvider(LivenessState livenessState, ReadinessState readinessState) {
Assert.notNull(livenessState, "LivenessState must not be null");
Assert.notNull(readinessState, "ReadinessState must not be null");
this.livenessState = livenessState;
this.readinessState = readinessState;
}
/**
* Return the {@link LivenessState} of the application.
* @return the liveness state
*/
public LivenessState getLivenessState() {
return this.livenessState;
}
/**
* Return the {@link ReadinessState} of the application.
* @return the readiness state
*/
public ReadinessState getReadinessState() {
return this.readinessState;
}

View File

@ -41,15 +41,14 @@ public final class LivenessState {
}
@Override
public boolean equals(Object o) {
if (this == o) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
LivenessState that = (LivenessState) o;
return this.status == that.status;
return this.status == ((LivenessState) obj).status;
}
@Override
@ -76,6 +75,7 @@ public final class LivenessState {
* The application is running and its internal state is correct.
*/
LIVE,
/**
* The internal state of the application is broken.
*/

View File

@ -41,15 +41,14 @@ public final class ReadinessState {
}
@Override
public boolean equals(Object o) {
if (this == o) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ReadinessState that = (ReadinessState) o;
return this.availability == that.availability;
return this.availability == ((ReadinessState) obj).availability;
}
@Override
@ -76,6 +75,7 @@ public final class ReadinessState {
* The application is not willing to receive traffic.
*/
UNREADY,
/**
* The application is ready to receive traffic.
*/

View File

@ -55,15 +55,7 @@ class JettyGracefulShutdown implements GracefulShutdown {
logger.info("Commencing graceful shutdown, allowing up to " + this.period.getSeconds()
+ "s for active requests to complete");
for (Connector connector : this.server.getConnectors()) {
try {
connector.shutdown().get();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (ExecutionException ex) {
// Continue
}
shutdown(connector);
}
this.shuttingDown = true;
long end = System.currentTimeMillis() + this.period.toMillis();
@ -87,6 +79,18 @@ class JettyGracefulShutdown implements GracefulShutdown {
return activeRequests == 0;
}
private void shutdown(Connector connector) {
try {
connector.shutdown().get();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (ExecutionException ex) {
// Continue
}
}
@Override
public boolean isShuttingDown() {
return this.shuttingDown;

View File

@ -37,7 +37,6 @@ import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.springframework.boot.web.server.GracefulShutdown;
import org.springframework.boot.web.server.ImmediateGracefulShutdown;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException;
@ -101,20 +100,20 @@ public class JettyWebServer implements WebServer {
this.autoStart = autoStart;
Assert.notNull(server, "Jetty Server must not be null");
this.server = server;
GracefulShutdown gracefulShutdown = null;
if (shutdownGracePeriod != null) {
StatisticsHandler handler = new StatisticsHandler();
handler.setHandler(server.getHandler());
server.setHandler(handler);
gracefulShutdown = new JettyGracefulShutdown(server, handler::getRequestsActive, shutdownGracePeriod);
}
else {
gracefulShutdown = new ImmediateGracefulShutdown();
}
this.gracefulShutdown = gracefulShutdown;
this.gracefulShutdown = createGracefulShutdown(server, shutdownGracePeriod);
initialize();
}
private GracefulShutdown createGracefulShutdown(Server server, Duration shutdownGracePeriod) {
if (shutdownGracePeriod == null) {
return GracefulShutdown.IMMEDIATE;
}
StatisticsHandler handler = new StatisticsHandler();
handler.setHandler(server.getHandler());
server.setHandler(handler);
return new JettyGracefulShutdown(server, handler::getRequestsActive, shutdownGracePeriod);
}
private void initialize() {
synchronized (this.monitor) {
try {

View File

@ -55,12 +55,7 @@ final class NettyGracefulShutdown implements GracefulShutdown {
}
this.shuttingDown = true;
try {
if (this.period != null) {
server.disposeNow(this.period);
}
else {
server.disposeNow();
}
disposeNow(server);
logger.info("Graceful shutdown complete");
return true;
}
@ -73,6 +68,15 @@ final class NettyGracefulShutdown implements GracefulShutdown {
}
}
private void disposeNow(DisposableServer server) {
if (this.period != null) {
server.disposeNow(this.period);
}
else {
server.disposeNow();
}
}
@Override
public boolean isShuttingDown() {
return this.shuttingDown;

View File

@ -35,7 +35,6 @@ import reactor.netty.http.server.HttpServerResponse;
import reactor.netty.http.server.HttpServerRoutes;
import org.springframework.boot.web.server.GracefulShutdown;
import org.springframework.boot.web.server.ImmediateGracefulShutdown;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException;
@ -96,7 +95,7 @@ public class NettyWebServer implements WebServer {
}
else {
this.httpServer = httpServer;
this.shutdown = new ImmediateGracefulShutdown();
this.shutdown = GracefulShutdown.IMMEDIATE;
}
}

View File

@ -40,7 +40,6 @@ import org.apache.commons.logging.LogFactory;
import org.apache.naming.ContextBindings;
import org.springframework.boot.web.server.GracefulShutdown;
import org.springframework.boot.web.server.ImmediateGracefulShutdown;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException;
@ -102,7 +101,7 @@ public class TomcatWebServer implements WebServer {
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdownGracePeriod != null) ? new TomcatGracefulShutdown(tomcat, shutdownGracePeriod)
: new ImmediateGracefulShutdown();
: GracefulShutdown.IMMEDIATE;
initialize();
}

View File

@ -41,7 +41,6 @@ import org.xnio.XnioWorker;
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.boot.web.server.GracefulShutdown;
import org.springframework.boot.web.server.ImmediateGracefulShutdown;
import org.springframework.boot.web.server.WebServer;
import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
import org.springframework.util.Assert;
@ -103,12 +102,12 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
}
handler = UndertowCompressionConfigurer.configureCompression(getCompression(), handler);
Closeable closeable = null;
GracefulShutdown gracefulShutdown = null;
if (isAccessLogEnabled()) {
AccessLogHandlerConfiguration accessLogHandlerConfiguration = configureAccessLogHandler(builder, handler);
closeable = accessLogHandlerConfiguration.closeable;
handler = accessLogHandlerConfiguration.accessLogHandler;
}
GracefulShutdown gracefulShutdown = null;
GracefulShutdownHandler gracefulShutdownHandler = Handlers.gracefulShutdown(handler);
Duration gracePeriod = getShutdown().getGracePeriod();
if (gracePeriod != null) {
@ -116,7 +115,7 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
handler = gracefulShutdownHandler;
}
else {
gracefulShutdown = new ImmediateGracefulShutdown();
gracefulShutdown = GracefulShutdown.IMMEDIATE;
}
builder.setHandler(handler);
return new UndertowWebServer(builder, getPort() >= 0, closeable, gracefulShutdown);

View File

@ -38,7 +38,6 @@ import org.xnio.channels.BoundChannel;
import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.GracefulShutdown;
import org.springframework.boot.web.server.ImmediateGracefulShutdown;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException;
@ -234,7 +233,7 @@ public class UndertowServletWebServer implements WebServer {
httpHandler = gracefulShutdownHandler;
}
else {
this.gracefulShutdown = new ImmediateGracefulShutdown();
this.gracefulShutdown = GracefulShutdown.IMMEDIATE;
}
this.builder.setHandler(httpHandler);
return this.builder.build();

View File

@ -30,7 +30,6 @@ import org.apache.commons.logging.LogFactory;
import org.xnio.channels.BoundChannel;
import org.springframework.boot.web.server.GracefulShutdown;
import org.springframework.boot.web.server.ImmediateGracefulShutdown;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException;
@ -84,7 +83,7 @@ public class UndertowWebServer implements WebServer {
* @since 2.0.4
*/
public UndertowWebServer(Undertow.Builder builder, boolean autoStart, Closeable closeable) {
this(builder, autoStart, closeable, new ImmediateGracefulShutdown());
this(builder, autoStart, closeable, GracefulShutdown.IMMEDIATE);
}
/**

View File

@ -24,6 +24,11 @@ package org.springframework.boot.web.server;
*/
public interface GracefulShutdown {
/**
* A {@link GracefulShutdown} that returns immediately with no grace period.
*/
GracefulShutdown IMMEDIATE = new ImmediateGracefulShutdown();
/**
* Shuts down the {@link WebServer}, returning {@code true} if activity ceased during
* the grace period, otherwise {@code false}.

View File

@ -20,9 +20,8 @@ package org.springframework.boot.web.server;
* A {@link GracefulShutdown} that returns immediately with no grace period.
*
* @author Andy Wilkinson
* @since 2.3.0
*/
public class ImmediateGracefulShutdown implements GracefulShutdown {
class ImmediateGracefulShutdown implements GracefulShutdown {
@Override
public boolean shutDownGracefully() {