I have @RestController
with the following mapping:
@GetMapping(value = "/periods"})
public PeriodInfoDto get(DateRange dateRange)
DateRange has three constructors:
public class DateRange {
@DateTimeFormat(iso=DateTimeFormat.ISO.DATE)
private YearMonth start;
@DateTimeFormat(iso=DateTimeFormat.ISO.DATE)
private YearMonth end;
public DateRange(YearMonth start, YearMonth end) {
this.start = start;
this.end = end;
}
public DateRange(YearMonth end, Period period) {
this(end.minus(period.minusMonths(1)), end);
}
When the client sends a request, the following exception is reported:
java.lang.IllegalStateException: No primary or single public constructor found for class com.example.domain.DateRange - and no default constructor found either at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:250) ~[spring-beans-5.3.5.jar:5.3.5]
DateRange
has a single primary constructor, DateRange(YearMonth, YearMonth)
. The other constructor call this.
The client passes in a full date string such as 2021-01-01 for end / start date request params, but I only care about the month and year.
Is there a way to tell Spring to use DateRange(YearMonth start, YearMonth end)
when binding the request?
CodePudding user response:
The stack trace indicates that Spring needs a so called primary constructor. For Java this means a single constructor that can be used. As you have 2 this mechanism fails.
You can work around this by removing the constructor using a YearMonth
and Period
and move that to a factory method and use that instead of the constructor.
public class DateRange {
private YearMonth start;
private YearMonth end;
public DateRange(YearMonth start, YearMonth end) {
this.start = start;
this.end = end;
}
public static DateRange of(YearMonth end, Period period) {
return new DateRange(end.minus(period.minusMonths(1)), end);
}
}
When using the format 2021-09 instead of a full date, Spring will automatically use the YearMonthFormatter
(available since Spring 4.2) to convert from/to a YearMonth
. So you don't need the @DateTimeFormat
annotation that way.
CodePudding user response:
Instead of adding new constructor you can move that logic to some instance method as shown in below code:
public class DateRange {
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private YearMonth start;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private YearMonth end;
public void init(YearMonth start, YearMonth end) {
this.start = start;
this.end = end;
}
public DateRange(YearMonth end, Period period) {
this.init(end.minus(period.minusMonths(1)), end);
}
}