Support multiple style of parsing/printing Durations
This commit introduces a notion of different styles for the formatting of Duration. The `@DurationFormat` annotation is added to ease selection of a style, which are represented as DurationFormat.Style enum, as well as a supported time unit represented as DurationFormat.Unit enum. DurationFormatter has been retroffited to take such a Style, optionally, at construction. The default is still the JDK style a.k.a. ISO-8601. This introduces the new SIMPLE style which uses a single number + a short human-readable suffix. For instance "-3ms" or "2h". This has the same semantics as the DurationStyle in Spring Boot and is intended as a replacement for that feature, providing access to the feature to projects that only depend on Spring Framework. Finally, the `@Scheduled` annotation is improved by adding detection of the style and parsing for the String versions of initial delay, fixed delay and fixed rate. See gh-22013 See gh-22474 Closes gh-30396
This commit is contained in:
committed by
Brian Clozel
parent
d219362eb1
commit
c92e043bbc
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.format.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Declares that a field or method parameter should be formatted as a {@link java.time.Duration},
|
||||
* according to the specified {@code style}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
|
||||
public @interface DurationFormat {
|
||||
|
||||
/**
|
||||
* Which {@code Style} to use for parsing and printing a {@code Duration}. Defaults to
|
||||
* the JDK style ({@link Style#ISO8601}).
|
||||
*/
|
||||
Style style() default Style.ISO8601;
|
||||
|
||||
/**
|
||||
* Define which {@link Unit} to fall back to in case the {@code style()}
|
||||
* needs a unit for either parsing or printing, and none is explicitly provided in
|
||||
* the input ({@code Unit.MILLIS} if unspecified).
|
||||
*/
|
||||
Unit defaultUnit() default Unit.MILLIS;
|
||||
|
||||
/**
|
||||
* Duration format styles.
|
||||
*/
|
||||
enum Style {
|
||||
|
||||
/**
|
||||
* Simple formatting based on a short suffix, for example '1s'.
|
||||
* Supported unit suffixes are: {@code ns, us, ms, s, m, h, d}.
|
||||
* This corresponds to nanoseconds, microseconds, milliseconds, seconds,
|
||||
* minutes, hours and days respectively.
|
||||
* <p>Note that when printing a {@code Duration}, this style can be lossy if the
|
||||
* selected unit is bigger than the resolution of the duration. For example,
|
||||
* {@code Duration.ofMillis(5).plusNanos(1234)} would get truncated to {@code "5ms"}
|
||||
* when printing using {@code ChronoUnit.MILLIS}.
|
||||
*/
|
||||
SIMPLE,
|
||||
|
||||
/**
|
||||
* ISO-8601 formatting.
|
||||
* <p>This is what the JDK uses in {@link java.time.Duration#parse(CharSequence)}
|
||||
* and {@link Duration#toString()}.
|
||||
*/
|
||||
ISO8601
|
||||
}
|
||||
|
||||
/**
|
||||
* Duration format unit, which mirrors a subset of {@link ChronoUnit} and allows conversion to and from
|
||||
* supported {@code ChronoUnit} as well as converting durations to longs.
|
||||
* The enum includes its corresponding suffix in the {@link Style#SIMPLE simple} Duration format style.
|
||||
*/
|
||||
enum Unit {
|
||||
/**
|
||||
* Nanoseconds ({@code "ns"}).
|
||||
*/
|
||||
NANOS(ChronoUnit.NANOS, "ns", Duration::toNanos),
|
||||
|
||||
/**
|
||||
* Microseconds ({@code "us"}).
|
||||
*/
|
||||
MICROS(ChronoUnit.MICROS, "us", duration -> duration.toNanos() / 1000L),
|
||||
|
||||
/**
|
||||
* Milliseconds ({@code "ms"}).
|
||||
*/
|
||||
MILLIS(ChronoUnit.MILLIS, "ms", Duration::toMillis),
|
||||
|
||||
/**
|
||||
* Seconds ({@code "s"}).
|
||||
*/
|
||||
SECONDS(ChronoUnit.SECONDS, "s", Duration::getSeconds),
|
||||
|
||||
/**
|
||||
* Minutes ({@code "m"}).
|
||||
*/
|
||||
MINUTES(ChronoUnit.MINUTES, "m", Duration::toMinutes),
|
||||
|
||||
/**
|
||||
* Hours ({@code "h"}).
|
||||
*/
|
||||
HOURS(ChronoUnit.HOURS, "h", Duration::toHours),
|
||||
|
||||
/**
|
||||
* Days ({@code "d"}).
|
||||
*/
|
||||
DAYS(ChronoUnit.DAYS, "d", Duration::toDays);
|
||||
|
||||
private final ChronoUnit chronoUnit;
|
||||
|
||||
private final String suffix;
|
||||
|
||||
private final Function<Duration, Long> longValue;
|
||||
|
||||
Unit(ChronoUnit chronoUnit, String suffix, Function<Duration, Long> toUnit) {
|
||||
this.chronoUnit = chronoUnit;
|
||||
this.suffix = suffix;
|
||||
this.longValue = toUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this {@code DurationFormat.Unit} to its {@link ChronoUnit} equivalent.
|
||||
*/
|
||||
public ChronoUnit asChronoUnit() {
|
||||
return this.chronoUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this {@code DurationFormat.Unit} to a simple {@code String} suffix,
|
||||
* suitable for the {@link Style#SIMPLE} style.
|
||||
*/
|
||||
public String asSuffix() {
|
||||
return this.suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a {@code long} from a {@code String} and interpret it to be a {@code Duration}
|
||||
* in the current unit.
|
||||
* @param value the String representation of the long
|
||||
* @return the corresponding {@code Duration}
|
||||
*/
|
||||
public Duration parse(String value) {
|
||||
return Duration.of(Long.parseLong(value), asChronoUnit());
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a {@code Duration} as a {@code String}, converting it to a long value
|
||||
* using this unit's precision via {@link #longValue(Duration)} and appending
|
||||
* this unit's simple {@link #asSuffix() suffix}.
|
||||
* @param value the {@code Duration} to convert to String
|
||||
* @return the String representation of the {@code Duration} in the {@link Style#SIMPLE SIMPLE style}
|
||||
*/
|
||||
public String print(Duration value) {
|
||||
return longValue(value) + asSuffix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given {@code Duration} to a long value in the resolution of this
|
||||
* unit. Note that this can be lossy if the current unit is bigger than the
|
||||
* actual resolution of the duration.
|
||||
* <p>For example, {@code Duration.ofMillis(5).plusNanos(1234)} would get truncated
|
||||
* to {@code 5} for unit {@code MILLIS}.
|
||||
* @param value the {@code Duration} to convert to long
|
||||
* @return the long value for the Duration in this Unit
|
||||
*/
|
||||
public long longValue(Duration value) {
|
||||
return this.longValue.apply(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@code Unit} corresponding to the given {@code ChronoUnit}.
|
||||
* @throws IllegalArgumentException if that particular ChronoUnit isn't supported
|
||||
*/
|
||||
public static Unit fromChronoUnit(@Nullable ChronoUnit chronoUnit) {
|
||||
if (chronoUnit == null) {
|
||||
return Unit.MILLIS;
|
||||
}
|
||||
for (Unit candidate : values()) {
|
||||
if (candidate.chronoUnit == chronoUnit) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No matching Unit for ChronoUnit." + chronoUnit.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@code Unit} corresponding to the given {@code String} suffix.
|
||||
* @throws IllegalArgumentException if that particular suffix is unknown
|
||||
*/
|
||||
public static Unit fromSuffix(String suffix) {
|
||||
for (Unit candidate : values()) {
|
||||
if (candidate.suffix.equalsIgnoreCase(suffix)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("'" + suffix + "' is not a valid simple duration Unit");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -198,6 +198,7 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar {
|
||||
registry.addFormatterForFieldType(MonthDay.class, new MonthDayFormatter());
|
||||
|
||||
registry.addFormatterForFieldAnnotation(new Jsr310DateTimeFormatAnnotationFormatterFactory());
|
||||
registry.addFormatterForFieldAnnotation(new DurationFormatAnnotationFormatterFactory());
|
||||
}
|
||||
|
||||
private DateTimeFormatter getFormatter(Type type) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.format.datetime.standard;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.context.support.EmbeddedValueResolutionSupport;
|
||||
import org.springframework.format.AnnotationFormatterFactory;
|
||||
import org.springframework.format.Parser;
|
||||
import org.springframework.format.Printer;
|
||||
import org.springframework.format.annotation.DurationFormat;
|
||||
|
||||
/**
|
||||
* Formats fields annotated with the {@link DurationFormat} annotation using the
|
||||
* selected style for parsing and printing JSR-310 {@code Duration}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
* @see DurationFormat
|
||||
* @see DurationFormatter
|
||||
*/
|
||||
public class DurationFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
|
||||
implements AnnotationFormatterFactory<DurationFormat> {
|
||||
|
||||
// Create the set of field types that may be annotated with @DurationFormat.
|
||||
private static final Set<Class<?>> FIELD_TYPES = Set.of(Duration.class);
|
||||
|
||||
@Override
|
||||
public final Set<Class<?>> getFieldTypes() {
|
||||
return FIELD_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Printer<?> getPrinter(DurationFormat annotation, Class<?> fieldType) {
|
||||
return new DurationFormatter(annotation.style(), annotation.defaultUnit());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parser<?> getParser(DurationFormat annotation, Class<?> fieldType) {
|
||||
return new DurationFormatter(annotation.style(), annotation.defaultUnit());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,25 +21,75 @@ import java.time.Duration;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.format.Formatter;
|
||||
import org.springframework.format.annotation.DurationFormat;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link Formatter} implementation for a JSR-310 {@link Duration},
|
||||
* following JSR-310's parsing rules for a Duration.
|
||||
* following JSR-310's parsing rules for a Duration by default and
|
||||
* supporting additional {@code DurationFormat.Style} styles.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.2.4
|
||||
* @see Duration#parse
|
||||
* @since 6.2
|
||||
* @see DurationFormatterUtils
|
||||
* @see DurationFormat.Style
|
||||
*/
|
||||
class DurationFormatter implements Formatter<Duration> {
|
||||
public class DurationFormatter implements Formatter<Duration> {
|
||||
|
||||
private final DurationFormat.Style style;
|
||||
@Nullable
|
||||
private final DurationFormat.Unit defaultUnit;
|
||||
|
||||
/**
|
||||
* Create a {@code DurationFormatter} following JSR-310's parsing rules for a Duration
|
||||
* (the {@link DurationFormat.Style#ISO8601 ISO-8601} style).
|
||||
*/
|
||||
DurationFormatter() {
|
||||
this(DurationFormat.Style.ISO8601);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code DurationFormatter} in a specific {@link DurationFormat.Style}.
|
||||
* <p>When a unit is needed but cannot be determined (e.g. printing a Duration in the
|
||||
* {@code SIMPLE} style), {@code DurationFormat.Unit#MILLIS} is used.
|
||||
*/
|
||||
public DurationFormatter(DurationFormat.Style style) {
|
||||
this(style, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code DurationFormatter} in a specific {@link DurationFormat.Style} with an
|
||||
* optional {@code DurationFormat.Unit}.
|
||||
* <p>If a {@code defaultUnit} is specified, it may be used in parsing cases when no
|
||||
* unit is present in the string (provided the style allows for such a case). It will
|
||||
* also be used as the representation's resolution when printing in the
|
||||
* {@link DurationFormat.Style#SIMPLE} style. Otherwise, the style defines its default
|
||||
* unit.
|
||||
*
|
||||
* @param style the {@code DurationStyle} to use
|
||||
* @param defaultUnit the {@code DurationFormat.Unit} to fall back to when parsing and printing
|
||||
*/
|
||||
public DurationFormatter(DurationFormat.Style style, @Nullable DurationFormat.Unit defaultUnit) {
|
||||
this.style = style;
|
||||
this.defaultUnit = defaultUnit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration parse(String text, Locale locale) throws ParseException {
|
||||
return Duration.parse(text);
|
||||
if (this.defaultUnit == null) {
|
||||
//delegate to the style
|
||||
return DurationFormatterUtils.parse(text, this.style);
|
||||
}
|
||||
return DurationFormatterUtils.parse(text, this.style, this.defaultUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String print(Duration object, Locale locale) {
|
||||
return object.toString();
|
||||
if (this.defaultUnit == null) {
|
||||
//delegate the ultimate of the default unit to the style
|
||||
return DurationFormatterUtils.print(object, this.style);
|
||||
}
|
||||
return DurationFormatterUtils.print(object, this.style, this.defaultUnit);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.format.datetime.standard;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.format.annotation.DurationFormat;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Support {@code Duration} parsing and printing in several styles, as listed in
|
||||
* {@link DurationFormat.Style}.
|
||||
* <p>Some styles may not enforce any unit to be present, defaulting to {@code DurationFormat.Unit#MILLIS}
|
||||
* in that case. Methods in this class offer overloads that take a {@link DurationFormat.Unit} to
|
||||
* be used as a fall-back instead of the ultimate MILLIS default.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Valentine Wu
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
*/
|
||||
public abstract class DurationFormatterUtils {
|
||||
|
||||
private DurationFormatterUtils() {
|
||||
// singleton
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given value to a duration.
|
||||
* @param value the value to parse
|
||||
* @param style the style in which to parse
|
||||
* @return a duration
|
||||
*/
|
||||
public static Duration parse(String value, DurationFormat.Style style) {
|
||||
return parse(value, style, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given value to a duration.
|
||||
* @param value the value to parse
|
||||
* @param style the style in which to parse
|
||||
* @param unit the duration unit to use if the value doesn't specify one ({@code null}
|
||||
* will default to ms)
|
||||
* @return a duration
|
||||
*/
|
||||
public static Duration parse(String value, DurationFormat.Style style, @Nullable DurationFormat.Unit unit) {
|
||||
return switch (style) {
|
||||
case ISO8601 -> parseIso8601(value);
|
||||
case SIMPLE -> parseSimple(value, unit);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the specified duration in the specified style.
|
||||
* @param value the value to print
|
||||
* @param style the style to print in
|
||||
* @return the printed result
|
||||
*/
|
||||
public static String print(Duration value, DurationFormat.Style style) {
|
||||
return print(value, style, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the specified duration in the specified style using the given unit.
|
||||
* @param value the value to print
|
||||
* @param style the style to print in
|
||||
* @param unit the unit to use for printing, if relevant ({@code null} will default
|
||||
* to ms)
|
||||
* @return the printed result
|
||||
*/
|
||||
public static String print(Duration value, DurationFormat.Style style, @Nullable DurationFormat.Unit unit) {
|
||||
return switch (style) {
|
||||
case ISO8601 -> value.toString();
|
||||
case SIMPLE -> printSimple(value, unit);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the style then parse the value to return a duration.
|
||||
* @param value the value to parse
|
||||
* @return the parsed duration
|
||||
* @throws IllegalArgumentException if the value is not a known style or cannot be
|
||||
* parsed
|
||||
*/
|
||||
public static Duration detectAndParse(String value) {
|
||||
return detectAndParse(value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the style then parse the value to return a duration.
|
||||
* @param value the value to parse
|
||||
* @param unit the duration unit to use if the value doesn't specify one ({@code null}
|
||||
* will default to ms)
|
||||
* @return the parsed duration
|
||||
* @throws IllegalArgumentException if the value is not a known style or cannot be
|
||||
* parsed
|
||||
*/
|
||||
public static Duration detectAndParse(String value, @Nullable DurationFormat.Unit unit) {
|
||||
return parse(value, detect(value), unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the style from the given source value.
|
||||
* @param value the source value
|
||||
* @return the duration style
|
||||
* @throws IllegalArgumentException if the value is not a known style
|
||||
*/
|
||||
public static DurationFormat.Style detect(String value) {
|
||||
Assert.notNull(value, "Value must not be null");
|
||||
// warning: the order of parsing starts to matter if multiple patterns accept a plain integer (no unit suffix)
|
||||
if (ISO_8601_PATTERN.matcher(value).matches()) {
|
||||
return DurationFormat.Style.ISO8601;
|
||||
}
|
||||
if (SIMPLE_PATTERN.matcher(value).matches()) {
|
||||
return DurationFormat.Style.SIMPLE;
|
||||
}
|
||||
throw new IllegalArgumentException("'" + value + "' is not a valid duration, cannot detect any known style");
|
||||
}
|
||||
|
||||
private static final Pattern ISO_8601_PATTERN = Pattern.compile("^[+-]?[pP].*$");
|
||||
private static final Pattern SIMPLE_PATTERN = Pattern.compile("^([+-]?\\d+)([a-zA-Z]{0,2})$");
|
||||
|
||||
private static Duration parseIso8601(String value) {
|
||||
try {
|
||||
return Duration.parse(value);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalArgumentException("'" + value + "' is not a valid ISO-8601 duration", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Duration parseSimple(String text, @Nullable DurationFormat.Unit fallbackUnit) {
|
||||
try {
|
||||
Matcher matcher = SIMPLE_PATTERN.matcher(text);
|
||||
Assert.state(matcher.matches(), "Does not match simple duration pattern");
|
||||
String suffix = matcher.group(2);
|
||||
DurationFormat.Unit parsingUnit = (fallbackUnit == null ? DurationFormat.Unit.MILLIS : fallbackUnit);
|
||||
if (StringUtils.hasLength(suffix)) {
|
||||
parsingUnit = DurationFormat.Unit.fromSuffix(suffix);
|
||||
}
|
||||
return parsingUnit.parse(matcher.group(1));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException("'" + text + "' is not a valid simple duration", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static String printSimple(Duration duration, @Nullable DurationFormat.Unit unit) {
|
||||
unit = (unit == null ? DurationFormat.Unit.MILLIS : unit);
|
||||
return unit.print(duration);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -141,15 +141,22 @@ public @interface Scheduled {
|
||||
/**
|
||||
* Execute the annotated method with a fixed period between the end of the
|
||||
* last invocation and the start of the next.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>This attribute variant supports Spring-style "${...}" placeholders
|
||||
* as well as SpEL expressions.
|
||||
* <p>The duration String can be in several formats:
|
||||
* <ul>
|
||||
* <li>a plain integer — which is interpreted to represent a duration in
|
||||
* milliseconds by default unless overridden via {@link #timeUnit()} (prefer
|
||||
* using {@link #fixedDelay()} in that case)</li>
|
||||
* <li>any of the known {@link org.springframework.format.annotation.DurationFormat.Style
|
||||
* DurationFormat.Style}: the {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 ISO8601}
|
||||
* style or the {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE SIMPLE} style
|
||||
* — using the {@link #timeUnit()} as fallback if the string doesn't contain an explicit unit</li>
|
||||
* </ul>
|
||||
* <p><b>NOTE: With virtual threads, fixed rates and cron triggers are recommended
|
||||
* over fixed delays.</b> Fixed-delay tasks operate on a single scheduler thread
|
||||
* with {@link org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler}.
|
||||
* @return the delay as a String value — for example, a placeholder
|
||||
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
|
||||
* @return the delay as a String value — for example a placeholder,
|
||||
* or a {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 java.time.Duration} compliant value
|
||||
* or a {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE simple format} compliant value
|
||||
* @since 3.2.2
|
||||
* @see #fixedDelay()
|
||||
*/
|
||||
@@ -165,12 +172,20 @@ public @interface Scheduled {
|
||||
|
||||
/**
|
||||
* Execute the annotated method with a fixed period between invocations.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>This attribute variant supports Spring-style "${...}" placeholders
|
||||
* as well as SpEL expressions.
|
||||
* @return the period as a String value — for example, a placeholder
|
||||
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
|
||||
* <p>The duration String can be in several formats:
|
||||
* <ul>
|
||||
* <li>a plain integer — which is interpreted to represent a duration in
|
||||
* milliseconds by default unless overridden via {@link #timeUnit()} (prefer
|
||||
* using {@link #fixedDelay()} in that case)</li>
|
||||
* <li>any of the known {@link org.springframework.format.annotation.DurationFormat.Style
|
||||
* DurationFormat.Style}: the {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 ISO8601}
|
||||
* style or the {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE SIMPLE} style
|
||||
* — using the {@link #timeUnit()} as fallback if the string doesn't contain an explicit unit</li>
|
||||
* <li>one of the above, with Spring-style "${...}" placeholders as well as SpEL expressions</li>
|
||||
* </ul>
|
||||
* @return the period as a String value — for example a placeholder,
|
||||
* or a {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 java.time.Duration} compliant value
|
||||
* or a {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE simple format} compliant value
|
||||
* @since 3.2.2
|
||||
* @see #fixedRate()
|
||||
*/
|
||||
@@ -189,12 +204,20 @@ public @interface Scheduled {
|
||||
/**
|
||||
* Number of units of time to delay before the first execution of a
|
||||
* {@link #fixedRate} or {@link #fixedDelay} task.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>This attribute variant supports Spring-style "${...}" placeholders
|
||||
* as well as SpEL expressions.
|
||||
* @return the initial delay as a String value — for example, a placeholder
|
||||
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
|
||||
* <p>The duration String can be in several formats:
|
||||
* <ul>
|
||||
* <li>a plain integer — which is interpreted to represent a duration in
|
||||
* milliseconds by default unless overridden via {@link #timeUnit()} (prefer
|
||||
* using {@link #fixedDelay()} in that case)</li>
|
||||
* <li>any of the known {@link org.springframework.format.annotation.DurationFormat.Style
|
||||
* DurationFormat.Style}: the {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 ISO8601}
|
||||
* style or the {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE SIMPLE} style
|
||||
* — using the {@link #timeUnit()} as fallback if the string doesn't contain an explicit unit</li>
|
||||
* <li>one of the above, with Spring-style "${...}" placeholders as well as SpEL expressions</li>
|
||||
* </ul>
|
||||
* @return the initial delay as a String value — for example a placeholder,
|
||||
* or a {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 java.time.Duration} compliant value
|
||||
* or a {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE simple format} compliant value
|
||||
* @since 3.2.2
|
||||
* @see #initialDelay()
|
||||
*/
|
||||
|
||||
@@ -59,6 +59,8 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.format.annotation.DurationFormat;
|
||||
import org.springframework.format.datetime.standard.DurationFormatterUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
@@ -567,21 +569,10 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
}
|
||||
|
||||
private static Duration toDuration(String value, TimeUnit timeUnit) {
|
||||
if (isDurationString(value)) {
|
||||
return Duration.parse(value);
|
||||
}
|
||||
return toDuration(Long.parseLong(value), timeUnit);
|
||||
DurationFormat.Unit unit = DurationFormat.Unit.fromChronoUnit(timeUnit.toChronoUnit());
|
||||
return DurationFormatterUtils.detectAndParse(value, unit); // interpreting as long as fallback already
|
||||
}
|
||||
|
||||
private static boolean isDurationString(String value) {
|
||||
return (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1))));
|
||||
}
|
||||
|
||||
private static boolean isP(char ch) {
|
||||
return (ch == 'P' || ch == 'p');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return all currently scheduled tasks, from {@link Scheduled} methods
|
||||
* as well as from programmatic {@link SchedulingConfigurer} interaction.
|
||||
|
||||
Reference in New Issue
Block a user