恕我直言,在座的各位根本不会写 Java!

学了这么多年的 Java,你写的代码如何?

导语

小编整理了一些java进阶学习资料和面试题,需要资料的请加JAVA高阶学习Q群:664389243
这是小编创建的java高阶学习交流群,加群一起交流学习深造。群里也有小编整理的2019年最新最全的java高阶学习资料!

自 2013 年毕业后,今年已经是我工作的第 4 个年头了,总在做 Java
相关的工作,终于有时间坐下来,写一篇关于 Java
写法的一篇文章,来探讨一下如果你真的是一个 Java 程序员,那你真的会写
Java 吗?

图片 1

笔者是一个务实的程序员,故本文绝非扯淡文章,文中内容都是干货,望读者看后,能有所收获。

导语

文章核心

自 2013 年毕业后,今年已经是我工作的第 4 个年头了,总在做 Java
相关的工作,终于有时间坐下来,写一篇关于 Java
写法的一篇文章,来探讨一下如果你真的是一个 Java 程序员,那你真的会写
Java 吗?

其实,本不想把标题写的那么恐怖,只是发现很多人干了几年 Java
以后,都自认为是一个不错的 Java
程序员了,可以拿着上万的工资都处宣扬自己了,写这篇文章的目的并不是嘲讽和我一样做
Java
的同行们,只是希望读者看到此篇文章后,可以和我一样,心平气和的争取做一个优秀的程序员。

笔者是一个务实的程序员,故本文绝非扯淡文章,文中内容都是干货,望读者看后,能有所收获。

”我自己是一名从事了十余年的后端的老程序员,辞职后目前在做讲师,近期我花了一个月整理了一份最适合2018年学习的JAVA干货(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)从事后端的小伙伴们都可以来了解一下的,这里是程序员秘密聚集地,各位还在架构师的道路上挣扎的小伙伴们速来。“

文章核心

加QQ群:585550789

其实,本不想把标题写的那么恐怖,只是发现很多人干了几年 Java
以后,都自认为是一个不错的 Java
程序员了,可以拿着上万的工资都处宣扬自己了,写这篇文章的目的并不是嘲讽和我一样做
Java
的同行们,只是希望读者看到此篇文章后,可以和我一样,心平气和的争取做一个优秀的程序员。

讲述方向

讲述方向

由于一直从事移动互联网相关工作,Java
开发中经常和移动端打交道或者做一些后端的工作,所以本篇文章更可能涉及和移动端的交互或者与后端的交互方式,笔者希望以自身的一些学习经验或者开发经验,可以带动认真阅读本篇文章的读者们,让大家对
Java 有一个更好的态度去学习它,它不只是一个赚钱的工具而已。

由于一直从事移动互联网相关工作,Java
开发中经常和移动端打交道或者做一些后端的工作,所以本篇文章更可能涉及和移动端的交互或者与后端的交互方式,笔者希望以自身的一些学习经验或者开发经验,可以带动认真阅读本篇文章的读者们,让大家对
Java 有一个更好的态度去学习它,它不只是一个赚钱的工具而已。

笔者身边有很多与笔者年龄相仿或年龄更大的朋友或同事,经常有人问我:“你现在还在学习吗?我觉得没什么好学的,这些东西都差不多”,我总是回答只要有时间,我就要看一会书,这个时候,大家都会露出一副不屑的眼神或笑容。其实,非常能理解身边朋友或同事的看法,以目前状态来讲,大多都是工作至少
5
年的程序员了,对于公司大大小小的业务需要,以目前的知识储备来讲,都可以轻松应对,“没有什么好学的”其实这句话没有多大的问题,但是,如果你对编程还有一点点兴趣,只是不知道如何努力或改进,希望本篇文章可以帮到你。

笔者身边有很多与笔者年龄相仿或年龄更大的朋友或同事,经常有人问我:“你现在还在学习吗?我觉得没什么好学的,这些东西都差不多”,我总是回答只要有时间,我就要看一会书,这个时候,大家都会露出一副不屑的眼神或笑容。其实,非常能理解身边朋友或同事的看法,以目前状态来讲,大多都是工作至少
5
年的程序员了,对于公司大大小小的业务需要,以目前的知识储备来讲,都可以轻松应对,“没有什么好学的”其实这句话没有多大的问题,但是,如果你对编程还有一点点兴趣,只是不知道如何努力或改进,希望本篇文章可以帮到你。

技术点

技术点

本文不是一个吹嘘的文章,不会讲很多高深的架构,相反,会讲解很多基础的问题和写法问题,如果读者自认为基础问题和写法问题都是不是问题,那请忽略这篇文章,节省出时间去做一些有意义的事情。

本文不是一个吹嘘的文章,不会讲很多高深的架构,相反,会讲解很多基础的问题和写法问题,如果读者自认为基础问题和写法问题都是不是问题,那请忽略这篇文章,节省出时间去做一些有意义的事情。

开发工具

开发工具

不知道有多少”老”程序员还在使用
Eclipse,这些程序员们要不就是因循守旧,要不就是根本就不知道其他好的开发工具的存在,Eclipse
吃内存卡顿的现象以及各种偶然莫名异常的出现,都告知我们是时候寻找新的开发工具了。

不知道有多少”老”程序员还在使用
Eclipse,这些程序员们要不就是因循守旧,要不就是根本就不知道其他好的开发工具的存在,Eclipse
吃内存卡顿的现象以及各种偶然莫名异常的出现,都告知我们是时候寻找新的开发工具了。

更换 IDE

更换 IDE

根本就不想多解释要换什么样的 IDE,如果你想成为一个优秀的 Java
程序员,请更换 IntelliJ IDEA。使用 IDEA 的好处,请搜索谷歌。

根本就不想多解释要换什么样的 IDE,如果你想成为一个优秀的 Java
程序员,请更换 IntelliJ IDEA。使用 IDEA 的好处,请搜索谷歌。

别告诉我快捷键不好用

别告诉我快捷键不好用

更换 IDE
不在我本文的重点内容中,所以不想用太多的篇幅去写为什么更换IDE。在这里,我只能告诉你,更换
IDE 只为了更好、更快的写好 Java 代码。原因略。

更换 IDE
不在我本文的重点内容中,所以不想用太多的篇幅去写为什么更换IDE。在这里,我只能告诉你,更换
IDE 只为了更好、更快的写好 Java 代码。原因略。

别告诉我快捷键不好用,请尝试新事物。

别告诉我快捷键不好用,请尝试新事物。

bean

bean

bean 使我们使用最多的模型之一,我将以大篇幅去讲解
bean,希望读者好好体会。

bean 使我们使用最多的模型之一,我将以大篇幅去讲解
bean,希望读者好好体会。

domain 包名

domain 包名

根据很多 Java 程序员的”经验”来看,一个数据库表则对应着一个 domain
对象,所以很多程序员在写代码时,包名则使用:com.xxx.domain
,这样写好像已经成为了行业的一种约束,数据库映射对象就应该是
domain。但是你错了,domain 是一个领域对象,往往我们再做传统 Java 软件
Web 开发中,这些 domain
都是贫血模型,是没有行为的,或是没有足够的领域模型的行为的,所以,以这个理论来讲,这些
domain 都应该是一个普通的 entity
对象,并非领域对象,所以请把包名改为:com.xxx.entity。

根据很多 Java 程序员的”经验”来看,一个数据库表则对应着一个 domain
对象,所以很多程序员在写代码时,包名则使用:com.xxx.domain
,这样写好像已经成为了行业的一种约束,数据库映射对象就应该是
domain。但是你错了,domain 是一个领域对象,往往我们再做传统 Java 软件
Web 开发中,这些 domain
都是贫血模型,是没有行为的,或是没有足够的领域模型的行为的,所以,以这个理论来讲,这些
domain 都应该是一个普通的 entity
对象,并非领域对象,所以请把包名改为:com.xxx.entity。

如果你还不理解我说的话,请看一下 Vaughn Vernon
出的一本叫做《IMPLEMENTING DOMAIN-DRIVEN
DESIGN》这本书,书中讲解了贫血模型与领域模型的区别,相信你会受益匪浅。

如果你还不理解我说的话,请看一下 Vaughn Vernon
出的一本叫做《IMPLEMENTING DOMAIN-DRIVEN
DESIGN》这本书,书中讲解了贫血模型与领域模型的区别,相信你会受益匪浅。

DTO

DTO

数据传输我们应该使用 DTO
对象作为传输对象,这是我们所约定的,因为很长时间我一直都在做移动端 API
设计的工作,有很多人告诉我,他们认为只有给手机端传输数据的时候(input or
output),这些对象成为 DTO
对象。请注意!这种理解是错误的,只要是用于网络传输的对象,我们都认为他们可以当做是
DTO 对象,比如电商平台中,用户进行下单,下单后的数据,订单会发到 OMS
或者 ERP 系统,这些对接的返回值以及入参也叫 DTO 对象。

数据传输我们应该使用 DTO
对象作为传输对象,这是我们所约定的,因为很长时间我一直都在做移动端 API
设计的工作,有很多人告诉我,他们认为只有给手机端传输数据的时候(input or
output),这些对象成为 DTO
对象。请注意!这种理解是错误的,只要是用于网络传输的对象,我们都认为他们可以当做是
DTO 对象,比如电商平台中,用户进行下单,下单后的数据,订单会发到 OMS
或者 ERP 系统,这些对接的返回值以及入参也叫 DTO 对象。

我们约定某对象如果是 DTO 对象,就将名称改为
XXDTO,比如订单下发OMS:OMSOrderInputDTO。

我们约定某对象如果是 DTO 对象,就将名称改为
XXDTO,比如订单下发OMS:OMSOrderInputDTO。

DTO 转化

DTO 转化

正如我们所知,DTO 为系统与外界交互的模型对象,那么肯定会有一个步骤是将
DTO 对象转化为 BO 对象或者是普通的 entity 对象,让 service 层去处理。

正如我们所知,DTO 为系统与外界交互的模型对象,那么肯定会有一个步骤是将
DTO 对象转化为 BO 对象或者是普通的 entity 对象,让 service 层去处理。

场景

场景

比如添加会员操作,由于用于演示,我只考虑用户的一些简单数据,当后台管理员点击添加用户时,只需要传过来用户的姓名和年龄就可以了,后端接受到数据后,将添加创建时间和更新时间和默认密码三个字段,然后保存数据库。

比如添加会员操作,由于用于演示,我只考虑用户的一些简单数据,当后台管理员点击添加用户时,只需要传过来用户的姓名和年龄就可以了,后端接受到数据后,将添加创建时间和更新时间和默认密码三个字段,然后保存数据库。

@RequestMapping( “/v1/api/user”)

@RequestMapping(“/v1/api/user”)

@RestController

@RestController

publicclassUserApi{

publicclassUserApi{

@Autowired

@Autowired

privateUserService userService;

privateUserService userService;

@PostMapping

@PostMapping

publicUser addUser(UserInputDTO userInputDTO){

publicUseraddUser(UserInputDTO userInputDTO){

User user = newUser();

User user =newUser();

user.setUsername(userInputDTO.getUsername;

user.setUsername(userInputDTO.getUsername;

user.setAge(userInputDTO.getAge;

user.setAge(userInputDTO.getAge;

returnuserService.addUser;

returnuserService.addUser;

}

}

}

}

我们只关注一下上述代码中的转化代码,其他内容请忽略:

我们只关注一下上述代码中的转化代码,其他内容请忽略:

User user = newUser();

User user =newUser();

user.setUsername(userInputDTO.getUsername;

user.setUsername(userInputDTO.getUsername;

user.setAge(userInputDTO.getAge;

user.setAge(userInputDTO.getAge;

请使用工具

请使用工具

上边的代码,从逻辑上讲,是没有问题的,只是这种写法让我很厌烦,例子中只有两个字段,如果有
20 个字段,我们要如何做呢? 一个一个进行 set
数据吗?当然,如果你这么做了,肯定不会有什么问题,但是,这肯定不是一个最优的做法。

上边的代码,从逻辑上讲,是没有问题的,只是这种写法让我很厌烦,例子中只有两个字段,如果有
20 个字段,我们要如何做呢? 一个一个进行 set
数据吗?当然,如果你这么做了,肯定不会有什么问题,但是,这肯定不是一个最优的做法。

网上有很多工具,支持浅拷贝或深拷贝的 Utils。举个例子,我们可以使用
org.springframework.beans.BeanUtils#copyProperties
对代码进行重构和优化:

网上有很多工具,支持浅拷贝或深拷贝的 Utils。举个例子,我们可以使用
org.springframework.beans.BeanUtils#copyProperties
对代码进行重构和优化:

@PostMapping

@PostMapping

publicUser addUser(UserInputDTO userInputDTO){

publicUseraddUser(UserInputDTO userInputDTO){

User user = newUser();

User user =newUser();

BeanUtils.copyProperties(userInputDTO,user);

BeanUtils.copyProperties(userInputDTO,user);

returnuserService.addUser;

returnuserService.addUser;

}

}

BeanUtils.copyProperties 是一个浅拷贝方法,复制属性时,我们只需要把 DTO
对象和要转化的对象两个的属性值设置为一样的名称,并且保证一样的类型就可以了。如果你在做
DTO 转化的时候一直使用 set
进行属性赋值,那么请尝试这种方式简化代码,让代码更加清晰!

BeanUtils.copyProperties 是一个浅拷贝方法,复制属性时,我们只需要把 DTO
对象和要转化的对象两个的属性值设置为一样的名称,并且保证一样的类型就可以了。如果你在做
DTO 转化的时候一直使用 set
进行属性赋值,那么请尝试这种方式简化代码,让代码更加清晰!

转化的语义

转化的语义

上边的转化过程,读者看后肯定觉得优雅很多,但是我们再写 Java
代码时,更多的需要考虑语义的操作,再看上边的代码:

上边的转化过程,读者看后肯定觉得优雅很多,但是我们再写 Java
代码时,更多的需要考虑语义的操作,再看上边的代码:

User user = newUser();

User user =newUser();

BeanUtils.copyProperties(userInputDTO,user);

BeanUtils.copyProperties(userInputDTO,user);

虽然这段代码很好的简化和优化了代码,但是他的语义是有问题的,我们需要提现一个转化过程才好,所以代码改成如下:

虽然这段代码很好的简化和优化了代码,但是他的语义是有问题的,我们需要提现一个转化过程才好,所以代码改成如下:

@PostMapping

@PostMapping

publicUser addUser(UserInputDTO userInputDTO){

publicUseraddUser(UserInputDTO userInputDTO){

User user = convertFor(userInputDTO);

User user = convertFor(userInputDTO);

returnuserService.addUser;

returnuserService.addUser;

}

}

privateUser convertFor(UserInputDTO userInputDTO){

privateUserconvertFor(UserInputDTO userInputDTO){

User user = newUser();

User user =newUser();

BeanUtils.copyProperties(userInputDTO,user);

BeanUtils.copyProperties(userInputDTO,user);

returnuser;

returnuser;

}

}

这是一个更好的语义写法,虽然他麻烦了些,但是可读性大大增加了,在写代码时,我们应该尽量把语义层次差不多的放到一个方法中,比如:

这是一个更好的语义写法,虽然他麻烦了些,但是可读性大大增加了,在写代码时,我们应该尽量把语义层次差不多的放到一个方法中,比如:

Useruser = convertFor(userInputDTO);

Useruser = convertFor(userInputDTO);

returnuserService.addUser;

returnuserService.addUser;

这两段代码都没有暴露实现,都是在讲如何在同一个方法中,做一组相同层次的语义操作,而不是暴露具体的实现。

这两段代码都没有暴露实现,都是在讲如何在同一个方法中,做一组相同层次的语义操作,而不是暴露具体的实现。

如上所述,是一种重构方式,读者可以参考 Martin Fowler 的《Refactoring
Imporving the Design of Existing Code》(重构 改善既有代码的设计)
这本书中的 Extract Method 重构方式。

如上所述,是一种重构方式,读者可以参考 Martin Fowler 的《Refactoring
Imporving the Design of Existing Code》(重构 改善既有代码的设计)
这本书中的 Extract Method 重构方式。

抽象接口定义

抽象接口定义

当实际工作中,完成了几个 API 的 DTO
转化时,我们会发现,这样的操作有很多很多,那么应该定义好一个接口,让所有这样的操作都有规则的进行。

当实际工作中,完成了几个 API 的 DTO
转化时,我们会发现,这样的操作有很多很多,那么应该定义好一个接口,让所有这样的操作都有规则的进行。

如果接口被定义以后,那么 convertFor
这个方法的语义将产生变化,它将是一个实现类。

如果接口被定义以后,那么 convertFor
这个方法的语义将产生变化,它将是一个实现类。

看一下抽象后的接口:

看一下抽象后的接口:

publicinterfaceDTOConvert<S,T> {

publicinterfaceDTOConvert{

T convert;

Tconvert;

}

}

虽然这个接口很简单,但是这里告诉我们一个事情,要去使用泛型,如果你是一个优秀的
Java 程序员,请为你想做的抽象接口,做好泛型吧。

虽然这个接口很简单,但是这里告诉我们一个事情,要去使用泛型,如果你是一个优秀的
Java 程序员,请为你想做的抽象接口,做好泛型吧。

我们再来看接口实现:

我们再来看接口实现:

publicclassUserInputDTOConvertimplementsDTOConvert{

publicclassUserInputDTOConvertimplementsDTOConvert{

@Override

@Override

publicUser convert(UserInputDTO userInputDTO){

publicUserconvert(UserInputDTO userInputDTO){

User user = newUser();

User user =newUser();

BeanUtils.copyProperties(userInputDTO,user);

BeanUtils.copyProperties(userInputDTO,user);

returnuser;

returnuser;

}

}

}

}

我们这样重构后,我们发现现在的代码是如此的简洁,并且那么的规范:

我们这样重构后,我们发现现在的代码是如此的简洁,并且那么的规范:

@RequestMapping( “/v1/api/user”)

@RequestMapping(“/v1/api/user”)

@RestController

@RestController

publicclassUserApi{

publicclassUserApi{

@Autowired

@Autowired

privateUserService userService;

privateUserService userService;

@PostMapping

@PostMapping

publicUser addUser(UserInputDTO userInputDTO){

publicUseraddUser(UserInputDTO userInputDTO){

User user = newUserInputDTOConvert().convert(userInputDTO);

User user =newUserInputDTOConvert().convert(userInputDTO);

returnuserService.addUser;

returnuserService.addUser;

}

}

}

}

review code

review code

如果你是一个优秀的 Java 程序员,我相信你应该和我一样,已经数次重复
review 过自己的代码很多次了。

如果你是一个优秀的 Java 程序员,我相信你应该和我一样,已经数次重复
review 过自己的代码很多次了。

我们再看这个保存用户的例子,你将发现,API
中返回值是有些问题的,问题就在于不应该直接返回 User
实体,因为如果这样的话,就暴露了太多实体相关的信息,这样的返回值是不安全的,所以我们更应该返回一个
DTO 对象,我们可称它为 UserOutputDTO:

我们再看这个保存用户的例子,你将发现,API
中返回值是有些问题的,问题就在于不应该直接返回 User
实体,因为如果这样的话,就暴露了太多实体相关的信息,这样的返回值是不安全的,所以我们更应该返回一个
DTO 对象,我们可称它为 UserOutputDTO:

@PostMapping

@PostMapping

publicUserOutputDTO addUser(UserInputDTO userInputDTO){

publicUserOutputDTOaddUser(UserInputDTO userInputDTO){

User user = newUserInputDTOConvert().convert(userInputDTO);

User user =newUserInputDTOConvert().convert(userInputDTO);

User saveUserResult = userService.addUser;

User saveUserResult = userService.addUser;

UserOutputDTO result =
newUserOutDTOConvert().convertToUser(saveUserResult);

UserOutputDTO result
=newUserOutDTOConvert().convertToUser(saveUserResult);

returnresult;

returnresult;

}

}

这样你的 API 才更健全。

这样你的 API 才更健全。

不知道在看完这段代码之后,读者有是否发现还有其他问题的存在,作为一个优秀的
Java 程序员,请看一下这段我们刚刚抽象完的代码:

不知道在看完这段代码之后,读者有是否发现还有其他问题的存在,作为一个优秀的
Java 程序员,请看一下这段我们刚刚抽象完的代码:

User user = newUserInputDTOConvert().convert(userInputDTO);

User user =newUserInputDTOConvert().convert(userInputDTO);

你会发现,new 这样一个 DTO
转化对象是没有必要的,而且每一个转化对象都是由在遇到 DTO
转化的时候才会出现,那我们应该考虑一下,是否可以将这个类和 DTO
进行聚合呢,看一下我的聚合结果:

你会发现,new 这样一个 DTO
转化对象是没有必要的,而且每一个转化对象都是由在遇到 DTO
转化的时候才会出现,那我们应该考虑一下,是否可以将这个类和 DTO
进行聚合呢,看一下我的聚合结果:

publicclassUserInputDTO{

publicclassUserInputDTO{

privateString username;

privateString username;

privateintage;

privateintage;

publicString getUsername(){

publicStringgetUsername(){

returnusername;

returnusername;

}

}

publicvoidsetUsername(String username){

publicvoidsetUsername(String username){

this.username = username;

this.username = username;

}

}

publicintgetAge(){

publicintgetAge(){

returnage;

returnage;

}

}

publicvoidsetAge{

publicvoidsetAge{

this.age = age;

this.age = age;

}

}

publicUser convertToUser(){

publicUserconvertToUser(){

UserInputDTOConvert userInputDTOConvert = newUserInputDTOConvert();

UserInputDTOConvert userInputDTOConvert =newUserInputDTOConvert();

User convert = userInputDTOConvert.convert;

User convert = userInputDTOConvert.convert;

returnconvert;

returnconvert;

}

}

privatestaticclassUserInputDTOConvertimplementsDTOConvert<UserInputDTO,User>
{

privatestaticclassUserInputDTOConvertimplementsDTOConvert{

@Override

@Override

publicUser convert(UserInputDTO userInputDTO){

publicUserconvert(UserInputDTO userInputDTO){

User user = newUser();

User user =newUser();

BeanUtils.copyProperties(userInputDTO,user);

BeanUtils.copyProperties(userInputDTO,user);

returnuser;

returnuser;

}

}

}

}

}

}

然后 API 中的转化则由:

然后 API 中的转化则由:

Useruser = new UserInputDTOConvert().convert(userInputDTO);

Useruser = new UserInputDTOConvert().convert(userInputDTO);

UsersaveUserResult = userService.addUser;

UsersaveUserResult = userService.addUser;

变成了:

变成了:

Useruser = userInputDTO.convertToUser();

Useruser = userInputDTO.convertToUser();

UsersaveUserResult = userService.addUser;

UsersaveUserResult = userService.addUser;

我们再 DTO
对象中添加了转化的行为,我相信这样的操作可以让代码的可读性变得更强,并且是符合语义的。

我们再 DTO
对象中添加了转化的行为,我相信这样的操作可以让代码的可读性变得更强,并且是符合语义的。

再查工具类

再查工具类

再来看 DTO 内部转化的代码,它实现了我们自己定义的 DTOConvert
接口,但是这样真的就没有问题,不需要再思考了吗?

再来看 DTO 内部转化的代码,它实现了我们自己定义的 DTOConvert
接口,但是这样真的就没有问题,不需要再思考了吗?

我觉得并不是,对于 Convert
这种转化语义来讲,很多工具类中都有这样的定义,这中 Convert
并不是业务级别上的接口定义,它只是用于普通 bean
之间转化属性值的普通意义上的接口定义,所以我们应该更多的去读其他含有
Convert 转化语义的代码。

我觉得并不是,对于 Convert
这种转化语义来讲,很多工具类中都有这样的定义,这中 Convert
并不是业务级别上的接口定义,它只是用于普通 bean
之间转化属性值的普通意义上的接口定义,所以我们应该更多的去读其他含有
Convert 转化语义的代码。

我仔细阅读了一下 GUAVA 的源码,发现了 com.google.common.base.Convert
这样的定义:

我仔细阅读了一下 GUAVA 的源码,发现了 com.google.common.base.Convert
这样的定义:

publicabstractclassConverter<A, B> implementsFunction<A, B>
{

publicabstractclassConverterimplementsFunction{

protectedabstractB doForward;

protectedabstractBdoForward;

protectedabstractA doBackward;

protectedabstractAdoBackward;

//其他略

//其他略

}

}

从源码可以了解到,GUAVA 中的 Convert
可以完成正向转化和逆向转化,继续修改我们 DTO 中转化的这段代码:

从源码可以了解到,GUAVA 中的 Convert
可以完成正向转化和逆向转化,继续修改我们 DTO 中转化的这段代码:

privatestaticclassUserInputDTOConvertimplementsDTOConvert<UserInputDTO,User>
{

privatestaticclassUserInputDTOConvertimplementsDTOConvert{

@Override

@Override

publicUser convert(UserInputDTO userInputDTO){

publicUserconvert(UserInputDTO userInputDTO){

User user = newUser();

User user =newUser();

BeanUtils.copyProperties(userInputDTO,user);

BeanUtils.copyProperties(userInputDTO,user);

returnuser;

returnuser;

}

}

}

}

修改后:

修改后:

privatestaticclassUserInputDTOConvertextendsConverter<UserInputDTO,
User> {

privatestaticclassUserInputDTOConvertextendsConverter{

@Override

@Override

protectedUser doForward(UserInputDTO userInputDTO){

protectedUserdoForward(UserInputDTO userInputDTO){

User user = newUser();

User user =newUser();

BeanUtils.copyProperties(userInputDTO,user);

BeanUtils.copyProperties(userInputDTO,user);

returnuser;

returnuser;

}

}

@Override

@Override

protectedUserInputDTO doBackward(User user){

protectedUserInputDTOdoBackward(User user){

UserInputDTO userInputDTO = newUserInputDTO();

UserInputDTO userInputDTO =newUserInputDTO();

BeanUtils.copyProperties(user,userInputDTO);

BeanUtils.copyProperties(user,userInputDTO);

returnuserInputDTO;

returnuserInputDTO;

}

}

}

}

看了这部分代码以后,你可能会问,那逆向转化会有什么用呢?其实我们有很多小的业务需求中,入参和出参是一样的,那么我们变可以轻松的进行转化,我将上边所提到的
UserInputDTO 和 UserOutputDTO 都转成 UserDTO 展示给大家。

看了这部分代码以后,你可能会问,那逆向转化会有什么用呢?其实我们有很多小的业务需求中,入参和出参是一样的,那么我们变可以轻松的进行转化,我将上边所提到的
UserInputDTO 和 UserOutputDTO 都转成 UserDTO 展示给大家。

DTO:

DTO:

publicclassUserDTO{

publicclassUserDTO{

privateString username;

privateString username;

privateintage;

privateintage;

publicString getUsername(){

publicStringgetUsername(){

returnusername;

returnusername;

}

}

publicvoidsetUsername(String username){

publicvoidsetUsername(String username){

this.username = username;

this.username = username;

}

}

publicintgetAge(){

publicintgetAge(){

returnage;

returnage;

}

}

publicvoidsetAge{

publicvoidsetAge{

this.age = age;

this.age = age;

}

}

publicUser convertToUser(){

publicUserconvertToUser(){

UserDTOConvert userDTOConvert = newUserDTOConvert();

UserDTOConvert userDTOConvert =newUserDTOConvert();

User convert = userDTOConvert.convert;

User convert = userDTOConvert.convert;

returnconvert;

returnconvert;

}

}

publicUserDTO convertFor(User user){

publicUserDTOconvertFor(User user){

UserDTOConvert userDTOConvert = newUserDTOConvert();

UserDTOConvert userDTOConvert =newUserDTOConvert();

UserDTO convert = userDTOConvert.reverse().convert;

UserDTO convert = userDTOConvert.reverse().convert;

returnconvert;

returnconvert;

}

}

privatestaticclassUserDTOConvertextendsConverter<UserDTO, User> {

privatestaticclassUserDTOConvertextendsConverter{

@Override

@Override

protectedUser doForward(UserDTO userDTO){

protectedUserdoForward(UserDTO userDTO){

User user = newUser();

User user =newUser();

BeanUtils.copyProperties(userDTO,user);

BeanUtils.copyProperties(userDTO,user);

returnuser;

returnuser;

}

}

@Override

@Override

protectedUserDTO doBackward(User user){

protectedUserDTOdoBackward(User user){

UserDTO userDTO = newUserDTO();

UserDTO userDTO =newUserDTO();

BeanUtils.copyProperties(user,userDTO);

BeanUtils.copyProperties(user,userDTO);

returnuserDTO;

returnuserDTO;

}

}

}

}

}

}

API:

API:

@PostMapping

@PostMapping

publicUserDTO addUser(UserDTO userDTO){

publicUserDTOaddUser(UserDTO userDTO){

User user = userDTO.convertToUser();

User user = userDTO.convertToUser();

User saveResultUser = userService.addUser;

User saveResultUser = userService.addUser;

UserDTO result = userDTO.convertFor(saveResultUser);

UserDTO result = userDTO.convertFor(saveResultUser);

returnresult;

returnresult;

}

}

当然,上述只是表明了转化方向的正向或逆向,很多业务需求的出参和入参的 DTO
对象是不同的,那么你需要更明显的告诉程序:逆向是无法调用的:

当然,上述只是表明了转化方向的正向或逆向,很多业务需求的出参和入参的 DTO
对象是不同的,那么你需要更明显的告诉程序:逆向是无法调用的:

privatestaticclassUserDTOConvertextendsConverter<UserDTO, User> {

privatestaticclassUserDTOConvertextendsConverter{

@Override

@Override

protectedUser doForward(UserDTO userDTO){

protectedUserdoForward(UserDTO userDTO){

User user = newUser();

User user =newUser();

BeanUtils.copyProperties(userDTO,user);

BeanUtils.copyProperties(userDTO,user);

returnuser;

returnuser;

}

}

@Override

@Override

protectedUserDTO doBackward(User user){

protectedUserDTOdoBackward(User user){

thrownewAsserti( “不支持逆向转化方法!”);

thrownewAssertionError(“不支持逆向转化方法!”);

}

}

}

}

看一下 doBackward
方法,直接抛出了一个断言异常,而不是业务异常,这段代码告诉代码的调用者,这个方法不是准你调用的,如果你调用,我就”断言”你调用错误了。

看一下 doBackward
方法,直接抛出了一个断言异常,而不是业务异常,这段代码告诉代码的调用者,这个方法不是准你调用的,如果你调用,我就”断言”你调用错误了。

bean 的验证

关于异常处理的更详细介绍,可以参考我之前的文章:如何优雅的设计 Java
异常(
,应该可以帮你更好的理解异常。

如果你认为我上边写的那个添加用户 API
写的已经非常完美了,那只能说明你还不是一个优秀的程序员。我们应该保证任何数据的入参到方法体内都是合法的。

bean 的验证

为什么要验证

如果你认为我上边写的那个添加用户 API
写的已经非常完美了,那只能说明你还不是一个优秀的程序员。我们应该保证任何数据的入参到方法体内都是合法的。

很多人会告诉我,如果这些 API
是提供给前端进行调用的,前端都会进行验证啊,你为什还要验证?

为什么要验证

其实答案是这样的,我从不相信任何调用我 API
或者方法的人,比如前端验证失败了,或者某些人通过一些特殊的渠道(比如
Charles 进行抓包),直接将数据传入到我的
API,那我仍然进行正常的业务逻辑处理,那么就有可能产生脏数据!

很多人会告诉我,如果这些 API
是提供给前端进行调用的,前端都会进行验证啊,你为什还要验证?

“对于脏数据的产生一定是致命”,这句话希望大家牢记在心,再小的脏数据也有可能让你找几个通宵!

其实答案是这样的,我从不相信任何调用我 API
或者方法的人,比如前端验证失败了,或者某些人通过一些特殊的渠道(比如
Charles 进行抓包),直接将数据传入到我的
API,那我仍然进行正常的业务逻辑处理,那么就有可能产生脏数据!

jsr 303验证

“对于脏数据的产生一定是致命”,这句话希望大家牢记在心,再小的脏数据也有可能让你找几个通宵!

hibernate 提供的 jsr 303
实现,我觉得目前仍然是很优秀的,具体如何使用,我不想讲,因为谷歌上你可以搜索出很多答案!

jsr 303验证

再以上班的 API 实例进行说明,我们现在对 DTO 数据进行检查:

hibernate 提供的 jsr 303
实现,我觉得目前仍然是很优秀的,具体如何使用,我不想讲,因为谷歌上你可以搜索出很多答案!

publicclassUserDTO{

再以上班的 API 实例进行说明,我们现在对 DTO 数据进行检查:

@NotNull

publicclassUserDTO{

privateString username;

@NotNull

@NotNull

privateString username;

privateintage;

@NotNull

//其他代码略

privateintage;

}

//其他代码略

API 验证:

}

@PostMapping

API 验证:

publicUserDTO addUser( @ValidUserDTO userDTO){

@PostMapping

User user = userDTO.convertToUser();

publicUserDTO addUser(@ValidUserDTO userDTO){

User saveResultUser = userService.addUser;

User user = userDTO.convertToUser();

UserDTO result = userDTO.convertFor(saveResultUser);

User saveResultUser = userService.addUser;

returnresult;

UserDTO result = userDTO.convertFor(saveResultUser);

}

returnresult;

我们需要将验证结果传给前端,这种异常应该转化为一个 api 异常。

}

@PostMapping

我们需要将验证结果传给前端,这种异常应该转化为一个 api 异常。

publicUserDTO addUser(@Valid UserDTO userDTO, BindingResult
bindingResult){

@PostMapping

checkDTOParams(bindingResult);

publicUserDTOaddUser(@Valid UserDTO userDTO, BindingResult
bindingResult){

User user = userDTO.convertToUser();

checkDTOParams(bindingResult);

User saveResultUser = userService.addUser;

User user = userDTO.convertToUser();

UserDTO result = userDTO.convertFor(saveResultUser);

User saveResultUser = userService.addUser;

returnresult;

UserDTO result = userDTO.convertFor(saveResultUser);

}

returnresult;

privatevoidcheckDTOParams(BindingResult bindingResult){

}

if(bindingResult.hasErrors{

privatevoidcheckDTOParams(BindingResult bindingResult){

//throw new 带验证码的验证错误异常

if(bindingResult.hasErrors{

}

//throw new 带验证码的验证错误异常

}

}

BindingResult 是 Spring MVC 验证 DTO 后的一个结果集,可以参考spring
官方文档(

}

检查参数后,可以抛出一个“带验证码的验证错误异常”,具体异常设计可以参考如何优雅的设计
Java 异常

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website