在 Lombok 的构建器中使用自定义 setter

Use custom setter in Lombok's builder

我在基于 Lombok 的 POJO 中有一个自定义 setter:

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password = null;

    public void setPassword(String password) {
        Assert.notNull(password);
        this.password = ENCODER.encode(password);
    }

但是当我使用 Lombok 生成的构建器时:

User user = User.builder()
    .password(password)
    .build();

我的自定义 setter 未被调用,因此密码未被编码。这让我很难过。

我的自定义setter当然是在直接使用的时候调用:

public void changePassword(String password, User user) {
    user.setPassword(password);
}

如何让 Lombok 的构建器使用我的自定义 setter?

Per the documentation for @Builder: 自己定义足够的骨架即可。特别是,Lombok 将生成 class UserBuilder、镜像 User 字段的字段和构建器方法,您可以自己提供任何或全部。

@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String username;

    private String password;

    public static class UserBuilder {
        public UserBuilder password(String password) {
            this.password = ENCODER.encode(password);
            return this;
        }
    }
}

您使用的是 setPassword 而不是构建器的 set 方法。

以下是对我有用的方法:

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class User {
    private String username;
    private String password;

    public static class UserBuilder {
        private String password;
        public UserBuilder password(String password ) {
            this.password ="ENCRIYP " +  password;
            return this;
        }
    }

    public static void main(String[] args) {
        System.out.println(User.builder().username("This is my username").password("Password").build().toString());

    }
}

结果是: User(username=This is my username, password=ENCRIYP Password)

我已经接受了 chrylis 的回答,但为了完整起见,这里有一些方法可以最大限度地减少自定义和重复。

自定义 setter 和带静态助手的构建器

静态助手可用于在自定义 User.UserBuilder::password 方法和自定义 User::setPassword 方法中共享大部分 设置密码 功能:

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password = null;

    public void setPassword(String password) {
        this.password = _encodePassword(password);
    }

    public static class UserBuilder {
        public UserBuilder password(String password) {
            this.password = _encodePassword(password)
            return this;
        }
    }

    private static String _encodePassword(String password) {
        Assert.notNull(password);
        return ENCODER.encode(password);
    }
}

自定义setter和构造函数

自定义构造函数可以使用 User::setPassword,它由生成的 Lombok 调用 User.UserBuilder::build():

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password = null;

    User(String password) {
        setPassword(password);
    }

    public void setPassword(String password) {
        Assert.notNull(password);
        this.password = ENCODER.encode(password);
    }
}

自定义 setter 和带静态助手的构造函数

或者,更优雅一点,使用自定义构造函数和静态辅助方法:

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password = null;

    User(String password) {
        _encodePassword(password, this);
    }

    public void setPassword(String password) {
        _encodePassword(password, this);
    }

    private static _encodePassword(String password, User user) {
        Assert.notNull(password);
        user.password = ENCODER.encode(password);
    }
}

不是问题的真正答案,而是一个边缘案例,让我花了很多时间来找到问题。
如果您在具有 Builder.Default 值的字段上具有 Builder 和自定义 setter,则生成的构建器中的字段名称 class 与原始字段不同。事实上,您将有两个字段而不是一个字段:password$valuepassword$set.
不过,您可以通过这种方式在自定义 setter 中使用它们:

public UserBuilder password(String password) {
  this.password$value = ENCODER.encode(password);
  this.password$set = true;
  return this;
}

棘手的部分是,如果您使用原始字段名称,Intellij IDEA 不会警告您(好像一切都很好,但当然不会编译)。已经向插件提交错误报告。

我在工作中遇到过这种情况。下面是我想出的。这个想法是覆盖 Lombok 的 protected User(User.UserBuilder b) 方法

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password;
    private Integer otherField;

    protected User(final User.UserBuilder<?, ?> b) {
        setPassword(b.password);
        otherField = b.otherField;
    }

    public void setPassword(String password) {
        Assert.notNull(password);
        this.password = ENCODER.encode(password);
    }
}