JSR 303 验证
Hibernate 提供的 JSR 303 实现,我觉得目前仍然是很优秀的,具体如何使用,我不想讲,因为谷歌上你可以搜索出很多答案!
再以上班的 API 实例进行说明,我们现在对 DTO 数据进行检查:
public class UserDTO {
@NotNull
private String username;
@NotNull
private int age;
//其他代码略
}
API 验证:
@PostMapping
public UserDTO addUser(@Valid UserDTO userDTO){
User user = userDTO.convertToUser();
User saveResultUser = userService.addUser(user);
UserDTO result = userDTO.convertFor(saveResultUser);
return result;
}
我们需要将验证结果传给前端,这种异常应该转化为一个 API 异常(带有错误码的异常)。
@PostMapping
public UserDTO addUser(@Valid UserDTO userDTO, BindingResult bindingResult){
checkDTOParams(bindingResult);
User user = userDTO.convertToUser();
User saveResultUser = userService.addUser(user);
UserDTO result = userDTO.convertFor(saveResultUser);
return result;
}
private void checkDTOParams(BindingResult bindingResult){
if(bindingResult.hasErrors()){
//throw new 带验证码的验证错误异常
}
}
BindingResult 是 Spring MVC 验证 DTO 后的一个结果集,可以参考 Spring 官方文档(spring.io/)。
检查参数后,可以抛出一个 “带验证码的验证错误异常”。
上边的 DTO 代码,已经让我看的很累了,我相信读者也是一样,看到那么多的 Getter 和 Setter 方法,太烦躁了,那时候有什么方法可以简化这些呢。
请拥抱 Lombok,它会帮助我们解决一些让我们很烦躁的问题。
去掉 Setter 和 Getter
其实这个标题,我不太想说,因为网上太多,但是因为很多人告诉我,他们根本就不知道 Lombok 的存在,所以为了让读者更好的学习,我愿意写这样一个例子:
@Setter
@Getter
public class UserDTO {
@NotNull
private String username;
@NotNull
private int age;
public User convertToUser(){
UserDTOConvert userDTOConvert = new UserDTOConvert();
User convert = userDTOConvert.convert(this);
return convert;
}
public UserDTO convertFor(User user){
UserDTOConvert userDTOConvert = new UserDTOConvert();
UserDTO convert = userDTOConvert.reverse().convert(user);
return convert;
}
private static class UserDTOConvert extends Converter<UserDTO, User> {
@Override
protected User doForward(UserDTO userDTO) {
User user = new User();
BeanUtils.copyProperties(userDTO,user);
return user;
}
@Override
protected UserDTO doBackward(User user) {
throw new AssertionError("不支持逆向转化方法!");
}
}
}
看到了吧,烦人的 Getter 和 Setter 方法已经去掉了。
但是上边的例子根本不足以体现 lombok 的强大。我希望写一些网上很难查到,或者很少人进行说明的 lombok 的使用以及在使用时程序语义上的说明。
比如:@Data,@AllArgsConstructor,@NoArgsConstructor… 这些我就不进行一一说明了,请大家自行查询资料。
Bean 中的链式风格
什么是链式风格?我来举个例子,看下面这个 Student 的 Bean:
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public Student setName(String name) {
this.name = name;
return this;
}
public int getAge() {
return age;
}
public Student setAge(int age) {
return this;
}
}
仔细看一下 set 方法,这样的设置便是 chain 的 style,调用的时候,可以这样使用:
Student student = new Student()
.setAge(24)
.setName("zs");
相信合理使用这样的链式代码,会更多的程序带来很好的可读性,那看一下如果使用 Lombok 进行改善呢,请使用 @Accessors(chain = true),看如下代码:
@Accessors(chain = true)
@Setter
@Getter
public class Student {
private String name;
private int age;
}
这样就完成了一个对于 Bean 来讲很友好的链式操作。
静态构造方法
静态构造方法的语义和简化程度真的高于直接去 new 一个对象。比如 new 一个 List 对象,过去的使用是这样的:
List<String> list = new ArrayList<>();
看一下 Guava 中的创建方式:
List<String> list = Lists.newArrayList();
Lists 命名是一种约定 (俗话说:约定优于配置),它是指 Lists 是 List 这个类的一个工具类,那么使用 List 的工具类去产生 List,这样的语义是不是要比直接 new 一个子类来的更直接一些呢,答案是肯定的,再比如如果有一个工具类叫做 Maps,那你是否想到了创建 Map 的方法呢:
HashMap<String, String> objectObjectHashMap = Maps.newHashMap();
好了,如果你理解了我说的语义,那么,你已经向成为 Java 程序员更近了一步了。
再回过头来看刚刚的 Student,很多时候,我们去写 Student 这个 bean 的时候,他会有一些必输字段,比如 Student 中的 name 字段,一般处理的方式是将 name 字段包装成一个构造方法,只有传入 name 这样的构造方法,才能创建一个 Student 对象。
接上上边的静态构造方法和必传参数的构造方法,使用 Lombok 将更改成如下写法(@RequiredArgsConstructor 和 @NonNull):
@Accessors(chain = true)
@Setter
@Getter
@RequiredArgsConstructor(staticName = "ofName")
public class Student {
@NonNull private String name;
private int age;
}
测试代码:
Student student = Student.ofName("zs");
这样构建出的 bean 语义是否要比直接 new 一个含参的构造方法 (包含 name 的构造方法) 要好很多。
当然,看过很多源码以后,我想相信将静态构造方法 ofName 换成 of 会先的更加简洁:
@Accessors(chain = true)
@Setter
@Getter
@RequiredArgsConstructor(staticName = "of")
public class Student {
@NonNull private String name;
private int age;
}
测试代码:
Student student = Student.of("zs");
当然它仍然是支持链式调用的:
Student student = Student.of("zs").setAge(24);
这样来写代码,真的很简洁,并且可读性很强。
使用 Builder
Builder 模式我不想再多解释了,读者可以看一下 Head First(《设计模式》)的建造者模式。
今天其实要说的是一种变种的 Builder 模式,那就是构建 Bean 的 Builder 模式,其实主要的思想是带着大家一起看一下 Lombok 给我们带来了什么。
看一下 Student 这个类的原始 Builder 状态:
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static Builder builder(){
return new Builder();
}
public static class Builder{
private String name;
private int age;
public Builder name(String name){
this.name = name;
return this;
}
public Builder age(int age){
this.age = age;
return this;
}
public Student build(){
Student student = new Student();
student.setAge(age);
student.setName(name);
return student;
}
}
}
调用方式:
Student student = Student.builder().name("zs").age(24).build();
这样的 Builder 代码,让我是在恶心难受,于是我打算用 Lombok 重构这段代码:
@Builder
public class Student {
private String name;
private int age;
}
调用方式:
Student student = Student.builder().name("zs").age(24).build();
代理模式
正如我们所知的,在程序中调用 Rest 接口是一个常见的行为动作,如果你和我一样使用过 Spring 的 RestTemplate,我相信你会我和一样,对它抛出的非 HTTP 状态码异常深恶痛绝。
所以我们考虑将 RestTemplate 最为底层包装器进行包装器模式的设计:
public abstract class FilterRestTemplate implements RestOperations {
protected volatile RestTemplate restTemplate;
protected FilterRestTemplate(RestTemplate restTemplate){
this.restTemplate = restTemplate;
}
//实现RestOperations所有的接口
}
然后再由扩展类对 FilterRestTemplate 进行包装扩展:
public class ExtractRestTemplate extends FilterRestTemplate {
private RestTemplate restTemplate;
public ExtractRestTemplate(RestTemplate restTemplate) {
super(restTemplate);
this.restTemplate = restTemplate;
}
public <T> RestResponseDTO<T> postForEntityWithNoException(String url, Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException {
RestResponseDTO<T> restResponseDTO = new RestResponseDTO<T>();
ResponseEntity<T> tResponseEntity;
try {
tResponseEntity = restTemplate.postForEntity(url, request, responseType, uriVariables);
restResponseDTO.setData(tResponseEntity.getBody());
restResponseDTO.setMessage(tResponseEntity.getStatusCode().name());
restResponseDTO.setStatusCode(tResponseEntity.getStatusCodeValue());
}catch (Exception e){
restResponseDTO.setStatusCode(RestResponseDTO.UNKNOWN_ERROR);
restResponseDTO.setMessage(e.getMessage());
restResponseDTO.setData(null);
}
return restResponseDTO;
}
}
包装器 ExtractRestTemplate 很完美的更改了异常抛出的行为,让程序更具有容错性。在这里我们不考虑 ExtractRestTemplate 完成的功能,让我们把焦点放在 FilterRestTemplate 上,“实现 RestOperations 所有的接口”,这个操作绝对不是一时半会可以写完的,当时在重构之前我几乎写了半个小时,如下:
public abstract class FilterRestTemplate implements RestOperations {
protected volatile RestTemplate restTemplate;
protected FilterRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
return restTemplate.getForObject(url,responseType,uriVariables);
}
@Override
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
return restTemplate.getForObject(url,responseType,uriVariables);
}
@Override
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
return restTemplate.getForObject(url,responseType);
}
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
return restTemplate.getForEntity(url,responseType,uriVariables);
}
//其他实现代码略。。。
}
我相信你看了以上代码,你会和我一样觉得恶心反胃,后来我用 Lombok 提供的代理注解优化了我的代码(@Delegate):
@AllArgsConstructor
public abstract class FilterRestTemplate implements RestOperations {
@Delegate
protected volatile RestTemplate restTemplate;
}
这几行代码完全替代上述那些冗长的代码。
搜索公纵号:MarkerHub,关注回复[ vue ]获取前后端入门教程!
是不是很简洁,做一个拥抱 Lombok 的程序员吧。
重构:需求案例
项目需求
项目开发阶段,有一个关于下单发货的需求:
如果今天下午 3 点前进行下单,那么发货时间是明天,如果今天下午 3 点后进行下单,那么发货时间是后天,如果被确定的时间是周日,那么在此时间上再加 1 天为发货时间。
思考与重构
我相信这个需求看似很简单,无论怎么写都可以完成。
很多人可能看到这个需求,就动手开始写 Calendar 或 Date 进行计算,从而完成需求。
而我给的建议是,仔细考虑如何写代码,然后再去写,不是说所有的时间操作都用 Calendar 或 Date 去解决,一定要看场景。
对于时间的计算我们要考虑 joda-time 这种类似的成熟时间计算框架来写代码,它会让代码更加简洁和易读。
请读者先考虑这个需求如何用 Java 代码完成,或先写一个你觉得完成这个代码的思路,再来看我下边的代码,这样,你的收获会更多一些:
final DateTime DISTRIBUTION_TIME_SPLIT_TIME = new DateTime().withTime(15,0,0,0);
private Date calculateDistributionTimeByOrderCreateTime(Date orderCreateTime){
DateTime orderCreateDateTime = new DateTime(orderCreateTime);
Date tomorrow = orderCreateDateTime.plusDays(1).toDate();
Date theDayAfterTomorrow = orderCreateDateTime.plusDays(2).toDate();
return orderCreateDateTime.isAfter(DISTRIBUTION_TIME_SPLIT_TIME) ? wrapDistributionTime(theDayAfterTomorrow) : wrapDistributionTime(tomorrow);
}
private Date wrapDistributionTime(Date distributionTime){
DateTime currentDistributionDateTime = new DateTime(distributionTime);
DateTime plusOneDay = currentDistributionDateTime.plusDays(1);
boolean isSunday = (DateTimeConstants.SUNDAY == currentDistributionDateTime.getDayOfWeek());
return isSunday ? plusOneDay.toDate() : currentDistributionDateTime.toDate() ;
}
读这段代码的时候,你会发现,我将判断和有可能出现的不同结果都当做一个变量,最终做一个三目运算符的方式进行返回,这样的优雅和可读性显而易见,当然这样的代码不是一蹴而就的,我优化了 3 遍产生的以上代码。读者可根据自己的代码和我写的代码进行对比。
提高方法
如果你做了 3 年 + 的程序员,我相信像如上这样的需求,你很轻松就能完成,但是如果你想做一个会写 Java 的程序员,就好好的思考和重构代码吧。
写代码就如同写字一样,同样的字,大家都会写,但是写出来是否好看就不一定了。如果想把程序写好,就要不断地思考和重构,敢于尝试,敢于创新,不要因循守旧,一定要做一个优秀的 Java 程序员。
提高代码水平最好的方法就是有条理的重构!(注意:是有条理的重构)
设计模式
设计模式就是工具,而不是提现你是否是高水平程序员的一个指标。
我经常会看到某一个程序员兴奋的大喊,哪个程序哪个点我用到了设计模式,写的多么多么优秀,多么多么好。我仔细去翻阅的时候,却发现有很多是过度设计的。
业务驱动技术 or 技术驱动业务
业务驱动技术 or 技术驱动业务 ?其实这是一个一直在争论的话题,但是很多人不这么认为,我觉得就是大家不愿意承认罢了。我来和大家大概分析一下作为一个 Java 程序员,我们应该如何判断自己所处于的位置。
-
业务驱动技术: 如果你所在的项目是一个收益很小或者甚至没有收益的项目,请不要搞其他创新的东西,不要驱动业务要如何如何做,而是要熟知业务现在的痛点是什么?如何才能帮助业务盈利或者让项目更好,更顺利的进行。
-
技术驱动业务: 如果你所在的项目是一个很牛的项目,比如淘宝这类的项目,我可以在满足业务需求的情况下,和业务沟通,使用什么样的技术能更好的帮助业务创造收益,比如说下单的时候要进队列,可能几分钟之后订单状态才能处理完成,但是会让用户有更流畅的体验,赚取更多的访问流量,那么我相信业务愿意被技术驱动,会同意订单的延迟问题,这样便是技术驱动业务。
代码设计
一直在做 Java 后端的项目,经常会有一些变动,我相信大家也都遇到过。
比如当我们写一段代码的时候,我们考虑将需求映射成代码的状态模式,突然有一天,状态模式里边又添加了很多行为变化的东西,这时候你就挠头了,你硬生生的将状态模式中添加过多行为和变化。
慢慢地你会发现这些状态模式,其实更像是一簇算法,应该使用策略模式,这时你应该已经晕头转向了。
Java 编程中没有银弹,请拥抱业务变化,一直思考重构,你就有一个更好的代码设计!
你真的优秀吗?
国外流行一种编程方式,叫做结对编程,我相信国内很多公司都没有这么做,我就不在讲述结对编程带来的好处了,其实就是一边 Code Review,一边互相提高的一个过程。既然做不到这个,那如何让自己活在自己的世界中不断提高呢?
“平时开发的时候,做出的代码总认为是正确的,而且写法是完美的。” 我相信这是大部分人的心声,还回到刚刚的问题,如何在自己的世界中不断提高呢?
答案就是:
- 多看成熟框架的源码
- 多回头看自己的代码
- 勤于重构
你真的优秀吗?如果你每周都完成了学习源码,回头看自己代码,然后勤于重构,我认为你就真的很优秀了。
即使也许你只是刚刚入门,但是一直坚持,你就是一个真的会写 java 代码的程序员了。
技能
UML
不想多讨论 UML 相关的知识,但是我觉得你如果真的会写 Java,请先学会表达自己,UML 就是你说话的语言,做一名优秀的 Java 程序员,请至少学会这两种 UML 图:
- 类图
- 时序图
Clean Code
我认为保持代码的简洁和可读性是代码的最基本保证,如果有一天为了程序的效率而降低了这两点,我认为是可以谅解的,除此之外,没有任何理由可以让你任意挥霍你的代码。
- 读者可以看一下 Robert C. Martin 出版的 Clean Code(《代码整洁之道》) 这本书
- 可以参考美团文章:《聊聊 Clean Code》
- 也可以看一下阿里的 Java 编码规范
无论如何,请保持你的代码的整洁。
Linux 基础命令
这点其实和会写 Java 没有关系,但是 Linux 很多时候确实承载运行 Java 的容器,请学好 Linux 的基础命令。
总结
Java 是一个大体系,今天讨论并未涉及框架和架构相关知识,只是讨论如何写好代码。
本文从写 Java 程序的小方面一直写到大方面,来阐述了如何才能写 m/mazi 好 Java 程序,并告诉读者们如何才能提高自身的编码水平。我希望看到这篇文章的各位都能做一个优秀的 Java 程序员。
来自:Lrwin 的技术博客
lrwinx.github.io/2017/03/04

欢迎关注我的微信公众号「码农突围」,分享Python、Java、大数据、机器学习、人工智能等技术,关注码农技术提升•职场突围•思维跃迁,20万+码农成长充电第一站,陪有梦想的你一起成长