Home > Net >  Is it possible to bind a Spring controller request parameter to an object with multiple constructors
Is it possible to bind a Spring controller request parameter to an object with multiple constructors

Time:09-17

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);
    }
}
  • Related