标签 工具类 下的文章 - 酷游博客
首页
关于
友链
Search
1
阿里的简历多久可以投递一次?次数多了有没有影响?可以同时进行吗?
45 阅读
2
Java中泛型的理解
40 阅读
3
Java 14 发布了,再也不怕 NullPointerException 了!
38 阅读
4
Java中的可变参数
37 阅读
5
该如何创建字符串,使用" "还是构造函数?
29 阅读
技术
登录
/
注册
找到
3
篇与
工具类
相关的结果
2025-01-22
新来个技术总监,禁止我们使用Lombok!
我有个学弟,在一家小型互联网公司做Java后端开发,最近他们公司新来了一个技术总监,这位技术总监对技术细节很看重,一来公司之后就推出了很多”政策”,比如定义了很多开发规范、日志规范、甚至是要求大家统一使用某一款IDE。 但是这些都不是我这个学弟和我吐槽的点,他真正和我吐槽的是,他很不能理解,这位新来的技术总监竟然禁止公司内部所有开发使用Lombok。但是又没给出十分明确的,可以让人信服的理由。 于是他来找我聊天,问我这个要求到底是否合理。关于这个事情,我认为这位技术总监的出发点是好的,但是做法未免有些极端。 之所以说出发点是好的,是因为使用Lombok确实会带来很多问题,而且我个人在工作中也基本不主动使用。 之所以说不主动使用,那是因为有些同事的代码还是使用了的,所以我也被迫的要安装Lombok的插件。 既然聊到这个话题,就简单说说我的一些看法。 Lombok有什么好处? Lombok是一款非常实用Java工具,可用来帮助开发人员消除Java的冗长代码,尤其是对于简单的Java对象(POJO)。它通过注释实现这一目的。 如果大家对于Lombok比较了解的话,可以先跳过这一段,直接往后看,如果不是很熟悉的话,可以简单了解一下。 想在项目中使用Lombok,需要三个步骤: 一、IDE中安装Lombok插件 目前Lombok支持多种IDE,其中包括主流的Eclips、Intellji IDEA、Myeclipse等都是支持的。 在IDEA中安装方式如下:  二、导入相关依赖 Lombok 支持使用多重构建工具进行导入依赖,目前主要支持maven、gardle、ant等均支持。 如使用maven导入方式如下: org.projectlombok lombok 1.18.12 provided 三、代码中使用注解 Lombok精简代码的方式主要是通过注解来实现,其中常用的有@Data、@Getter/@Setter、@Builder、@NonNull等。 如使用@Data注解,即可简单的定义一个Java Bean: import lombok.Data; @Data public class Menu { private String shopId; private String skuMenuId; private String skuName; } 使用@Data注解在类上,相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstrutor这些注解,对于POJO类十分有用。 即自动帮忙给例子中的Menu类中定义了toString、Getter、Setter等方法。 通过上面的例子,大家可以发现,我们是好用@Data注解大大减少了代码量,使代码非常简洁。这也是很多开发者热衷于使用Lombok的主要原因。 另外,关于Lombok的使用,不同人有不同的看法,因为很多人都使用过Lombok,对于他的优点都比较了解,所以接下来我们重点说一下Lombok的使用会带来哪些问题。 Lombok有什么坏处? 强X队友 因为Lombok的使用要求开发者一定要在IDE中安装对应的插件。 如果未安装插件的话,使用IDE打开一个基于Lombok的项目的话会提示找不到方法等错误。导致项目编译失败。 也就是说,如果项目组中有一个人使用了Lombok,那么其他人就必须也要安装IDE插件。否则就没办法协同开发。 更重要的是,如果我们定义的一个jar包中使用了Lombok,那么就要求所有依赖这个jar包的所有应用都必须安装插件,这种侵入性是很高的。 代码可读性,可调试性低 在代码中使用了Lombok,确实可以帮忙减少很多代码,因为Lombok会帮忙自动生成很多代码。 但是这些代码是要在编译阶段才会生成的,所以在开发的过程中,其实很多代码其实是缺失的。 在代码中大量使用Lombok,就导致代码的可读性会低很多,而且也会给代码调试带来一定的问题。 比如,我们想要知道某个类中的某个属性的getter方法都被哪些类引用的话,就没那么简单了。 有坑 因为Lombok使代码开发非常简便,这就使得部分开发者对其产生过度依赖。 在使用Lombok过程中,如果对于各种注解的底层原理不理解的话,很容易产生意想不到的结果。 举一个简单的例子,我们知道,当我们使用@Data定义一个类的时候,会自动帮我们生成equals()方法 。 但是如果只使用了@Data,而不使用@EqualsAndHashCode(callSuper=true)的话,会默认是@EqualsAndHashCode(callSuper=false),这时候生成的equals()方法只会比较子类的属性,不会考虑从父类继承的属性,无论父类属性访问权限是否开放。 这就可能得到意想不到的结果。 影响升级 因为Lombok对于代码有很强的侵入性,就可能带来一个比较大的问题,那就是会影响我们对JDK的升级。 按照如今JDK的升级频率,每半年都会推出一个新的版本,但是Lombok作为一个第三方工具,并且是由开源团队维护的,那么他的迭代速度是无法保证的。 所以,如果我们需要升级到某个新版本的JDK的时候,若其中的特性在Lombok中不支持的话就会受到影响。 还有一个可能带来的问题,就是Lombok自身的升级也会受到限制。 因为一个应用可能依赖了多个jar包,而每个jar包可能又要依赖不同版本的Lombok,这就导致在应用中需要做版本仲裁,而我们知道,jar包版本仲裁是没那么容易的,而且发生问题的概率也很高。 破坏封装性 以上几个问题,我认为都是有办法可以避免的。但是有些人排斥使用Lombok还有一个重要的原因,那就是他会破坏封装性。 众所周知,Java的三大特性包括封装性、继承性和多态性。 如果我们在代码中直接使用Lombok,那么他会自动帮我们生成getter、setter 等方法,这就意味着,一个类中的所有参数都自动提供了设置和读取方法。 举个简单的例子,我们定义一个购物车类: @Data public class ShoppingCart { //商品数目 private int itemsCount; //总价格 private double totalPrice; //商品明细 private List items = new ArrayList(); } //例子来源于《极客时间-设计模式之美》 我们知道,购物车中商品数目、商品明细以及总价格三者之前其实是有关联关系的,如果需要修改的话是要一起修改的。 但是,我们使用了Lombok的@Data注解,对于itemsCount 和 totalPrice这两个属性。虽然我们将它们定义成 private 类型,但是提供了 public 的 getter、setter 方法。 外部可以通过 setter 方法随意地修改这两个属性的值。我们可以随意调用 setter 方法,来重新设置 itemsCount、totalPrice 属性的值,这也会导致其跟 items 属性的值不一致。 而面向对象封装的定义是:通过访问权限控制,隐藏内部数据,外部仅能通过类提供的有限的接口访问、修改内部数据。所以,暴露不应该暴露的 setter 方法,明显违反了面向对象的封装特性。 好的做法应该是不提供getter/setter,而是只提供一个public的addItem方法,同时取修改itemsCount、totalPrice以及items三个属性。 总结 本文总结了常用的Java开发工具Lombok的优缺点。 优点是使用注解即可帮忙自动生成代码,大大减少了代码量,使代码非常简洁。 但是并不意味着Lombok的使用没有任何问题,在使用Lombok的过程中,还可能存在对队友不友好、对代码不友好、对调试不友好、对升级不友好等问题。 最重要的是,使用Lombok还会导致破坏封装性的问题。 虽然使用Lombok存在着很多方便,但是也带来了一些问题。 但是到底建不建议在日常开发中使用,我其实保持一个中立的态度,不建议大家过度依赖,也不要求大家一定要彻底不用。 只要大家在使用的过程中,或者评估要不要在代码中引入Lombok之前,在想到他的优点的同时,能够考虑到他给代码带来的问题的,那么本文的目的也就达到了! 参考资料: https://time.geekbang.org/column/article/164907 https://projectlombok.org/
技术
# 工具类
酷游
1月22日
0
3
0
2025-01-22
日志打印出来的对象都是XXX@39ddf169这样的,怎么办?
我们在开发的时候,经常要打印日志,有的时候会在一些代码的关键节点处进行日志输出。 使用logback/log4j等原生的日志框架,在日志输出的时候可能会遇到一个问题,那就是经常我们要打印对象的时候,如以下代码: log.info("req = {}", aRequest); 打印结果却是以下形式: com.hollis.java.ways.ApplyRequest@39ddf169 其实原因比较简单,那就是要打印的对象没有重写toString方法,这样无法将该对象的参数打印出来。 所以,为了可以把对象的值都打印出来,我们一般要求对于自己定义的入参、出参等定义toString方法。 但是有些时候,我们使用的是外部定义的request和response对象,他们并没有覆盖toString,当对这些对象打印的时候,就会出现以上问题。 一般简单的解决办法是,可以通过JSON把对象转成String,如: log.info("req = {}", JSON.toJSONString(aRequest)); 日志输出: req = {"name":"Hollis","wechat":"hollischuang","javaways":"Java之道"} 但是,这样的话,就需要在每一个日志记录的地方人为的要处理下,很不高效,而且也容易遗忘。 作为程序员,要想办法干掉这种手动操作,有一个好的办法,可以一劳永逸: 借助logback(log4j也有类似的功能)的MessageConverter。无侵入性的解决这个问题 1、自定义一个Layout /** * 参数JSON格式化类 * * @author Hollis */ public class ArgumentJsonFormatLayout extends MessageConverter { @Override public String convert(ILoggingEvent event) { try { return MessageFormatter.arrayFormat(event.getMessage(), Stream.of(event.getArgumentArray()) .map(JSON::toJSONString).toArray()).getMessage(); } catch (Exception e) { return event.getMessage(); } } } 2、在logback中配置上这个Layout 这样,就可以直接使用log.info("req = {}",obj)这样的形式记录日志了。
技术
# 工具类
酷游
1月22日
0
20
0
2025-01-22
使用Dozer优雅的将DO转换成VO
在Web开发中,我们会接触到很多领域模型中的概念,其中大部分和实体相关的概念都有缩写,一般以O(Object)结尾。其中比较常见的由DO、DTO、VO、DAO等。我们也经常有把一个实体对象转换为另外一个实体对象的操作。本文主要是介绍一种作者在实践中总结的一种自认为比较优雅的转换方式。欢迎拍砖。 什么是DO、DTO和VO 在Java中 VO、 PO、DO、DTO、 BO、 QO、DAO、POJO的概念中介绍过Java中的各种模型概念。在这里简单再总结一下: 在日常的项目开发中,VO对应于页面上需要显示的数据(表单),DO对应于数据库中存储的数据(数据表),DTO对应于除二者之外需要进行传递的数据。 很多人可能对VO和DTO并不是那么熟悉,相反对DO却比较熟悉,那是因为在很多项目中由于种种原因我们只使用了DO,原因可能有以下几种: 1、项目太小,对于一种业务实体,封装成一个DO就够了。 2、并不熟悉DTO、VO,更不知道他们之间的区别。 3、了解DO\DTO\VO之间的区别,但是懒得用。 那么,这里,博主再啰嗦一下为什么要引入这么多概念,为什么我要建议大家在自己的项目中使用这些实体对象。 为什么不能只用一个DO 我们来看这样一个例子。假如我们的项目中由User这样一个实体。我们在创建User表的时候一般包含一下属性: 针对这个实体,我们通常需要创建一个DO类,用来封装这个User实体。 public class UserDO { private Integer id; //唯一主键 private Date createdTime; //创建时间 private Date updatedTime; //最后更新时间 private String name; //姓名 private Integer age; //年龄 private String gender; //性别 private String address; //住址 private String password; //密码 private String nickName; //昵称 private Date birthday; //生日 private String politicalStatus; //政治面貌,1表示群众,2表示团员,3表示党员,4表示其他,100表示未知 private Integer companyId; //公司的ID private Integer status; //数据状态,1表示可用,0表示不可用 //setter and getter } 然后,在代码中,从DAO一直到前端展示,我们都通过这个UserDO类的对象来进行数据传输。这样做会有什么问题嘛? 不需要的字段也会传递到前端页面。 如password、createdTime、updatedTime和status这几个字段我们可能在前端根本不需要展示,但是这些字段有可能也会被传递到前端(除非我们在SQL查询的时候没有查询出对应的字段)。这不仅使数据的传输量增大,还可能有安全性问题。 某些字段需要转换,但是无法支持。 对于上面例子中的政治面貌字段,我们在数据库中存储的是数字,但是在前端页面我要展示的是中文描述。这种情况只能在前端通过if/else的方式来分情况展示。 某些字段要展示,但是并不希望出现在数据库中 在User表中我们只保存了这个用户的companyId,需要同时查询company表来查询出该公司的更多详细信息。对于User对象,如果我们想在前端同时展示他所属的公司,希望通过一次查询全都查出来怎么办?有几个简单的方案,第一个是让UserDO中包含一个Company类的属性,通过这个属性来传递。另外一种是把我们希望传到前端的Company的属性也写到UserDO中。但是,如果真的这么做了,那UserDO还能被称作DO了吗? 还有很多问题,这这里就不详细介绍了。 可见,使用一个DO从头用到尾(从数据库到前端页面)并不是一种好的设计。 如何正确的使用DO、DTO、VO 对于上面的例子,我们可以将他设计成以下类。由于模型并不复杂,这里只需要再引入VO就可以了。 UserDO已经和数据库字段一一对应了,这里不需要修改。 public class UserDO { private Integer id; //唯一主键 private Date createdTime; //创建时间 private Date updatedTime; //最后更新时间 private String name; //姓名 private Integer age; //年龄 private String gender; //性别 private String address; //住址 private String password; //密码 private String nickName; //昵称 private Date birthday; //生日 private String education; //学历 private String politicalStatus; //政治面貌,1表示群众,2表示团员,3表示党员,4表示其他,100表示未知 private Integer companyId; //公司的ID private Integer status; //数据状态,1表示可用,0表示不可用 //setter and getter } 引入UserVO,用于封装传递到前端需要展示的字段。 public class UserVO { private Integer id; //唯一主键 private String name; //姓名 private Integer age; //年龄 private String gender; //性别 private String address; //住址 private String nickName; //昵称 private Date birthday; //生日 private String education; //学历 private String politicalStatus; //政治面貌,群众、团员、党员等 private Company company; //公司 //setter and getter } UserVO中只包含了展示所需要的字段,并不需要展示的字段在这里不需要包含。 在引入了这个类之后,我们就可在进行数据库查询的时候使用UserDO,然后再需要传递到前端的时候把DO转换成VO。 总结 看到这里,你可能已经发现,UserDO和UserVO中包含了大量的相同字段。难道真的有必要再单独设计个VO嘛?我可以明确告诉你的是,当你的系统越来越大,表中的字段越来越多的时候,使用DO\DTO\VO等概念进行分层处理是绝对有好处的。至于如何进行有效的在不同的实体类间进行转换是我接下来要介绍的。 优雅的将DO转换成VO Dozer 是一个对象转换工具。 Dozer可以在JavaBean到JavaBean之间进行递归数据复制,并且这些JavaBean可以是不同的复杂的类型。所有的mapping,Dozer将会很直接的将名称相同的fields进行复制,如果field名不同,或者有特别的对应要求,则可以在xml中进行定义。 前面我们介绍的DO\DTO\VO不就是JavaBean嘛。正好可以使用Dozer进行转换呀。除了使用Dozer,当然你还由其他选择: 典型的解决方案就是手动拷贝,弊端很明显,代码中充斥大量Set 和Get方法,真正的业务被埋藏值与值的拷贝之中。 另一种方案就是使用BeanUtil,但BeanUtil不够很好的灵活性,又时候还不得不手动拷贝。Dozer可以灵活的对对象进行转换,且使用简单。 Dozer 支持的转换类型 Primitive 基本数据类型 , 后面带 Wrapper 是包装类 Complex Type 是复杂类型 • Primitive to Primitive Wrapper • Primitive to Custom Wrapper • Primitive Wrapper to Primitive Wrapper • Primitive to Primitive • Complex Type to Complex Type • String to Primitive • String to Primitive Wrapper • String to Complex Type if the Complex Type contains a String constructor • String 到复杂类型 , 如果复杂类型包含一个 String 类型的构造器 • String to Map • Collection to Collection • Collection to Array • Map to Complex Type • Map to Custom Map Type • Enum to Enum • Each of these can be mapped to one another: java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.util.Calendar, java.util.GregorianCalendar • String to any of the supported Date/Calendar Objects. • Objects containing a toString() method that produces a long representing time in (ms) to any supported Date/Calendar object. 在普通Java项目中使用Dozer 在pom.xml中增加依赖 net.sf.dozer dozer 5.5.1 使用Dozer进行类转换 public class Main { public static void main(String[] args) { DozerBeanMapper mapper = new DozerBeanMapper(); UserDO userDO = new UserDO(); userDO.setName("hollis"); userDO.setAddress("hz"); userDO.setAge(25); userDO.setCompanyId(1); userDO.setBirthday(new Date()); userDO.setGender("male"); userDO.setEducation("1"); userDO.setNickName("hollis"); userDO.setPoliticalStatus("3"); UserVO userVO = (UserVO) mapper.map(userDO, UserVO.class); System.out.println(userVO); } } 特殊的字段转换在使用mapper进行转换前,设置一个或多个mapping文件 List myMappingFiles = new ArrayList(); myMappingFiles.add("dozer-mapping.xml"); mapper.setMappingFiles(myMappingFiles); 配置dozer-mapping.xml文件 数据类型不一致,或者名称不相同或者有级联关系等情况下,可以通过文件dozer-mapping.xml来进行定制Bean的转换 com.hollis.lab.UserDO com.hollis.lab.UserVO 在JavaWeb项目中使用Dozer 在pom.xml中增加依赖 net.sf.dozer dozer 5.5.1 使用Spring集成dozer classpath:mapping/dozer-mapping.xml 使用baseMapper进行Bean的转换 @Autowired private Mapper baseMapper; private UserVO doToVo(UserDO userDO){ if(userDO == null) return null; UserVO vo = baseMapper.map(userDO, UserVO.getClass()); if(userDO.getCompanyId != null) getCompany(vo); return vo; } 通过以上的代码加配置,我们就实现了从DO转换到VO的部分操作,之所以说是部分操作,是因为我们在dozer-mapping.xml并没有做多余的配置,只是使用dozer将DO中和VO中共有的属性转换了过来。对于其他的类型不同或者名称不同等的转换可以参考官网例子通过设置dozer-mapping.xml文件来实现。 上面还有一个getCompany()没有实现。这个方法其实就是通过companyId查询出company实体然后在赋值给UserVO中的company属性。 在使用了dozer之后,我们可以把UserDO中的部分属性赋值到UserVO中,其实,转化的过程是通过调用UserDO中的getter方法和UserVO中的setter方法来实现的。读者可以做个实验,对于UserVO中的部分属性不写Setter方法看看还能不能把属性值转换过来,博主已经测试过了,是不能的。
技术
# 工具类
酷游
1月22日
0
6
0
易航博客