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) { if (this == obj) {
return true; return true;
} }
if (obj == null) { if (obj == null || getClass() != obj.getClass()) {
return false;
}
if (getClass() != obj.getClass()) {
return false; return false;
} }
CacheKey other = (CacheKey) obj; CacheKey other = (CacheKey) obj;
if (this.apiVersion != other.apiVersion) { return this.apiVersion.equals(other.apiVersion)
return false; && ObjectUtils.nullSafeEquals(this.principal, other.principal);
}
if (this.principal == null) {
if (other.principal != null) {
return false;
}
}
else if (!this.principal.equals(other.principal)) {
return false;
}
return true;
} }
@Override @Override
@ -208,7 +195,7 @@ public class CachingOperationInvoker implements OperationInvoker {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + this.apiVersion.hashCode(); 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; 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. 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]] [[cloud-deployment-kubernetes]]
=== Kubernetes === Kubernetes
Spring Boot auto-detects Kubernetes deployment environments by checking the environment for `"*_SERVICE_HOST"` and `"*_SERVICE_PORT"` variables. 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>>. 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]] [[cloud-deployment-kubernetes-container-lifecycle]]
==== 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... 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. 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]] [[cloud-deployment-heroku]]
=== Heroku === Heroku
Heroku is another popular PaaS platform. 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. 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]] [[production-ready-kubernetes-probes]]
=== 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]. 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. 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). 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]] [[production-ready-kubernetes-probes-external-state]]
==== Checking external state with Kubernetes Probes ==== 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. 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]. 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]] [[production-ready-kubernetes-probes-lifecycle]]
==== Application lifecycle and Probes states ==== Application lifecycle and Probes states
An important aspect of the Kubernetes Probes support is its consistency with the application lifecycle. 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 |live
|ready |ready
|Startup tasks are finished. The application is receiving traffic. |Startup tasks are finished. The application is receiving traffic.
|=== |===
When a Spring Boot application shuts down: When a Spring Boot application shuts down:
@ -975,12 +978,12 @@ When a Spring Boot application shuts down:
|broken |broken
|unready |unready
|The application context is closed and the application cannot serve traffic. |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. 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]] [[production-ready-application-info]]
=== Application Information === Application Information
Application information exposes various information collected from all {spring-boot-actuator-module-code}/info/InfoContributor.java[`InfoContributor`] beans defined in your `ApplicationContext`. 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. 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. See the {spring-boot-module-api}/builder/SpringApplicationBuilder.html[`SpringApplicationBuilder` Javadoc] for full details.
[[boot-features-application-availability-state]] [[boot-features-application-availability-state]]
=== 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]. 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. 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]] [[boot-features-application-availability-liveness]]
==== Liveness State ==== 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. 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. 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>>. 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]] [[boot-features-application-availability-readiness]]
==== Readiness State ==== Readiness State
The "Readiness" state of an application tells whether the application is ready to handle traffic. 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. * {@link HttpEntity} to send {@link Content} content.
*
* @author Phillip Webb
*/ */
private static class WritableHttpEntity extends AbstractHttpEntity { 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] include::../gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts[tags=layered]
---- ----
[[packaging-layers-configuration]] [[packaging-layers-configuration]]
===== Custom Layers configuration ===== Custom Layers configuration
Depending on your application, you may want to tune how layers are created and add new ones. 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.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar; import org.gradle.jvm.tasks.Jar;
@ -91,15 +92,11 @@ public class SpringBootExtension {
* @param configurer the task configurer * @param configurer the task configurer
*/ */
public void buildInfo(Action<BuildInfo> configurer) { public void buildInfo(Action<BuildInfo> configurer) {
TaskProvider<BuildInfo> bootBuildInfo = this.project.getTasks().register("bootBuildInfo", BuildInfo.class, TaskContainer tasks = this.project.getTasks();
(task) -> { TaskProvider<BuildInfo> bootBuildInfo = tasks.register("bootBuildInfo", BuildInfo.class,
task.setGroup(BasePlugin.BUILD_GROUP); this::configureBuildInfoTask);
task.setDescription("Generates a META-INF/build-info.properties file.");
task.getConventionMapping().map("destinationDir",
() -> new File(determineMainSourceSetResourcesOutputDir(), "META-INF"));
});
this.project.getPlugins().withType(JavaPlugin.class, (plugin) -> { 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) -> { this.project.afterEvaluate((evaluated) -> {
BuildInfoProperties properties = bootBuildInfo.get().getProperties(); BuildInfoProperties properties = bootBuildInfo.get().getProperties();
if (properties.getArtifact() == null) { 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() { private File determineMainSourceSetResourcesOutputDir() {
return this.project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets() return this.project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getResourcesDir(); .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()); String coordinates = this.coordinatesByFileName.get(details.getName());
LibraryCoordinates libraryCoordinates = (coordinates != null) ? new LibraryCoordinates(coordinates) LibraryCoordinates libraryCoordinates = (coordinates != null) ? new LibraryCoordinates(coordinates)
: new LibraryCoordinates("?:?:?"); : 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/")) { if (path.startsWith("BOOT-INF/classes/")) {
return this.layers.getLayer(details.getSourcePath()); return this.layers.getLayer(details.getSourcePath());

View File

@ -153,12 +153,6 @@ public class LayerConfiguration {
private static final class StrategySpec { private static final class StrategySpec {
private enum TYPE {
LIBRARIES, RESOURCES;
}
private final TYPE type; private final TYPE type;
private List<LibraryFilter> libraryFilters; private List<LibraryFilter> libraryFilters;
@ -232,6 +226,12 @@ public class LayerConfiguration {
return new StrategySpec(TYPE.RESOURCES); 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 LibraryScope scope;
private final boolean unpackRequired;
private final LibraryCoordinates coordinates; private final LibraryCoordinates coordinates;
private final boolean unpackRequired;
/** /**
* Create a new {@link Library}. * Create a new {@link Library}.
* @param file the source file * @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 * @param unpackRequired if the library needs to be unpacked before it can be used
*/ */
public Library(String name, File file, LibraryScope scope, boolean unpackRequired) { 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.name = (name != null) ? name : file.getName();
this.file = file; this.file = file;
this.scope = scope; this.scope = scope;
this.unpackRequired = unpackRequired;
this.coordinates = coordinates; this.coordinates = coordinates;
this.unpackRequired = unpackRequired;
} }
/** /**
@ -113,6 +122,14 @@ public class Library {
return this.scope; 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 * Return if the file cannot be used directly as a nested jar and needs to be
* unpacked. * unpacked.
@ -122,14 +139,6 @@ public class Library {
return this.unpackRequired; return this.unpackRequired;
} }
/**
* Return the {@linkplain LibraryCoordinates coordinates} of the library.
* @return the coordinates
*/
public LibraryCoordinates getCoordinates() {
return this.coordinates;
}
long getLastModified() { long getLastModified() {
return this.file.lastModified(); 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'"); Assert.isTrue(elements.length >= 2, "Coordinates must contain at least 'groupId:artifactId'");
this.groupId = elements[0]; this.groupId = elements[0];
this.artifactId = elements[1]; this.artifactId = elements[1];
if (elements.length > 2) { this.version = (elements.length > 2) ? elements[2] : null;
this.version = elements[2];
}
else {
this.version = null;
}
} }
/**
* Return the group ID of the coordinates.
* @return the group ID
*/
public String getGroupId() { public String getGroupId() {
return this.groupId; return this.groupId;
} }
/**
* Return the artifact ID of the coordinates.
* @return the artifact ID
*/
public String getArtifactId() { public String getArtifactId() {
return this.artifactId; return this.artifactId;
} }
/**
* Return the version of the coordinates.
* @return the version
*/
public String getVersion() { public String getVersion() {
return this.version; 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; package org.springframework.boot.loader.tools.layer.library;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.boot.loader.tools.Library; import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.LibraryCoordinates; import org.springframework.boot.loader.tools.LibraryCoordinates;
@ -32,58 +32,21 @@ import org.springframework.boot.loader.tools.LibraryCoordinates;
*/ */
public class CoordinateFilter implements LibraryFilter { 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) { public CoordinateFilter(List<String> includes, List<String> excludes) {
this.includes.addAll(includes); this.includes = includes.stream().map(this::asPattern).collect(Collectors.toList());
this.excludes.addAll(excludes); this.excludes = excludes.stream().map(this::asPattern).collect(Collectors.toList());
} }
@Override private Pattern asPattern(String string) {
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) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
LibraryCoordinates coordinates = library.getCoordinates(); for (int i = 0; i < string.length(); i++) {
if (coordinates != null) { char c = string.charAt(i);
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);
if (c == '.') { if (c == '.') {
builder.append("\\."); builder.append("\\.");
} }
@ -97,4 +60,25 @@ public class CoordinateFilter implements LibraryFilter {
return Pattern.compile(builder.toString()); 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")); 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(), LibraryCoordinates coordinates = new LibraryCoordinates(artifact.getGroupId(), artifact.getArtifactId(),
artifact.getVersion()); 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.FilteredLibraryStrategy;
import org.springframework.boot.loader.tools.layer.library.LibraryFilter; import org.springframework.boot.loader.tools.layer.library.LibraryFilter;
import org.springframework.boot.loader.tools.layer.library.LibraryStrategy; import org.springframework.boot.loader.tools.layer.library.LibraryStrategy;
import org.springframework.util.Assert;
/** /**
* Produces a {@link CustomLayers} based on the given {@link Document}. * Produces a {@link CustomLayers} based on the given {@link Document}.
@ -46,12 +47,12 @@ public class CustomLayersProvider {
public CustomLayers getLayers(Document document) { public CustomLayers getLayers(Document document) {
Element root = document.getDocumentElement(); Element root = document.getDocumentElement();
NodeList nl = root.getChildNodes(); NodeList nodes = root.getChildNodes();
List<Layer> layers = new ArrayList<>(); List<Layer> layers = new ArrayList<>();
List<LibraryStrategy> libraryStrategies = new ArrayList<>(); List<LibraryStrategy> libraryStrategies = new ArrayList<>();
List<ResourceStrategy> resourceStrategies = new ArrayList<>(); List<ResourceStrategy> resourceStrategies = new ArrayList<>();
for (int i = 0; i < nl.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
Node node = nl.item(i); Node node = nodes.item(i);
if (node instanceof Element) { if (node instanceof Element) {
processNode(layers, libraryStrategies, resourceStrategies, (Element) node); processNode(layers, libraryStrategies, resourceStrategies, (Element) node);
} }
@ -80,14 +81,13 @@ public class CustomLayersProvider {
private List<Layer> getLayers(Element element) { private List<Layer> getLayers(Element element) {
List<Layer> layers = new ArrayList<>(); List<Layer> layers = new ArrayList<>();
NodeList nodes = element.getChildNodes(); NodeList childNodes = element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < childNodes.getLength(); i++) {
Node node = nodes.item(i); Node childNode = childNodes.item(i);
if (node instanceof Element) { if (childNode instanceof Element) {
Element ele = (Element) node; Element childElement = (Element) childNode;
String nodeName = ele.getNodeName(); if ("layer".equals(childElement.getNodeName())) {
if ("layer".equals(nodeName)) { layers.add(new Layer(childElement.getTextContent()));
layers.add(new Layer(ele.getTextContent()));
} }
} }
} }
@ -98,26 +98,11 @@ public class CustomLayersProvider {
FilterFactory<E> filterFactory, Predicate<String> filterPredicate) { FilterFactory<E> filterFactory, Predicate<String> filterPredicate) {
List<T> contents = new ArrayList<>(); List<T> contents = new ArrayList<>();
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
Node item = nodes.item(i); Node node = nodes.item(i);
if (item instanceof Element) { if (node instanceof Element) {
Element element = (Element) item; Element element = (Element) node;
if ("layer-content".equals(element.getTagName())) { if ("layer-content".equals(element.getTagName())) {
NodeList filterList = item.getChildNodes(); List<E> filters = getFilters(node, filterFactory, filterPredicate);
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);
}
}
}
String layer = element.getAttribute("layer"); String layer = element.getAttribute("layer");
contents.add(strategyFactory.getStrategy(layer, filters)); contents.add(strategyFactory.getStrategy(layer, filters));
} }
@ -126,16 +111,33 @@ public class CustomLayersProvider {
return contents; return contents;
} }
private List<String> getPatterns(Element element, String key) { private <E> List<E> getFilters(Node node, FilterFactory<E> factory, Predicate<String> predicate) {
NodeList patterns = element.getElementsByTagName(key); NodeList childNodes = node.getChildNodes();
List<String> values = new ArrayList<>(); Assert.state(childNodes.getLength() > 0, "Filters for layer-content must not be empty.");
for (int j = 0; j < patterns.getLength(); j++) { List<E> filters = new ArrayList<>();
Node item = patterns.item(j); for (int i = 0; i < childNodes.getLength(); i++) {
if (item instanceof Element) { Node childNode = childNodes.item(i);
values.add(item.getTextContent()); 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> { interface StrategyFactory<E, T> {

View File

@ -186,14 +186,16 @@ public class RepackageMojo extends AbstractPackagerMojo {
if (this.outputTimestamp == null || this.outputTimestamp.length() < 2) { if (this.outputTimestamp == null || this.outputTimestamp.length() < 2) {
return null; return null;
} }
long epochSeconds; return FileTime.from(getOutputTimestampEpochSeconds(), TimeUnit.SECONDS);
}
private long getOutputTimestampEpochSeconds() {
try { try {
epochSeconds = Long.parseLong(this.outputTimestamp); return Long.parseLong(this.outputTimestamp);
} }
catch (NumberFormatException ex) { 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 org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat; 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.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -74,14 +74,14 @@ public class CustomLayersProviderTests {
@Test @Test
void getLayerResolverWhenDocumentContainsLibraryLayerWithNoFilters() { void getLayerResolverWhenDocumentContainsLibraryLayerWithNoFilters() {
assertThatIllegalArgumentException() assertThatIllegalStateException()
.isThrownBy(() -> this.customLayersProvider.getLayers(getDocument("library-layer-no-filter.xml"))) .isThrownBy(() -> this.customLayersProvider.getLayers(getDocument("library-layer-no-filter.xml")))
.withMessage("Filters for layer-content must not be empty."); .withMessage("Filters for layer-content must not be empty.");
} }
@Test @Test
void getLayerResolverWhenDocumentContainsResourceLayerWithNoFilters() { void getLayerResolverWhenDocumentContainsResourceLayerWithNoFilters() {
assertThatIllegalArgumentException() assertThatIllegalStateException()
.isThrownBy(() -> this.customLayersProvider.getLayers(getDocument("resource-layer-no-filter.xml"))) .isThrownBy(() -> this.customLayersProvider.getLayers(getDocument("resource-layer-no-filter.xml")))
.withMessage("Filters for layer-content must not be empty."); .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.ApplicationEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.util.Assert;
/** /**
* Holds the availability state of the application. * Holds the availability state of the application.
@ -36,19 +37,38 @@ public class ApplicationAvailabilityProvider implements ApplicationListener<Appl
private ReadinessState readinessState; private ReadinessState readinessState;
/**
* Create a new {@link ApplicationAvailabilityProvider} instance with
* {@link LivenessState#broken()} and {@link ReadinessState#unready()}.
*/
public ApplicationAvailabilityProvider() { public ApplicationAvailabilityProvider() {
this(LivenessState.broken(), ReadinessState.unready()); 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) { 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.livenessState = livenessState;
this.readinessState = readinessState; this.readinessState = readinessState;
} }
/**
* Return the {@link LivenessState} of the application.
* @return the liveness state
*/
public LivenessState getLivenessState() { public LivenessState getLivenessState() {
return this.livenessState; return this.livenessState;
} }
/**
* Return the {@link ReadinessState} of the application.
* @return the readiness state
*/
public ReadinessState getReadinessState() { public ReadinessState getReadinessState() {
return this.readinessState; return this.readinessState;
} }

View File

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

View File

@ -41,15 +41,14 @@ public final class ReadinessState {
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object obj) {
if (this == o) { if (this == obj) {
return true; return true;
} }
if (o == null || getClass() != o.getClass()) { if (obj == null || getClass() != obj.getClass()) {
return false; return false;
} }
ReadinessState that = (ReadinessState) o; return this.availability == ((ReadinessState) obj).availability;
return this.availability == that.availability;
} }
@Override @Override
@ -76,6 +75,7 @@ public final class ReadinessState {
* The application is not willing to receive traffic. * The application is not willing to receive traffic.
*/ */
UNREADY, UNREADY,
/** /**
* The application is ready to receive traffic. * 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() logger.info("Commencing graceful shutdown, allowing up to " + this.period.getSeconds()
+ "s for active requests to complete"); + "s for active requests to complete");
for (Connector connector : this.server.getConnectors()) { for (Connector connector : this.server.getConnectors()) {
try { shutdown(connector);
connector.shutdown().get();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (ExecutionException ex) {
// Continue
}
} }
this.shuttingDown = true; this.shuttingDown = true;
long end = System.currentTimeMillis() + this.period.toMillis(); long end = System.currentTimeMillis() + this.period.toMillis();
@ -87,6 +79,18 @@ class JettyGracefulShutdown implements GracefulShutdown {
return activeRequests == 0; return activeRequests == 0;
} }
private void shutdown(Connector connector) {
try {
connector.shutdown().get();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (ExecutionException ex) {
// Continue
}
}
@Override @Override
public boolean isShuttingDown() { public boolean isShuttingDown() {
return this.shuttingDown; 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.eclipse.jetty.util.component.AbstractLifeCycle;
import org.springframework.boot.web.server.GracefulShutdown; 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.PortInUseException;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.server.WebServerException;
@ -101,20 +100,20 @@ public class JettyWebServer implements WebServer {
this.autoStart = autoStart; this.autoStart = autoStart;
Assert.notNull(server, "Jetty Server must not be null"); Assert.notNull(server, "Jetty Server must not be null");
this.server = server; this.server = server;
GracefulShutdown gracefulShutdown = null; this.gracefulShutdown = createGracefulShutdown(server, shutdownGracePeriod);
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;
initialize(); 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() { private void initialize() {
synchronized (this.monitor) { synchronized (this.monitor) {
try { try {

View File

@ -55,12 +55,7 @@ final class NettyGracefulShutdown implements GracefulShutdown {
} }
this.shuttingDown = true; this.shuttingDown = true;
try { try {
if (this.period != null) { disposeNow(server);
server.disposeNow(this.period);
}
else {
server.disposeNow();
}
logger.info("Graceful shutdown complete"); logger.info("Graceful shutdown complete");
return true; 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 @Override
public boolean isShuttingDown() { public boolean isShuttingDown() {
return this.shuttingDown; return this.shuttingDown;

View File

@ -35,7 +35,6 @@ import reactor.netty.http.server.HttpServerResponse;
import reactor.netty.http.server.HttpServerRoutes; import reactor.netty.http.server.HttpServerRoutes;
import org.springframework.boot.web.server.GracefulShutdown; 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.PortInUseException;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.server.WebServerException;
@ -96,7 +95,7 @@ public class NettyWebServer implements WebServer {
} }
else { else {
this.httpServer = httpServer; 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.apache.naming.ContextBindings;
import org.springframework.boot.web.server.GracefulShutdown; 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.PortInUseException;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.server.WebServerException;
@ -102,7 +101,7 @@ public class TomcatWebServer implements WebServer {
this.tomcat = tomcat; this.tomcat = tomcat;
this.autoStart = autoStart; this.autoStart = autoStart;
this.gracefulShutdown = (shutdownGracePeriod != null) ? new TomcatGracefulShutdown(tomcat, shutdownGracePeriod) this.gracefulShutdown = (shutdownGracePeriod != null) ? new TomcatGracefulShutdown(tomcat, shutdownGracePeriod)
: new ImmediateGracefulShutdown(); : GracefulShutdown.IMMEDIATE;
initialize(); 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.AbstractReactiveWebServerFactory;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.boot.web.server.GracefulShutdown; import org.springframework.boot.web.server.GracefulShutdown;
import org.springframework.boot.web.server.ImmediateGracefulShutdown;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter; import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -103,12 +102,12 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
} }
handler = UndertowCompressionConfigurer.configureCompression(getCompression(), handler); handler = UndertowCompressionConfigurer.configureCompression(getCompression(), handler);
Closeable closeable = null; Closeable closeable = null;
GracefulShutdown gracefulShutdown = null;
if (isAccessLogEnabled()) { if (isAccessLogEnabled()) {
AccessLogHandlerConfiguration accessLogHandlerConfiguration = configureAccessLogHandler(builder, handler); AccessLogHandlerConfiguration accessLogHandlerConfiguration = configureAccessLogHandler(builder, handler);
closeable = accessLogHandlerConfiguration.closeable; closeable = accessLogHandlerConfiguration.closeable;
handler = accessLogHandlerConfiguration.accessLogHandler; handler = accessLogHandlerConfiguration.accessLogHandler;
} }
GracefulShutdown gracefulShutdown = null;
GracefulShutdownHandler gracefulShutdownHandler = Handlers.gracefulShutdown(handler); GracefulShutdownHandler gracefulShutdownHandler = Handlers.gracefulShutdown(handler);
Duration gracePeriod = getShutdown().getGracePeriod(); Duration gracePeriod = getShutdown().getGracePeriod();
if (gracePeriod != null) { if (gracePeriod != null) {
@ -116,7 +115,7 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
handler = gracefulShutdownHandler; handler = gracefulShutdownHandler;
} }
else { else {
gracefulShutdown = new ImmediateGracefulShutdown(); gracefulShutdown = GracefulShutdown.IMMEDIATE;
} }
builder.setHandler(handler); builder.setHandler(handler);
return new UndertowWebServer(builder, getPort() >= 0, closeable, gracefulShutdown); 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.Compression;
import org.springframework.boot.web.server.GracefulShutdown; 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.PortInUseException;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.server.WebServerException;
@ -234,7 +233,7 @@ public class UndertowServletWebServer implements WebServer {
httpHandler = gracefulShutdownHandler; httpHandler = gracefulShutdownHandler;
} }
else { else {
this.gracefulShutdown = new ImmediateGracefulShutdown(); this.gracefulShutdown = GracefulShutdown.IMMEDIATE;
} }
this.builder.setHandler(httpHandler); this.builder.setHandler(httpHandler);
return this.builder.build(); return this.builder.build();

View File

@ -30,7 +30,6 @@ import org.apache.commons.logging.LogFactory;
import org.xnio.channels.BoundChannel; import org.xnio.channels.BoundChannel;
import org.springframework.boot.web.server.GracefulShutdown; 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.PortInUseException;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.server.WebServerException;
@ -84,7 +83,7 @@ public class UndertowWebServer implements WebServer {
* @since 2.0.4 * @since 2.0.4
*/ */
public UndertowWebServer(Undertow.Builder builder, boolean autoStart, Closeable closeable) { 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 { 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 * Shuts down the {@link WebServer}, returning {@code true} if activity ceased during
* the grace period, otherwise {@code false}. * 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. * A {@link GracefulShutdown} that returns immediately with no grace period.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.3.0
*/ */
public class ImmediateGracefulShutdown implements GracefulShutdown { class ImmediateGracefulShutdown implements GracefulShutdown {
@Override @Override
public boolean shutDownGracefully() { public boolean shutDownGracefully() {