由于最近刚步入工作,比较忙,很长时间没有去更新博客与整理知识了。
今天我们来聊一聊一些开发中常见但是又觉得比较恶心的代码,如何去处理这些问题呢。浏览过这篇博文之后,大家也可以提出自己的想法,是不是还有更好的解决方案。
目录
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的拷贝。
今天的分享就结束了,小伙伴们如果有更好的建议,欢迎提出指正,但是请不要谩骂与辱骂,写一篇博客实属不易。