简体   繁体   中英

Lombok @Builder with inheritance that requires a certain field

Java 11, Spring Boot and Lombok here. I am trying to build up a hierarchy of objects extending Spring's ApplicationEvent class which forces me to provide a constructor:

public ApplicationEvent(Object source) {
   ...
}

So extending ApplicationEvent I have a TrackedEvent that is an abstract base class for my other events to build off:

public abstract class TrackedEvent extends ApplicationEvent {

    private String correlationId;
    private String description;
    // ... lots of fields here

}

One of the (many) events extending TrackedEvent is OperationalEvent :

public class OperationalEvent extends TrackedEvent {

    private OperationType type;
    private OperationStatus status;
    // ... several more fields here

}

Other than the source: Object field, which is required by the grandparent ApplicationEvent constructor, all other TrackedEvent and OperationalEvent fields are optional. So because there are so many fields here to populate and instantiation-time, I feel the Builder Pattern is appropriate. So I'd like to employ Lombok's @Builder annotation (and possibly other annos) to obtain an API something like so:

OperationalEvent opsEvent = OperationalEvent.Builder(this)
    .description("something happened")
    .status(OperationalStatus.THE_THING)
    .build();

My thinking here is we would pass this in as a required builder constructor argument (to satisfy ApplicationEvent 's constructor for source: Object ...somehow) and then use the @Builder like normal.

Is this possible to do in Lombok, and if so, how?

As a fallback I can just write my own builders but since I have so many subclasses it would be great to leverage Lombok if at all possible.

Since you can't use @SuperBuilder annotation, beacause you don't have access to ApplicationEvent class, as far as I can see, the only solution is to do something like following:

Assume that you have TrackedEvent class wich looks something like this:

public class TrackedEvent extends ApplicationEvent {
    protected String correlationId;
    protected String description;

    public TrackedEvent(Object source, String correlationId, String description) {
        super(source);
        this.correlationId = correlationId;
        this.description = description;
    }

    public TrackedEvent(Object source) {
        super(source);
    }


}

And you have OperationalEvent class wich extends above class. Than, you can implement builder pattern in OptionalEvent class like this:

public class OperationalEvent extends TrackedEvent {

    private String type;
    private String status;

    public OperationalEvent(Object source) {
        super(source);
    }


    @Builder(builderMethodName = "optionalEvent")
    public OperationalEvent(Object source, String correlationId, String description, String type, String status) {
        super(source, correlationId, description);
        this.type = type;
        this.status = status;
    }

    @Override
    public String toString() {
        return "OperationalEvent{" +
                "type='" + type + '\'' +
                ", status='" + status + '\'' +
                "correlationId:" + correlationId +
                '}';
    }
}

And than the following code should work as expected:

public static void main(String[] args) {
    ApplicationEvent applicationEvent = new ApplicationEvent(new Object()) {
        @Override
        public Object getSource() {
            return super.getSource();
        }
    };
    OperationalEvent opsEvent = OperationalEvent.optionalEvent()
            .description("something happened")
            .source(applicationEvent)
            .status("status")
            .correlationId("1")
            .build();

}

You can use @SuperBuilder together with an intermediate abstract class, as described here .

The advantage over using customized @Builder s is that you only have to manually implement this single custom intermediate class once. There is no need for customization in every subclass.

In this case, the intermediate class looks as follows:

public abstract class ApplicationEventSuperBuilderEnabler extends ApplicationEvent {

    public static abstract class ApplicationEventSuperBuilderEnablerBuilder<C extends ApplicationEventSuperBuilderEnabler, B extends ApplicationEventSuperBuilderEnablerBuilder<C, B>> {
        private Object source;

        public B source(Object source) {
            this.source = source;
            return self();
        }

        protected abstract B self();

        public abstract C build();
    }

    protected ApplicationEventSuperBuilderEnabler(ApplicationEventSuperBuilderEnablerBuilder<?, ?> b) {
        super(b.source);
    }
}

Next, let all your direct subclasses extend ApplicationEventSuperBuilderEnabler (instead of ApplicationEvent ) and put a @SuperBuilder on all classes:

@SuperBuilder
public abstract class TrackedEvent extends ApplicationEventSuperBuilderEnabler { ... }

@SuperBuilder
public class OperationalEvent extends TrackedEvent { ... }

Now you can use it like this:

OperationalEvent opsEvent = OperationalEvent.builder()
        .source(this)
        .description("something happened")
        .status(OperationalStatus.THE_THING)
        .build();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM