Lombok与JavaBean之间不同名属性拷贝的思考

由于最近刚步入工作,比较忙,很长时间没有去更新博客与整理知识了。

今天我们来聊一聊一些开发中常见但是又觉得比较恶心的代码,如何去处理这些问题呢。浏览过这篇博文之后,大家也可以提出自己的想法,是不是还有更好的解决方案。

目录

1、lombok

 1.1 安装

1.2 使用

2、Bean之间拷贝不同名字段


1、lombok

  开发中,虽然我们使用IDE可以快捷键生成get/set/toString/hashCode/equals等这些方法,但是当字段特别多的时候,我们需要修改代码中的字段是非常常见的。如果每次修改字段都要去这样先删除然后再生成这些无聊的代码,是不是会比较费劲呢,而且一个没有任何逻辑的Bean代码过也会让自己看着也不舒服。

  lombok就是解决这种问题的,它通过一些注解,可以帮助我们生成get/set/toString/hashCode/equals这些代码,让我们的普通Bean可以只声明一些属性,让代码看起来更加直观。在这里,我们先不去了解lombok的原理是怎么样的,先看看如何使用,爽一爽再说。

 1.1 安装

在IntelliJ中安装lombok插件:

由于我这里已经事先安装好,在搜索栏中输入lombok,然后点击lombok plugin,再点击右边的Install即可。对于eclipse步骤,请小伙伴们自行谷歌。

安装好了以后,我们在pom.xml文件中添加以下依赖:

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.10</version>
        <scope>provided</scope>
    </dependency>

做完以上步骤之后,等待IDE自动导包完成,就可以使用了。

1.2 使用

我们在专门存放bean的包中创建一个UserInfo类,并加上这四个注解:

@Getter
@Setter
@ToString
@EqualsAndHashCode
public class UserInfo {
    private String id;
    private String username;
    private String password;
    private String birthday;
    private String sex;
    private String age;
    private String reverse;
    private String phoneNum;
    private String wechatNum;
}

是不是非常简单,看起来很直观。@Getter会帮你生成每个属性的getXXX方法,@Setter会帮你生成每个属性的setXXX方法,@ToString会帮你生成toString方法,@EqualsAndHashCode会帮你生成equals与hashcode方法。为了了解了解是真是假,我们来看一下这几个神奇的注解生成的方法结构:

再来看看这些注解帮我们生成的源代码,以后使用lombok的其他注解时如果你想了解它帮我们生成了什么代码都可以这么做:右击UserInfo这个bean,选择Reactor——>Delombok——>All lombok annotations

看看查看的结果:

这就是lombok帮我们生成的代码。嗯,感觉到确实很好用。可是这么多个注解,脑壳痛~~其实,我们使用一个@Data也可以了,@Data注解就包括了以上四个注解的功能:

//@Getter
//@Setter
//@ToString
//@EqualsAndHashCode
@Data
public class UserInfo {
    private String id;
    private String username;
    private String password;
    private String birthday;
    private String sex;
    private String age;
    private String reverse;
    private String phoneNum;
    private String wechatNum;
}

OK,大功告成,简单方便。不过,有一些小细节需要处理,当这个类有继承关系的时候,我们的的toString方法想要将父类的属性也能打印,那就需要加入值@ToString(callSuper="true"),对于比较两个对象也同时需要比较父类的属性时,需要加入值@EqualsAndHashCode(callSuper="true")

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UserInfo extends BaseInfo{
    private String id;
    private String username;
    private String password;
    private String birthday;
    private String sex;
    private String age;
    private String reverse;
    private String phoneNum;
    private String wechatNum;
}

默认@Data会帮我们生成一个无参数的构造方法,当我们自己声明了别的构造方法的时候,这个无参构造就不会再默认生成了。这时我们可以使用@NoArgsConstructor去帮Bean生成无参构造方法,可以用@AllArgsConstructor去生成全参的构造方法。

lombok还有很多很多其它的功能。比如可以生成链式调用的注解@Accessors(chain=true),在类上声明该注解后可以有链式调用的效果,调用方式如下:

UserInfo userInfo = new UserInfo();
        userInfo
                .setAge("22")
                .setBirthday("1995-01-01")
                .setPassword("123")
                .setReverse("aaa")
                ...

还可以使用@NonNull可以声明于成员变量或者参数上,标识他们不能为空,否则抛出空指针异常。@RequiredArgsConstructor声明于类上能够生成final属性或者被@NonNull标注了的属性的构造方法。@Buider可以声明在类上,可以将类直接转变成建造者模式生成实例,调用方式如下:

        UserInfo userInfo = UserInfo.builder()
                .age("12")
                .birthday("1990-01-01")
                .password("123")
                .phoneNum("123456")
                .wechatNum("aaa")
                .build();

@Slf4j、@Log4j可以声明在类上,帮我们自动生成日志变量,比如@Slf4j效果相当于:

 private static final Logger log = LoggerFactory.getLogger(UserInfo.class);

我们在代码中直接用log属性就可以了。

在处理流的时候,我们时常需要关闭流,@Cleanup作用于局部变量、参数上可以自动关闭流,这个注解只对实现了java.io.Closeable接口的对象有效。

我个人最常用的还是@Getter、@Setter、@Data、@ToString、@EqualsAndHashCode、@Buider、@Slf4j这几个注解,不知道小伙伴们有没有使用呢。lombok还有其它很多的功能,比如代理等等,想深入了解的小伙伴可以自行谷歌。关于这个插件,大家可以跟反自由,如果个人使用还无所谓,但是群体工作会牵连整个项目组都需要安装插件,得说服你的老大才行~

至于好处嘛,那就是偷偷懒了。

 

2、Bean之间拷贝不同名字段

  这是一个比较棘手的问题,没错。有时候setXXX代码可以在某个Controller中居然占据了百分之20-40的代码量,不知道小伙伴们能不能忍,反正我不能忍。有些小伙伴会说,我们可以使用BeanUtils.copyProperties(),当然,这只是在两个bean之间的属性名相同的时候使用还是蛮舒服的,可是如果属性名不相同的时候怎么办,没办法,只能硬着头皮干。

  我们在日常开发中,最常用的数据流套路就是:前端参数传入—>后台Controller方法使用一个DTOBean进行接收—>将DTOBean转化为实体EntityBean(实体类)——>业务处理、存储到数据库库或者查询数据库库—>返回查询实体EntityBean——>转化成返回给前端的DTOBean/VOBean——>结束,在这个流程中,存在着较多属性拷贝的操作,所以说场景还是非常常见的。在这之前,我们约定一个前提,由于某些问题所以两个Bean之间的多数属性名称是不同的。

  能不能找到一个办法优化一下呢,可以尝试把这些拷贝(完全无逻辑、业务的操作)代码内聚到DTOBean中,让这个只有传输作用的Bean带上自己的拷贝功能,首先看看我们的Entity:

@Data
@Accessors(chain = true)
@Entity
@Table(name = "t_user")
public class User {
    @Id
    private BigDecimal dataId;  //主键
    private String uName;       //用户名
    private String uPwd;        //密码
    @Temporal(TemporalType.DATE)
    private Date uAge;          //年龄
    private String uSex;        //性别
    private String phone;       //手机号
    private String wechat;      //微信号
}

假设DTOBean(前端传入的参数用这个Bean接收)中对拷贝操作进行了聚合,方法名字叫做convert,这里暂时先声明为空方法,看看我们的DTOBean:

@Data
@Accessors(chain = true)
public class UserInfo{

    private String id;          //主键
    private String username;    //用户名
    private String password;    //密码
    private String birthday;    //生日
    private String sex;         //性别
    private String age;         //年龄
    private String phoneNum;    //手机号
    private String wechatNum;   //微信号

    /**
     * 拷贝方法
     * @return
     */
    public User convert(){
        return null;
    }
}

如果这么干了之后,看看我们的Controller能够怎么调用:

 @RequestMapping("/save")
    public String save(@Valid UserInfo userDTO){
        //转换成entity
        User user = userDTO.convert();
        //保存
        userRepository.save(user);
        if(Objects.isNull(user.getDataId())){
            return "插入失败";
        }
        return "插入成功";
    }

拷贝操作都封装到convert里面去了,convert意为“转换”,看起来可以更加直观。这样改造了之后是不是觉得Controller清爽了很多呢。接下来看一看我们这个convert怎么写:

    /**
     * 拷贝方法
     *
     * @return
     */
    public User convert() {
        return new Function<UserInfo, User>() {
            @Override
            public User apply(UserInfo userInfo) {
                User user = new User();
                //纯属性拷贝操作,其中只掺杂有类型转换,没有业务、没有逻辑操作
                user
                        .setDataId(new BigDecimal(userInfo.getId()))
                        .setPhone(userInfo.getPhoneNum())
                        .setUAge(Integer.valueOf(userInfo.getAge()))
                        .setUName(userInfo.getUsername())
                        .setUPwd(userInfo.getPassword())
                        .setUSex(userInfo.getSex())
                        .setWechat(userInfo.getWechatNum());
                //如果有相同的一些属性,也能加上这句
                //BeanUtils.copyProperties(this, user);
                return user;
            }
        }.apply(this);
    }

除了这个匿名的Function,核心代码看起来一切都很正常,就是一些属性拷贝的操作与属性转换操作。为什么要画蛇添足,加上这么个Function的东西。Function<T,R>是Java8支持函数式编程的一个函数式接口,这个接口有个两个泛型<T,R>,T代表的是传入的参数,R代表的是返回的参数,这个函数式接口意义在于明确指出了入参与出参的类型,也就是转换操作的核心逻辑,如map()方法中接收的lambda表达式就是Function函数式接口。我们直接把上述代码变成匿名式的函数式简化吧:

    /**
     * 拷贝方法
     *
     * @return
     */
    public User convert() {
        return ((Function<UserInfo, User>) userInfo -> {
            User user = new User();
            //纯属性拷贝操作,其中只掺杂有类型转换,没有业务、没有逻辑操作
            user
                    .setDataId(new BigDecimal(userInfo.getId()))
                    .setPhone(userInfo.getPhoneNum())
                    .setUAge(Integer.valueOf(userInfo.getAge()))
                    .setUName(userInfo.getUsername())
                    .setUPwd(userInfo.getPassword())
                    .setUSex(userInfo.getSex())
                    .setWechat(userInfo.getWechatNum());
            //如果有相同的一些属性,也能加上这句
            //BeanUtils.copyProperties(this, user);
            return user;
        }).apply(this);
    }

引入Function的意义在于convert方法依托的是Function的apply方法,而不是自己去处理转化的实际操作。这可以使得转化的职责不在于convert方法本身,而是在于这个接口的实现。有时候,看着像是化蛇添足,其实是增强了方法的语义化与职责。无论convert方法是怎么实现的,最终的目的就在于处理了两个Bean的拷贝。

 

今天的分享就结束了,小伙伴们如果有更好的建议,欢迎提出指正,但是请不要谩骂与辱骂,写一篇博客实属不易。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页