回答

收藏

好用到爆的 Java 技巧(好用到爆的图片)

知识点 知识点 43 人阅读 | 0 人回复 | 2023-01-19

帖子摘要:导语 自2013年毕业后今年已经是我工作的第4个年头了总在做Java相关的工作终于有时间坐下来写一篇关于Java写法的一篇文章来探讨一下如果你真的是一个Java程序员那你真的会写Java吗? 笔者是一......
- K' u' w) Z9 I/ H9 G0 W, X' _* _0 L) W
大家好,欢迎来到Java吧(www.java8.com),交流、学习Java技术、获取Java资源无任何套路,今天说一说:“好用到爆的 Java 技巧”
5 X2 s5 N5 b* q3 W  z, i! L
+ }! U9 T1 z% r1 m+ {7 u
+ o8 T& J. P, q8 r+ N( J. v! }
        
! [& ~: T1 ^0 w$ a               
7 Y  C) x; _& q2 ]% E                    导语4 x2 B! x5 X& t# T8 ?+ [. R

8 _( X0 f$ d4 S( G. R% ?7 ^自2013年毕业后今年已经是我工作的第4个年头了总在做Java相关的工作终于有时间坐下来写一篇关于Java写法的一篇文章来探讨一下如果你真的是一个Java程序员那你真的会写Java吗?
, X0 f/ T2 }9 v: n' `% S , x6 h( J/ p8 L9 r/ y4 d. N1 r- }
笔者是一个务实的程序员故本文绝非扯淡文章文中内容都是干货望读者看后能有所收获。
0 {% ~6 M* M9 t
2 m( f. u3 {2 U4 e1 L文章核心
0 [+ v! i% r, V2 E& n5 _; }: R- x
  q* T. y1 S' U* H其实本不想把标题写的那么恐怖只是发现很多人干了几年java以后都自认为是一个不错的java程序员了可以拿着上万的工资都处宣扬自己了写这篇文章的目的并不是嘲讽和我一样做java的同行们只是希望读者看到此篇文章后可以和我一样心平气和的争取做一个优秀的程序员。
4 a2 x3 }  f' P* m8 H( y / @8 n, f) N$ L0 {0 w
讲述方向5 }/ B8 X* x( [

' E3 X8 Q! n3 X0 S% d9 C5 o( \由于一直从事移动互联网相关工作java开发中经常和移动端打交道或者做一些后端的工作所以本篇文章更可能涉及和移动端的交互或者与后端的交互方式笔者希望以自身的一些学习经验或者开发经验可以带动认真阅读本篇文章的读者们让大家对java有一个更好的态度去学习它它不只是一个赚钱的工具而已。/ g/ g8 A4 b' F7 F1 T' s9 a
  B! x2 r' N' _' |, {+ d
笔者身边有很多与笔者年龄相仿或年龄更大的朋友或同事经常有人问我“你现在还在学习吗我觉得没什么好学的这些东西都差不多”我总是回答只要有时间我就要看一会书这个时候大家都会露出一副不屑的眼神或笑容。其实非常能理解身边朋友或同事的看法以目前状态来讲大多都是工作至少5年的程序员了对于公司大大小小的业务需要以目前的知识储备来讲都可以轻松应对“没有什么好学的”其实这句话没有多大的问题但是如果你对编程还有一点点兴趣只是不知道如何努力或改进希望本篇文章可以帮到你。
! ]4 e+ O; a0 ?7 J! D( @
! s' t; f3 ]) H! p* x# W技术点7 j1 c+ v) |% O1 E$ P$ A! t* y

% I$ u. D8 i$ S; D# G% ^本文不是一个吹嘘的文章不会讲很多高深的架构相反会讲解很多基础的问题和写法问题如果读者自认为基础问题和写法问题都不是问题那请忽略这篇文章节省出时间去做一些有意义的事情。
' H1 h9 c3 A3 N5 @3 ? " p0 M9 P# ^, z
开发工具
3 b9 P! }% c2 b' m
4 x" V0 m7 t8 D1 R. A3 ^+ i0 O) `不知道有多少”老”程序员还在使用eclipse这些程序员们要不就是因循守旧要不就是根本就不知道其他好的开发工具的存在eclipse吃内存卡顿的现象以及各种偶然莫名异常的出现都告知我们是时候寻找新的开发工具了。
, g6 \% \  j/ e1 t3 [8 b9 Z
, Z% ~" j5 Q7 D3 m$ T更换IDE
5 ^3 G9 S- m9 ]0 s1 X5 F( R0 ^ $ G5 ^7 ~1 t! t- _
根本就不想多解释要换什么样的IDE如果你想成为一个优秀的java程序员请更换intellij idea. 使用idea的好处请搜索谷歌。
7 G  G. n: O9 I& l' A2 w6 Z
/ s2 P9 m' _( l# ?9 }/ Q/ M3 v别告诉我快捷键不好用: g8 r4 k3 Z1 S1 v3 i

% K; E6 W- U$ H8 r4 @更换IDE不在我本文的重点内容中所以不下想用太多的篇幅去写为什么更换IDE请谷歌。
' `2 f* M$ C% w8 E- f+ U& f 5 V6 [- e" y4 }) C% ]3 p
在这里我只能告诉你更换IDE只为了更好、更快的写好java代码。原因略。
. d) O$ w3 y# V
$ r1 `7 R1 x6 J. e; J4 g别告诉我快捷键不好用请尝试新事物。
( E/ A, W2 d. [: n$ a5 c4 Y ( ^, T4 ]) O3 c" ]2 t; r
bean6 ?, a9 ]9 v( X" X, H( S
7 O! j2 v9 Q6 b6 j8 e; y! X$ A/ [" L
bean使我们使用最多的模型之一我将以大篇幅去讲解bean希望读者好好体会。
1 F* Z1 i) `7 @# ]
6 d- }+ f5 o0 l0 P/ Y3 L' b6 Idomain包名
8 B8 t0 c- f7 P 5 a& l5 y, Y7 H4 y5 s' V
根据很多java程序员的”经验”来看一个数据库表则对应着一个domain对象所以很多程序员在写代码时包名则使用com.xxx.domain 这样写好像已经成为了行业的一种约束数据库映射对象就应该是domain。但是你错了domain是一个领域对象往往我们再做传统java软件web开发中这些domain都是贫血模型是没有行为的或是没有足够的领域模型的行为的所以以这个理论来讲这些domain都应该是一个普通的entity对象并非领域对象所以请把包名改为:com.xxx.entity。
- p& U  {' w6 P  f0 v - J' V6 ^) F6 ~3 z! `, ~, s' e
如果你还不理解我说的话请看一下Vaughn Vernon出的一本叫做《IMPLEMENTING DOMAIN-DRIVEN DESIGN》(实现领域驱动设计)这本书书中讲解了贫血模型与领域模型的区别相信你会受益匪浅。* E$ z3 z) Y# u. `1 }% F! F3 m

' t% K/ s6 Q/ ~7 R6 LDTO' k5 i3 d& `$ C- c$ a
8 l  P: M# V: k5 [7 O4 p
数据传输我们应该使用DTO对象作为传输对象这是我们所约定的因为很长时间我一直都在做移动端api设计的工作有很多人告诉我他们认为只有给手机端传输数据的时候(input or output)这些对象成为DTO对象。请注意这种理解是错误的只要是用于网络传输的对象我们都认为他们可以当做是DTO对象比如电商平台中用户进行下单下单后的数据订单会发到OMS 或者 ERP系统这些对接的返回值以及入参也叫DTO对象。
$ l( D/ w4 p3 w % J  G' v: f! h" r
我们约定某对象如果是DTO对象就将名称改为XXDTO,比如订单下发OMSOMSOrderInputDTO。5 p1 J- i4 B' z' e7 a
: B# s1 ^/ r+ I& N6 h& [1 `
DTO转化
  B1 ~' G- m6 \# i4 p$ i , w0 N/ Q2 \- d5 V+ f
正如我们所知DTO为系统与外界交互的模型对象那么肯定会有一个步骤是将DTO对象转化为BO对象或者是普通的entity对象让service层去处理。6 V# V# @4 g4 z( X8 C7 X  U9 E2 a
' o8 V# F- |4 `( _+ q
场景
) o. N# _( ]7 h& R" F
/ c! d* C* F; o# @) u  F4 L# r! r比如添加会员操作由于用于演示我只考虑用户的一些简单数据当后台管理员点击添加用户时只需要传过来用户的姓名和年龄就可以了后端接受到数据后将添加创建时间和更新时间和默认密码三个字段然后保存数据库。6 k. h. c3 v9 L3 p

7 F7 T+ [6 _# C- u: e6 X 5 ]1 H, h* T# w5 k
@RequestMapping("/v1/api/user")
; o: B& ?" H  e5 g+ S @RestController
! t. _! Q- X6 |' ]- H public class UserApi {
' J) v- N2 D2 y- L     @Autowired& H- r7 [* S7 {5 z
     private UserService userService;
1 Q- w4 _7 ^* r6 A+ [; v     @PostMapping
2 p& h) y. A2 ?( X* O- ?     public User addUser(UserInputDTO userInputDTO){5 [- b8 @; S' S$ B% M
         User user = new User();# O1 A; K3 Z+ t$ e. t$ C( A1 b
         user.setUsername(userInputDTO.getUsername());
$ ^1 u; |2 B6 G! j8 d         user.setAge(userInputDTO.getAge());
' d/ b% k+ B( i7 c         return userService.addUser(user);1 W8 U9 f5 e* d- }8 D  u
     }: P, L. E) m  B- A" d
}
) x3 l4 Z+ l4 A & {1 g4 U7 e* d& G. S4 N/ V' p
我们只关注一下上述代码中的转化代码其他内容请忽略:$ h& W* P! _8 t$ I9 r

6 _; u) S, E$ z' S4 j ; R" C, Z4 e- G; ~
User user = new User();% r5 O! _& T4 X& U6 g
user.setUsername(userInputDTO.getUsername());9 v$ f+ M; x. @( T) ]. g
user.setAge(userInputDTO.getAge());  t) B. p1 _7 U9 T& N- [

% F5 k2 N" ?; L- i/ W! e请使用工具0 `1 V+ m/ h" p3 I
4 w# X! T( \8 s) q
上边的代码从逻辑上讲是没有问题的只是这种写法让我很厌烦例子中只有两个字段如果有20个字段我们要如何做呢一个一个进行set数据吗当然如果你这么做了肯定不会有什么问题但是这肯定不是一个最优的做法。
% I  {& J# N( J7 p3 F5 C7 O , q& F8 e4 ]  w) H
网上有很多工具支持浅拷贝或深拷贝的Utils. 举个例子我们可以使用org.springframework.beans.BeanUtils#copyProperties对代码进行重构和优化:; z4 a/ f- M* Z+ S9 ]! p- x

/ b! P# f2 I: {
% u$ y8 U4 {) w7 Q5 F# F@PostMapping
2 o! {1 k2 Q* p" X5 q5 ~ public User addUser(UserInputDTO userInputDTO){
4 e* U2 M$ y' _8 W, N9 u     User user = new User();
" N% \' a- B4 ^& J% J8 j; |     BeanUtils.copyProperties(userInputDTO,user);
2 ?, \2 U# M  a( y0 I     return userService.addUser(user);
8 g: g8 ]' F: u; r. y% M( V }
7 B4 k1 x8 i* y& H. t : @9 g% f4 c# I/ e0 w
BeanUtils.copyProperties是一个浅拷贝方法复制属性时我们只需要把DTO对象和要转化的对象两个的属性值设置为一样的名称并且保证一样的类型就可以了。如果你在做DTO转化的时候一直使用set进行属性赋值那么请尝试这种方式简化代码让代码更加清晰!0 ?$ i- d8 Y8 F- W) B, [

8 e/ h+ z8 Y7 `: W# ]) D转化的语义
( u, ^% H; r7 W8 Y4 T 3 G; O3 O! R; A# p9 C
上边的转化过程读者看后肯定觉得优雅很多但是我们再写java代码时更多的需要考虑语义的操作再看上边的代码:* K1 `. m. w" N5 |

' U) l: O! O* J, w: c, s
, ?5 R5 o* I  MUser user = new User();
! J' Q: E" ]* z) ?4 p8 }( C* ~ BeanUtils.copyProperties(userInputDTO,user);
/ c1 ~4 n- A& y& l+ x3 d, V
8 B8 S5 O7 v. a, A! z虽然这段代码很好的简化和优化了代码但是他的语义是有问题的我们需要提现一个转化过程才好,所以代码改成如下:8 L8 J, d! f& t  @( M6 M

+ d2 L+ K7 E" p) I, Z8 ~1 e - O5 y4 S+ _4 ?. j- X" e6 w
@PostMapping  X" z7 p  M! Y- a
  public User addUser(UserInputDTO userInputDTO){
4 \. L; O! Y$ c$ t# ^& L, T' g/ {          User user = convertFor(userInputDTO);/ Q' t1 u7 t! b0 b
          return userService.addUser(user);
  L* A# q& `- K# p: h" y( I  }
3 o; [2 }+ F, p/ b: J  private User convertFor(UserInputDTO userInputDTO){
7 F1 F5 E/ }: ], ~* B1 y          User user = new User();
) W8 s  u/ y8 p+ N          BeanUtils.copyProperties(userInputDTO,user);0 W2 q  a: a& d- m. T$ f8 D
          return user;
3 c. {. O' f$ k: I% F$ U  }7 G% l  n) D# ^" S
# o* M, g1 I( X
这是一个更好的语义写法虽然他麻烦了些但是可读性大大增加了在写代码时我们应该尽量把语义层次差不多的放到一个方法中比如:. t% g& r: m) p& E4 y
9 B; A- o2 t& K( [' A
4 j: F% f9 Y6 ]/ v4 e7 o7 d7 p
User user = convertFor(userInputDTO);+ J+ I3 X3 B2 O% m0 B& e. L! y2 t+ k  l
return userService.addUser(user);; b/ a" H* W5 m) s2 p5 N; ?; l. \! u

) S' m! K8 Z  L0 s这两段代码都没有暴露实现都是在讲如何在同一个方法中做一组相同层次的语义操作而不是暴露具体的实现。
' H3 S( o8 d7 ~/ d+ r   N( P% O" i$ a
如上所述是一种重构方式读者可以参考Martin Fowler的《Refactoring Imporving the Design of Existing Code》(重构 改善既有代码的设计) 这本书中的Extract Method重构方式。* f" @0 J- ^1 z; ?& @5 J$ I

% }# B/ L  B) m* F抽象接口定义, L8 X( x$ W, R
, Z5 I7 }* Y) E' @
当实际工作中完成了几个api的DTO转化时我们会发现这样的操作有很多很多那么应该定义好一个接口让所有这样的操作都有规则的进行。2 e5 U3 q9 w# c2 _6 Z8 V! P

9 \3 y  o, s8 R8 V3 H' O如果接口被定义以后那么convertFor这个方法的语义将产生变化他将是一个实现类。
( X, P1 D+ y  r* \8 w8 X& @0 t
5 q. E8 S, L* Y* i* Y  u看一下抽象后的接口:8 _" l" I$ q4 z

& u: n9 L! l8 l5 A6 D' J - ^4 z& D% s1 l$ H: g* D- @
public interface DTOConvert {  o9 n" l0 Q# o  E5 g8 g
     T convert(S s);
3 s# @+ u5 r7 F  V }7 z: F/ b8 ?1 G9 {0 z7 [7 U
: @* [) J, @& F  U
虽然这个接口很简单但是这里告诉我们一个事情要去使用泛型如果你是一个优秀的java程序员请为你想做的抽象接口做好泛型吧。
' n) R' x# N9 z, z7 F 8 k4 @3 E) O; t) P- s  [+ Z7 l
我们再来看接口实现:
% A; u# A! A4 x5 _# d: w * j0 A" d2 n3 H, a
* O$ q- F9 n& h  R2 p
public class UserInputDTOConvert implements DTOConvert {& g  I( @: x9 O* G7 S) x6 r. x
@Override6 g- o" w9 [1 \' I* Z0 J
public User convert(UserInputDTO userInputDTO) {( s. ~9 f0 f' @8 d" M
User user = new User();$ }* P4 w* Z* b) p
BeanUtils.copyProperties(userInputDTO,user);- @3 _6 j' `$ r, Z+ L
return user;
5 y! s0 i/ M" z- v- `& A' ~ }4 R- F8 \, Q$ A7 D
}
& W+ J0 p2 o5 z4 a
) ?: r  r# E0 ^; o* G1 {0 j$ C  j我们这样重构后我们发现现在的代码是如此的简洁并且那么的规范:$ s, n  x  g$ E$ _$ L% T% q; I

. ?# d2 c( U" Y7 J- B" |" l5 d  X ) i1 C: E0 Z* V5 D. ?% K7 {4 o
@RequestMapping("/v1/api/user")
1 P4 [$ ?! \! u5 r, X. B @RestController
! X! {) \( L* ?6 c+ _/ R  S* e, \ public class UserApi {
1 E) w% f3 M! V- a0 c, I     @Autowired- f2 d4 s. Y9 r
     private UserService userService;
4 B0 A% v+ p3 }0 V3 q/ j- F; C     @PostMapping0 W8 |# r4 S4 C+ B6 _% s
     public User addUser(UserInputDTO userInputDTO){( G1 @% G$ V) `6 [3 n
         User user = new UserInputDTOConvert().convert(userInputDTO);
* ~( @( {7 @# e         return userService.addUser(user);
, |' Y: A1 [3 t# a     }
* ?0 n  |0 Z+ A. q" A- D4 E; S }. _/ [0 ?& b% j- t
2 w& a3 d/ W4 T3 K- c
review code% c+ j$ D' [2 }/ z9 [4 k0 p1 |

& ^/ Z5 U7 O  X4 {% _如果你是一个优秀的java程序员我相信你应该和我一样已经数次重复review过自己的代码很多次了。
6 R' o) j  h* q, O
$ E1 ~0 N9 ?8 X7 y! Y- _我们再看这个保存用户的例子你将发现api中返回值是有些问题的问题就在于不应该直接返回User实体因为如果这样的话就暴露了太多实体相关的信息这样的返回值是不安全的所以我们更应该返回一个DTO对象我们可称它为UserOutputDTO:2 P4 g& |8 l) t. \
6 |9 J+ B2 P2 h; M' o% f0 [
7 F, r4 y& a7 `7 z) U0 k
@PostMapping
  t: Z; ?! {3 q2 B6 m" B public UserOutputDTO addUser(UserInputDTO userInputDTO){# j# _& a1 [: p! S
         User user = new UserInputDTOConvert().convert(userInputDTO);4 m6 I$ s4 u2 O0 M
         User saveUserResult = userService.addUser(user);
, P0 H  K0 K1 j6 B: c5 D4 S         UserOutputDTO result = new UserOutDTOConvert().convertToUser(saveUserResult);
4 H3 i( I! P) E2 R; \( |& c6 Y         return result;' r) b0 B# R6 U6 o7 L8 P
}0 l+ S& F7 }" f# j  X, w' A
5 v( R, ~3 ^5 j. |; V
这样你的api才更健全。
9 a. G' X2 ~; q5 R- ]
6 z! k0 c2 m# V! F# }* A2 N6 ?不知道在看完这段代码之后读者有是否发现还有其他问题的存在作为一个优秀的java程序员请看一下这段我们刚刚抽象完的代码:; Y/ h$ ~& i$ i9 _% j
User user = new UserInputDTOConvert().convert(userInputDTO);5 I! `8 S+ b# f

( `( z! G; B: U你会发现new这样一个DTO转化对象是没有必要的而且每一个转化对象都是由在遇到DTO转化的时候才会出现那我们应该考虑一下是否可以将这个类和DTO进行聚合呢看一下我的聚合结果:
& A% ^! U  d+ A% g public class UserInputDTO {/ J/ [& n9 \2 W* C
private String username;: g' z2 t; j# \! F$ G
private int age;/ z1 g: d: L( f6 ]; x# _: Z, a

: A7 [6 g+ U2 \& A, F2 m
; h& t# y3 }$ r8 |. T( D: u+ L: G    public String getUsername() {5 W, i5 T  t* N/ s
         return username;; U& R3 Y- W) S; C
     }
* a# b; B! w! x0 Z: `0 e  d. @5 y     public void setUsername(String username) {! U, ^. p4 \. T) X# U
         this.username = username;$ w! G" |: B: g+ s, C+ t
     }7 P* N" j3 w1 I) `
     public int getAge() {
- b" Z+ V( V& v! m         return age;
6 T- g' y' A1 v0 I  z! ?     }
7 `# b3 M- H0 F2 u& r/ j     public void setAge(int age) {
7 z5 q: B; T7 Q3 {         this.age = age;: T; j) {- e# }: r
     }+ W4 [, ~3 B" _. `

" [4 W) j# p2 ?1 t& g$ \     public User convertToUser(){, {, h6 m& v, n  z" z/ i% B
         UserInputDTOConvert userInputDTOConvert = new UserInputDTOConvert();
) ~( _" B1 E, z( Z2 V         User convert = userInputDTOConvert.convert(this);3 D& U" \' |; b1 C1 {
         return convert;
% R( Q3 Q( d( A# g4 E3 O6 @     }3 t$ h3 ^' \) j+ O: G
     private static class UserInputDTOConvert implements DTOConvert[U] {/ |3 S$ l& Z. B! S( ^1 ^
         @Override' K4 E/ x& x0 D" N
         public User convert(UserInputDTO userInputDTO) {! u2 j, F( V# o9 V* r+ |
             User user = new User();6 a' \' n- |+ b+ A7 b
             BeanUtils.copyProperties(userInputDTO,user);
& B5 \3 q2 H& Z8 J+ k+ f             return user;0 a& q8 i5 w) g7 _7 |2 P% w: c
         }! }3 \3 {. Q1 D: E+ e
     }$ m* z- n6 o! F; V! H) e8 \
}2 f  w! ^# \! ]( v5 x" V# ?, Q9 j
; O6 f1 c$ q+ M: \& S+ @7 c  t
然后api中的转化则由:0 O2 G7 p& S. t- W, _! v3 o
User user = new UserInputDTOConvert().convert(userInputDTO);; v1 K! i" w* v! c8 t
User saveUserResult = userService.addUser(user);
9 V" M. t- I9 N. p2 R: f; [0 a6 O& W
3 N6 K, z' e% A- \  F9 Q变成了:
( X  \7 f8 {0 N* }6 ]2 J9 `( w User user = userInputDTO.convertToUser();
# o; F( C) ~" k User saveUserResult = userService.addUser(user);; h. k- |% g# R( U0 |$ }3 ~! R

% M/ J5 l" |* f/ ]我们再DTO对象中添加了转化的行为我相信这样的操作可以让代码的可读性变得更强并且是符合语义的。
% g" t& G% F+ J( t1 X- T5 [ + \" ^3 R  n+ f$ b6 n- X* V/ l
再查工具类+ w- G" ?# d, _
7 s" @0 C3 m( R( s5 x
再来看DTO内部转化的代码它实现了我们自己定义的DTOConvert接口但是这样真的就没有问题不需要再思考了吗# X; ?3 R7 Z4 A  I2 h7 y$ G
+ m* ?/ s# }/ v# p" I* y& |
我觉得并不是对于Convert这种转化语义来讲很多工具类中都有这样的定义这中Convert并不是业务级别上的接口定义它只是用于普通bean之间转化属性值的普通意义上的接口定义所以我们应该更多的去读其他含有Convert转化语义的代码。, r, m; I! i3 B; O' e, N+ k3 ^) ~$ r
* O) ~3 S/ X: J
我仔细阅读了一下GUAVA的源码发现了com.google.common.base.Convert这样的定义:' M& H2 S; n* t# ]1 o9 }( Q& n: L
7 o  m* T5 I$ E- |  Q

3 Y5 _  H* ?1 k& Cpublic abstract class Converter implements Function {3 a; z3 Z* ^0 }
     protected abstract B doForward(A a);# e( ~# c8 l3 r6 C3 |, I+ h% Z
     protected abstract A doBackward(B b);6 e) o/ Z! M% U% y) m  X1 y1 |
     //其他略. T3 b; q6 p1 C/ A
}
6 s& A* j. \% @- ]. o( @+ t8 N 0 L0 N% f5 v1 v0 h! z5 w& L7 T
从源码可以了解到GUAVA中的Convert可以完成正向转化和逆向转化继续修改我们DTO中转化的这段代码:& [+ ^" X3 x8 b/ |' F

/ n7 H8 Q9 b5 S& _, p- \$ K ! E1 A5 D" |3 Q' @) W+ R  M' X1 R
private static class UserInputDTOConvert implements DTOConvert[U] {
& H3 a! I; Z9 Y  o) t& N  P3 ]0 [- j: b         @Override
8 g5 H) d/ u6 O         public User convert(UserInputDTO userInputDTO) {
( t* b& }$ ?$ H! n                 User user = new User();
. M0 l( v5 h# q1 W/ O                 BeanUtils.copyProperties(userInputDTO,user);% j) S9 r0 l  k0 C) `7 R
                 return user;
; s& N3 [5 T3 S2 C! A" C( V         }6 K5 o  {. N' y- h1 c$ T# j
}" F, S4 E( H6 g( Q) E( m7 b
7 x1 v1 ]$ N& |8 t  N7 t( L+ G, c5 ?) P
修改后:2 L* \  U, P' }

; {6 q  @9 E4 W3 W: E6 @$ r6 t) I
& F- }4 K# W5 \- Aprivate static class UserInputDTOConvert extends Converter[U] {
- v5 z7 J( V- H/ N* v          @Override
+ J1 z" d! y1 F8 z! z4 ~+ m( O          protected User doForward(UserInputDTO userInputDTO) {
- K5 [' {' a' U0 c0 u, y8 o                  User user = new User();
+ n, ?+ q4 w5 `; w) i& b                  BeanUtils.copyProperties(userInputDTO,user);
* }, H9 m' T: q, I# \                  return user;
* U& }. c- d' y+ o% Y9 o          }6 D7 A9 x4 E% B4 s
          @Override
" Q* V2 [) Y1 h# C) e          protected UserInputDTO doBackward(User user) {
9 ^6 v% B# }, O" r# W$ q                  UserInputDTO userInputDTO = new UserInputDTO();
' A; i& l/ L+ F8 P) M. ^# t, K                  BeanUtils.copyProperties(user,userInputDTO);1 r( Y, Q8 g; I: f9 H# {- i* u4 Z/ k
                  return userInputDTO;
6 F  L) W- R1 z2 [) X4 X& P          }
0 {  P2 a6 K+ C4 M  }! B- Z0 f  B' z) L

/ C; Z9 u; D& g9 w6 u( i4 y) L看了这部分代码以后你可能会问那逆向转化会有什么用呢其实我们有很多小的业务需求中入参和出参是一样的那么我们变可以轻松的进行转化我将上边所提到的UserInputDTO和UserOutputDTO都转成UserDTO展示给大家:
, J+ x2 _7 N, i7 F/ q! U/ d; l2 |
$ U# \- a* S9 S$ w6 n2 q( a4 bDTO
5 n! g5 G- _2 y  ?6 m6 ~2 O
& U; _. N  W7 \+ J1 z4 a 7 |0 Y' p4 R! S5 f- z, t9 l* V
public class UserDTO {
& D+ X6 K: Y. y& f     private String username;6 u! [) f3 X# t5 W# |
     private int age;/ l3 J) s9 J4 M. _2 r/ K/ V* C3 F
     public String getUsername() {
! b- g4 [+ A% V' x$ I' N" _             return username;
; K4 L6 ^6 z3 ?2 f' y' `; o     }
6 ^+ @1 r  V: d$ B( J) u     public void setUsername(String username) {- ^# s" R, g# v8 ~5 n8 A
             this.username = username;4 c4 V- ~- p: s: O' `  V
     }
9 J# A) }" ]) s8 T; D6 B     public int getAge() {1 e; y2 M" G; h9 w( F- C
             return age;7 E6 c( [. |1 A
     }: u3 ]7 h" |* a2 s0 Z+ R' T. M0 d
     public void setAge(int age) {2 V$ j- k- P, \7 x0 z4 `
             this.age = age;0 A- \9 E1 M! L5 H: \# R6 J
     }* [: y# e- N4 w: `8 c+ ]  t' X

# M9 ~  h: G8 l/ ?/ g     public User convertToUser(){
! M. b# s; O9 @/ h. Y+ {             UserDTOConvert userDTOConvert = new UserDTOConvert();! Q& w2 w1 g; s
             User convert = userDTOConvert.convert(this);
4 y+ Y% |- l& a' O1 W* u             return convert;
  V" J; b7 O/ R/ a" w* }     }% B) z- E% D% r- Q4 M
     public UserDTO convertFor(User user){9 o  {8 @4 b8 ^/ D4 ]/ J- s( }
             UserDTOConvert userDTOConvert = new UserDTOConvert();
& C" G) V1 X1 l             UserDTO convert = userDTOConvert.reverse().convert(user);( q' i- r' l7 X; E7 t( C+ ^
             return convert;/ s9 q8 W8 M" F2 m7 A& H/ D# H
     }
* ]/ _; \$ m1 Q2 J     private static class UserDTOConvert extends Converter[U] {
" r. F8 j) Y% I% M3 s: z8 X             @Override
( ~2 ?7 x$ m( q             protected User doForward(UserDTO userDTO) {
& l$ e* ]/ q0 V* t0 Q                     User user = new User();
& a$ j2 {/ m& }, c                     BeanUtils.copyProperties(userDTO,user);
* f$ v8 t" b. ~, s                     return user;
% g4 _; j2 O, h3 C0 p             }
! o4 E0 |$ N/ ?8 J& S# ^             @Override8 R+ @" K$ k$ k- ?; O( w/ ]
             protected UserDTO doBackward(User user) {% n3 j; S& K# y- \; Y4 i
                     UserDTO userDTO = new UserDTO();9 k1 A& N8 l6 m! p
                     BeanUtils.copyProperties(user,userDTO);& D8 r+ Q6 A: S
                     return userDTO;
$ t! Q4 f* D! m1 @             }
: B  i. S' {9 {     }
/ \) l0 z7 M3 C }( f, R5 F3 n4 @% }
8 r2 e* C9 g' ^* ~
api:0 G0 @7 L2 ~2 b3 [( s

5 r* L2 T5 P/ {
) [7 g. Y/ z- d+ n* Y@PostMapping
! r5 O* k, A: o- m- Q, v0 N- ^; I  A  public UserDTO addUser(UserDTO userDTO){
2 Z5 F# y$ B- u( I; Y          User user =  userDTO.convertToUser();; v% |# i$ d/ B: H7 S
          User saveResultUser = userService.addUser(user);
; A1 k( V1 x0 k1 r: z: V& r          UserDTO result = userDTO.convertFor(saveResultUser);" w! l+ r7 i6 A& F
          return result;( K* y' Z, k+ i
  }
  ~. g. @0 I5 [! Z 6 o# s, x: w- o/ [" t' w5 k
当然上述只是表明了转化方向的正向或逆向很多业务需求的出参和入参的DTO对象是不同的那么你需要更明显的告诉程序逆向是无法调用的:  v1 D. Q, d& x; N
' V/ R  P6 ~9 N4 |! d* y: ?2 C
" p' p+ ?- r' T' ?) D/ b
private static class UserDTOConvert extends Converter[U] {' G2 v. G6 T  o2 w9 K" F  j+ A7 a
          @Override. C: y4 I% t9 s! W  M4 b6 W: E
          protected User doForward(UserDTO userDTO) {
% k! o# _8 i) \' e                  User user = new User();' x( q! C1 u0 v0 A  p
                  BeanUtils.copyProperties(userDTO,user);* \" z# O+ I4 t
                  return user;
0 t; X( P& o1 {4 k          }
# ^" r6 f; D; Q3 ^/ }+ J9 J3 F          @Override
8 i2 I. h8 d  p+ m0 ~) p          protected UserDTO doBackward(User user) {
2 V9 |2 R  w; J2 L$ i                  throw new AssertionError("不支持逆向转化方法!");- @( E( X$ |& r3 L6 H- y; k: ]
          }
1 O/ O! R7 }, Y2 ?% U. ?1 d  }
3 Z( |$ j% ?0 q 2 y" q0 o0 ~( m+ W  h; P
看一下doBackward方法直接抛出了一个断言异常而不是业务异常这段代码告诉代码的调用者这个方法不是准你调用的如果你调用我就”断言”你调用错误了。; i8 ?7 ~; `+ l) m$ K

1 f1 C4 n! l) @, dbean的验证
( K  X  _, j9 o5 \
7 O) W# V0 u5 f6 G5 R# r如果你认为我上边写的那个添加用户api写的已经非常完美了那只能说明你还不是一个优秀的程序员。我们应该保证任何数据的入参到方法体内都是合法的。
% s9 j) H, m5 H1 w1 z& L
3 y) l: j9 i, K- _  n( P- B9 ~- }为什么要验证
5 r% Y9 U3 U  }. _ ; X4 X+ f$ g; K* ?, e3 ^
很多人会告诉我如果这些api是提供给前端进行调用的前端都会进行验证啊你为什还要验证
3 k, U$ L: o7 \8 x
0 Q5 d3 a7 @8 R# u) i其实答案是这样的我从不相信任何调用我api或者方法的人比如前端验证失败了或者某些人通过一些特殊的渠道(比如Charles进行抓包),直接将数据传入到我的api那我仍然进行正常的业务逻辑处理那么就有可能产生脏数据4 ]6 S6 i( v) U. f
) k: B" r) [5 E- [4 _8 [4 V
“对于脏数据的产生一定是致命”这句话希望大家牢记在心再小的脏数据也有可能让你找几个通宵
7 p) M) b8 ]$ n % w: M! T! q8 H0 ^7 Y
jsr 303验证
: [- n0 f, w( k& A( K7 C- n, }
4 |- r. Q1 j6 r. \* |8 W# Z" ]hibernate提供的jsr 303实现我觉得目前仍然是很优秀的具体如何使用我不想讲因为谷歌上你可以搜索出很多答案!
/ R- Q4 E: Q& A4 D% | 3 L9 ^& H  L: m6 I! e" o
再以上班的api实例进行说明我们现在对DTO数据进行检查:2 b3 Q. Q( q  W2 ^* o9 k7 I5 g
7 O( z7 J7 U# F: G5 v' m+ W

2 Z1 u/ g" {$ B6 h# T8 Epublic class UserDTO {# n  W& \* P8 g# C# x" k1 l6 s
     @NotNull
/ F9 Q" n" T% B     private String username;
' U! P  e3 }& i2 I     @NotNull% `% ]9 ~* D9 O) R
     private int age;
, v9 I' M! n. n0 u( M         //其他代码略
( c( ~3 I7 c" N/ ^; d }
3 o+ I7 F! j2 q: W% ` / ^( z$ ~; a  w% z" K9 l- M7 \8 x) s
api验证:7 W3 ~( `; e5 x( V

, x" D/ K3 x) x- B4 M 2 y" ]/ A" ^  D
@PostMapping5 D9 F7 m5 \. f: P
     public UserDTO addUser(@Valid UserDTO userDTO){8 K4 t% c4 O6 j6 @
             User user =  userDTO.convertToUser();" K* b3 Q9 \. T6 g3 Y' y
             User saveResultUser = userService.addUser(user);
7 e  `. Z  b8 Q2 [. z# O             UserDTO result = userDTO.convertFor(saveResultUser);, v4 \; Z: g* i; O& a2 h6 _) O
             return result;
7 u- x; `6 K5 g' a3 }0 h     }
4 I( }4 `1 o$ Y& L0 Z6 ]. ^( j" U2 G
" _- T' s. S, ~: V0 E/ P  U  i我们需要将验证结果传给前端这种异常应该转化为一个api异常(带有错误码的异常)。
+ g5 F1 ?- y! p* \9 S$ D + W) x5 b# ~+ H' |- @
7 O4 @/ Z, w% y
@PostMapping4 m! z" p2 i& X: ]
public UserDTO addUser(@Valid UserDTO userDTO, BindingResult bindingResult){8 v0 i& T* Q8 H. }: U% ]8 w. j, C* J
      checkDTOParams(bindingResult);- ?& J3 [: o6 Z6 }0 E; D% M
      User user =  userDTO.convertToUser();* M" x* ?  Y" F* {' b2 s
      User saveResultUser = userService.addUser(user);
6 i5 V5 l& e* H, P- |% s, ^* z7 B      UserDTO result = userDTO.convertFor(saveResultUser);, R; C0 [1 G  D) I) R
      return result;8 J- q3 S4 i1 ~, c+ d; W! P
}4 c0 |4 |- n6 B( G  f4 g
private void checkDTOParams(BindingResult bindingResult){
5 h6 b: E( o; V; s9 p      if(bindingResult.hasErrors()){
) X, F* o7 e- J7 {              //throw new 带验证码的验证错误异常
0 @% j1 d: p' v      }
+ [' j" \4 A& B1 `$ S9 F/ D }
1 s: |- `& ~/ K4 z: P4 P ) ]2 n0 |2 n' @- {8 p: s
BindingResult是Spring MVC验证DTO后的一个结果集可以参考spring 官方文档4 d9 {& ^9 x% U8 }) H0 W5 h! _

# l/ o9 o' G/ g检查参数后可以抛出一个“带验证码的验证错误异常”具体异常设计可以参考如何优雅的设计java异常8 |4 ^/ p+ A0 Y7 A) X

+ C; D5 h- `0 \8 t+ m3 Y拥抱lombok: x% _+ o! q! ]  d7 Q5 }
7 T- d9 j/ r7 H/ v6 G
上边的DTO代码已经让我看的很累了我相信读者也是一样看到那么多的Getter和Setter方法太烦躁了那时候有什么方法可以简化这些呢。9 a' l3 b7 Z8 E" G% }/ z! N

$ r+ J. d& G; S, P) r请拥抱lombok,它会帮助我们解决一些让我们很烦躁的问题4 _+ ?6 E3 O8 }% S0 U

% c- ?: a# y2 g# r  B+ S去掉Setter和Getter. }: k: @+ Q0 F8 s5 G" I

# l, t3 A: h$ P6 T8 p其实这个标题我不太想说因为网上太多但是因为很多人告诉我他们根本就不知道lombok的存在所以为了让读者更好的学习我愿意写这样一个例子
2 x' {7 V+ m$ H" i( }! h$ K 7 v$ O$ w( D" C% w) V
2 T# ~& S1 D- b7 I
@Setter5 h& R% `2 M& B. r; H9 R" K
@Getter7 p; `  J# r/ K6 I  z. ~8 ^
public class UserDTO {
' q' @( \" n; T     @NotNull4 E7 y* r6 L1 r6 M- E
     private String username;2 x+ {; U( j" [9 S. S1 k' v
     @NotNull  }1 E! w1 _. J
     private int age;
9 z! ?2 t0 a2 ~& k. w: k' u     public User convertToUser(){
" y! Z' P% P  V  D         UserDTOConvert userDTOConvert = new UserDTOConvert();
2 Q1 a% M. n/ c8 W3 ]: L/ c         User convert = userDTOConvert.convert(this);
8 I! t+ R) j0 M9 ]3 Y         return convert;) X6 o5 c) Z* ^1 M! p) i
     }% @( u$ {# [; v; a  g  g
     public UserDTO convertFor(User user){# Q" t4 L, a* r" Z1 B
         UserDTOConvert userDTOConvert = new UserDTOConvert();5 P. P2 {$ [- S4 A; z0 c
         UserDTO convert = userDTOConvert.reverse().convert(user);: I; M7 J5 `. r% K4 H: z0 h
         return convert;: `0 s; }9 x" s( w" y, _
     }
  V/ C8 X2 M9 O     private static class UserDTOConvert extends Converter[U] {7 u6 d) @6 i+ N/ }+ J6 q
         @Override7 U" o8 A9 f4 f6 J0 J$ g
         protected User doForward(UserDTO userDTO) {
+ R7 ~1 V  W& b" e8 G* T             User user = new User();3 c# \" A+ L, Z. m& G3 |+ K; i: j
             BeanUtils.copyProperties(userDTO,user);
- e3 q3 Y& Q8 R7 Y3 |             return user;
/ F: F6 W0 F6 ~6 n2 C3 A' ~         }
4 P4 A6 U% T  j* `1 n8 T         @Override3 s/ o9 b! I! `" m- G) m$ L+ ^
         protected UserDTO doBackward(User user) {$ S0 M' k2 d+ T3 U  G# {$ _
             throw new AssertionError("不支持逆向转化方法!");' L& W5 ?! m8 x$ c4 ]* P6 @
         }
4 n& q$ k* V1 W  l/ B0 |     }, V' Y; c; e+ a, f' Y& y& c
}
& I6 Q8 q  A5 G1 r  U' f* x: @! s 0 {( `2 ?/ o4 Q  C2 B0 c- o
看到了吧烦人的Getter和Setter方法已经去掉了。6 S0 [7 A% F9 p) d8 A- T' w+ s0 }
; d) K% \. c% y2 I
但是上边的例子根本不足以体现lombok的强大。我希望写一些网上很难查到或者很少人进行说明的lombok的使用以及在使用时程序语义上的说明。
3 B- ~4 X- O* i6 L
3 u% f5 ?" e( |# G* U9 S  L+ j比如:@Data,@AllArgsConstructor,@NoArgsConstructor..这些我就不进行一一说明了请大家自行查询资料.
* w, J# y" G& x: e, S& J- K
2 S8 g! D( a0 e* c( U, v* ~bean中的链式风格2 j! s/ p* u& W$ ?
" Y$ y$ ^# V( x' q0 n" t: |* i
什么是链式风格我来举个例子看下面这个Student的bean:* c1 l' {* E& S1 d0 o, z  G) q
  g  k4 e9 ]0 q& ?+ ~# h
' _2 q# J7 G* c! |$ q4 p0 ~4 e. x
public class Student {- P, t" @* W7 i% i1 s: E
     private String name;
' i6 h1 N' ^% T6 X     private int age;( \# m  q7 k! R
     public String getName() {
: ^8 a7 A, @; |9 }# g8 g" `         return name;
9 H/ {2 ~% l+ p4 V4 E     }
, M( A& n* X  r     public Student setName(String name) {( a# C* C8 e" g% t- ~6 Y
         this.name = name;: s! X: G  D( n6 Z1 k+ ~% p/ k2 i
         return this;
% `, G3 A( r# s" L* E     }
6 u1 w7 |9 U  C! t     public int getAge() {( W- X# \9 r% y
         return age;
- r) |! F% W% a$ G' U1 L+ z     }; M: ~; s/ h* ^  M: d" ^: B
     public Student setAge(int age) {
8 Y+ e, P5 o+ u: k8 p         return this;) v: h" S" `/ [& R1 g
     }" x% x7 ^+ ^7 u
}
" y4 V! p4 @( ?; |4 z2 G8 g1 W
) H5 E9 X" w2 \$ R8 L仔细看一下set方法这样的设置便是chain的style调用的时候可以这样使用:
4 ?5 r- B  @% x, e " [4 C( h0 f+ E" h* b( C2 \
) t( u9 r( p, A$ `; ?% t' P3 V7 ]5 S
Student student = new Student()
' X' x7 c5 g: M5 k$ }! V2 s         .setAge(24)* q' D# d& u# ?& I7 u" Z
         .setName("zs");4 J6 m0 |" ~3 r8 k
/ W7 M3 f7 ]% A" [7 \$ v! X
相信合理使用这样的链式代码会更多的程序带来很好的可读性那看一下如果使用lombok进行改善呢请使用 @Accessors(chain = true),看如下代码:5 z  U  R1 M1 a* Y2 Q1 f4 A
, b. G+ j9 |+ U8 x$ `4 N+ A
. o! m" ?6 B+ S. c! Z- |
@Accessors(chain = true)2 a& }8 s3 k" o6 E
@Setter
' ?& Z3 S  f( I" ]# ] @Getter
% p$ F& t+ ?& u! s! m public class Student {  c0 e, d4 t% M' M
     private String name;
# A" Q" T- B7 K) f     private int age;2 Q. q" H/ B/ k3 V5 J) A
}
) g0 ?7 x+ U8 ?
7 k! W+ y! {6 h; S这样就完成了一个对于bean来讲很友好的链式操作。) T. |. [3 r* w3 W! y# E

4 W, k/ D* D, x3 [: V静态构造方法4 M) T5 m5 N/ j4 g: C7 ]

2 ]) w( N3 B% H" f) y* e* h2 E( Q静态构造方法的语义和简化程度真的高于直接去new一个对象。比如new一个List对象过去的使用是这样的:' U) f1 |) e* }

' w9 p8 t+ e# R% V3 h 5 M3 e4 n3 l$ w0 d- ]' g
List list = new ArrayList();
) D0 ^, ?# L+ H( b7 ~ % O* H+ R) g9 R' K
看一下guava中的创建方式:6 h" ]) a5 ]6 ~4 f; e7 Y

0 b: S; e: I. b) }4 B) b, w
4 J- G9 C- C2 n% ~, m/ w/ f6 Y: LList list = Lists.newArrayList();; C) v# ]; ~$ }/ J' k8 m

: c3 f; U3 y' i/ m/ SLists命名是一种约定(俗话说约定优于配置)它是指Lists是List这个类的一个工具类那么使用List的工具类去产生List这样的语义是不是要比直接new一个子类来的更直接一些呢答案是肯定的再比如如果有一个工具类叫做Maps那你是否想到了创建Map的方法呢6 Z- y4 t* u# R3 F) ?* ]0 D9 F

2 y0 b! k$ C* }7 l
8 t& b% h' h5 kHashMap objectObjectHashMap = Maps.newHashMap();  }: R6 p& O& v/ O  l

) t6 }  ^; Y- U: x5 F$ J好了如果你理解了我说的语义那么你已经向成为java程序员更近了一步了。, C8 Y+ H: @$ ^+ l7 P
" c- S5 V& L- e( m5 K
再回过头来看刚刚的Student很多时候我们去写Student这个bean的时候他会有一些必输字段比如Student中的name字段一般处理的方式是将name字段包装成一个构造方法只有传入name这样的构造方法才能创建一个Student对象。" n( w& W7 r  I5 _# B% P5 W

3 |# b: C! [7 N- O8 f4 C' @/ r接上上边的静态构造方法和必传参数的构造方法使用lombok将更改成如下写法@RequiredArgsConstructor 和 @NonNull:( e: x2 B7 L4 t
  q: ~- p  A3 W
5 f  ^: [* x3 T
@Accessors(chain = true)! D9 d. Q+ ^9 j* A# q5 n: Y
@Setter- A, O+ ^6 B! c  b6 l9 d
@Getter  P  g! H- t0 Z1 ~
@RequiredArgsConstructor(staticName = "ofName")
/ ?: }0 e6 f+ b' Q1 z0 L public class Student {3 s% O$ r- \+ Y6 M" ~1 g
     @NonNull private String name;
0 h0 ?9 P( m1 I! L* [: O2 _     private int age;9 W( s/ y6 {( e& K8 M3 m9 ^
}
& x9 ^  B% B7 `$ s0 I
& L+ K& _+ a. a' C- _, S" j测试代码:
& O7 d+ B# p# v+ I0 R
' r* V; e( ], ^8 P0 n ! @) @3 L  \, v4 Q; Z& {% a( ^
Student student = Student.ofName("zs");
2 H) U. ?0 r# ?% @1 S8 W 7 j/ P/ {& r0 U4 G& {: z0 }
这样构建出的bean语义是否要比直接new一个含参的构造方法(包含 name的构造方法)要好很多。
  Y, P, _: r1 d' T2 F$ ?( i; x
. |! M* a5 i. G. `/ Z当然看过很多源码以后我想相信将静态构造方法ofName换成of会先的更加简洁:1 f& w; Z  M0 n& T! j6 w7 r6 X
# r" u7 K  e3 e7 R

; _6 \9 Q# l5 W@Accessors(chain = true)! H1 I& f- O9 ]
@Setter
4 p: W# x0 S9 w9 T  x @Getter
% T$ w5 {$ P% i" f1 t, N @RequiredArgsConstructor(staticName = "of")
& n0 n: |/ z& [5 |) }0 \ public class Student {
8 k$ @; n3 }, g) x& F         @NonNull private String name;0 R& n$ P3 X5 a  i4 A( }% z% W
         private int age;: u! ?- }3 s+ b) V+ e& c8 n' P
}
8 i! {+ c" L- M5 P( V
- v6 U2 W, J3 g0 T8 V, u6 E9 e% m: O" X测试代码:) l, ]  Y& H; ~9 u# S

3 s/ v, ~" w: [" [  s/ F* K
2 I9 s$ L! _  P- L! ZStudent student = Student.of("zs");
1 G) P) i1 u$ x) { 7 q+ `# @6 \$ D
当然他仍然是支持链式调用的:2 A. [/ G: G  V5 i" g& A
( A* J1 W  D6 H+ a5 h+ ]+ r- Q2 _

5 }, D5 V5 U" T; C. ~! E2 H9 l' s, }. IStudent student = Student.of("zs").setAge(24);/ c) U; Z3 E5 r
! P/ b/ B9 K. ~( f* l  x
这样来写代码真的很简洁并且可读性很强。1 E6 Q) E) x2 u! r3 Y0 s
9 i/ N; i% }8 T3 ^, K; f. A. r
使用builder$ a) e6 Z1 n& w4 X$ v
' O  M: s# B3 D3 U' R% ^9 I
Builder模式我不想再多解释了读者可以看一下《Head First》(设计模式) 的建造者模式。) f6 x6 |) r* v4 B/ D
* H) L. a. Q/ g9 ]! i% _
今天其实要说的是一种变种的builder模式那就是构建bean的builder模式其实主要的思想是带着大家一起看一下lombok给我们带来了什么。
7 S$ g7 X& o; A" m 7 P6 T' T; K3 T) P
看一下Student这个类的原始builder状态:
& x# m0 D8 j+ B0 T( `
+ R6 A. ~/ h$ m; E
0 I: q9 o# _# E1 f. {# z& vpublic class Student {1 b/ U: X3 i$ J4 \
     private String name;* H& @1 m: f% D) z8 @
     private int age;
. @- n- J: C: N3 a3 B$ ^( x' w( ]& w     public String getName() {
; V9 A% N3 [. ]+ a- J4 ]% ^! B             return name;
! B0 ^- V9 [( L- ?" M3 x     }9 Z  }% _% g/ |2 g2 t! u6 J# X
     public void setName(String name) {9 ?2 o) K5 Y) Z: h9 J+ J$ j
             this.name = name;
7 u' ?* U  y; X8 m5 G$ D) U     }3 ~$ e3 ^' @, O2 O3 |! ]9 ?
     public int getAge() {# U2 g$ U; _* N2 R
             return age;
/ J) o+ i6 R" M: L     }
8 t# h% \# q2 M% b     public void setAge(int age) {% f$ V. R' l# C- G: w
             this.age = age;
& K0 q; c6 W+ t; k) j& W5 E' G7 h1 T     }
# F% V9 {: `% ^     public static Builder builder(){! o; z' i( W; W# m+ R) H4 I7 }: x) G
             return new Builder();9 @/ ^+ e8 g2 U. {% D- v3 c
     }$ n0 W7 P# T, a3 r! w
     public static class Builder{
$ s- k* \. j+ }* G0 p+ C. I# k4 d             private String name;
/ R/ Y6 m$ q" _! {             private int age;6 T0 Y$ k% t$ x8 g, g" R7 z
             public Builder name(String name){4 s/ H3 l0 Q% p6 k& P! ^$ S8 L
                     this.name = name;
9 U' h7 {8 M6 M+ s' c- W6 s                     return this;; A6 ^4 u1 @6 J6 X# {
             }
: r) z/ T6 O7 F/ \2 Y             public Builder age(int age){
% D. X7 E7 s  N# ~, L                     this.age = age;
6 l2 v5 r) x: x7 u* b                     return this;
4 J0 {8 q4 A% _: S$ s* y2 x             }
  C0 G* J' J0 ~$ I  h" F: W             public Student build(){
3 \1 q* G$ r# u9 W0 ?1 e                     Student student = new Student();( N0 A1 }! x7 B
                     student.setAge(age);
) O2 i* T8 }1 _; X& V                     student.setName(name);
$ J7 X  j: P7 b0 R4 \9 w% v                     return student;$ J* x# C/ [+ I
             }6 ?( A3 b1 c& Q# l' Z
     }6 X/ s3 I8 b7 _8 ^& Q
}9 M% L4 Q* [9 m/ }6 F

7 K7 ~2 G. F3 M# G$ `. k调用方式:
% ]7 q1 A. P; Y* `1 I6 N2 G
& g+ _- t# g' H9 J0 Z+ ~ - Z$ I6 q  Y* u2 m6 i1 h
Student student = Student.builder().name("zs").age(24).build();& k" A/ W6 b# a+ t, ^5 b5 d' z

2 y& `7 x% {- E4 D1 D, |这样的builder代码让我是在恶心难受于是我打算用lombok重构这段代码:2 }6 l/ D5 ]. q* {$ Y
5 p9 I8 G3 B% }! _7 M) Y/ d: z
, q9 v: |- T! x5 g# }# F' ?! l
@Builder% J. ]4 M) J: V' z
public class Student {
" n9 D+ e5 x! [" E" ~+ r( _     private String name;
  f/ Z* O  t8 j) a* i( _9 o, ?     private int age;- Z$ f1 y  d2 h- y& b% ?+ f
}( a) f4 @; v. [3 A: _( P' K  s

0 l* p2 g/ V) e调用方式:# g4 C% C8 [5 o$ @
! q5 |) Z  E& x! _7 I5 X. g8 }+ T
# P' y4 j/ L. }1 L
Student student = Student.builder().name("zs").age(24).build();
+ [  q+ s# z5 u: Z7 v " H. N" S# x2 f! }0 [7 h, {
代理模式
+ ~; V; }) m8 c) T: J6 w 9 I) p9 e0 w  m% H
正如我们所知的在程序中调用rest接口是一个常见的行为动作如果你和我一样使用过spring 的RestTemplate,我相信你会我和一样对他抛出的非http状态码异常深恶痛绝。
+ K1 O: X. r: ]+ J ; ^+ k* h' w- G
所以我们考虑将RestTemplate最为底层包装器进行包装器模式的设计:
+ @. N$ W/ D' f! F5 V
# a: _9 I  b2 x
0 q# d+ I. e* _' [public abstract class FilterRestTemplate implements RestOperations {
) b7 A8 i4 c: N- J5 B         protected volatile RestTemplate restTemplate;
" D" A1 ?8 C% \' w/ ^. K  i         protected FilterRestTemplate(RestTemplate restTemplate){1 f2 V$ Q; Q! q
                 this.restTemplate = restTemplate;
. s5 \( c* m" n0 F& L         }4 j  ?- z8 a/ o; H
         //实现RestOperations所有的接口
& x. m% [4 N) h* B! S1 x }
$ j/ c( G* {6 b# ~3 p7 i0 V, `
- g3 Z5 J6 d+ f然后再由扩展类对FilterRestTemplate进行包装扩展:
/ A% t) t4 P# T3 M 2 q/ W+ V0 z" I7 ?
2 x6 G' ], K5 X' G3 {6 \5 ~
public class ExtractRestTemplate extends FilterRestTemplate {7 i. `7 N9 \' w" d8 v$ N
     private RestTemplate restTemplate;7 A' T3 L/ O4 }% J8 x. {$ T
     public ExtractRestTemplate(RestTemplate restTemplate) {
6 }& [5 T" D; x' T$ u2 v             super(restTemplate);  V% ^7 j% T; p: O: E, t% C3 {7 C# p
             this.restTemplate = restTemplate;, R2 y( @. T0 o6 ?" `# c
     }  m5 _5 V+ K1 O2 B
     public  RestResponseDTO postForEntityWithNoException(String url, Object request, Class responseType, Object... uriVariables)- R8 C% x4 B3 m" S4 ?1 c5 G+ u/ z, }
                     throws RestClientException {
0 P9 I$ o6 W9 ]! G% k             RestResponseDTO restResponseDTO = new RestResponseDTO();
- W7 }& O1 c9 z' d% C             ResponseEntity tResponseEntity;
5 J! m  M/ o! T7 g             try {
2 o/ k  s  P4 _                     tResponseEntity = restTemplate.postForEntity(url, request, responseType, uriVariables);0 u8 b% `4 C4 n6 W# c
                     restResponseDTO.setData(tResponseEntity.getBody());) h' E2 m$ m- v" P# l
                     restResponseDTO.setMessage(tResponseEntity.getStatusCode().name());
! D/ p1 ?2 m2 h, A" D: {+ X$ r                     restResponseDTO.setStatusCode(tResponseEntity.getStatusCodeValue());
( q8 L! F- H' u0 j; [6 Y% C             }catch (Exception e){
% J  ^& m7 q! M: V8 ]                     restResponseDTO.setStatusCode(RestResponseDTO.UNKNOWN_ERROR);
" H7 i0 j# u. a                     restResponseDTO.setMessage(e.getMessage());
' \$ Z4 E+ t) n( t$ S$ H/ j6 r                     restResponseDTO.setData(null);7 H) H7 O5 ~: u* d. q) i
             }
' H0 h8 D% e/ u! y             return restResponseDTO;. }* A- H- l; _& x2 _' U
     }
4 ^% n9 K5 M0 r }
( W: y5 M2 Z1 D9 Z- q . r' S7 V- o0 t  e+ Q
包装器ExtractRestTemplate很完美的更改了异常抛出的行为让程序更具有容错性。在这里我们不考虑ExtractRestTemplate完成的功能让我们把焦点放在FilterRestTemplate上“实现RestOperations所有的接口”,这个操作绝对不是一时半会可以写完的当时在重构之前我几乎写了半个小时,如下:
: ^- s) r' c2 |  j % a3 c- W/ c% M) m6 b/ j3 z" q
( U& n" J2 f; t: [4 O, ]
public abstract class FilterRestTemplate implements RestOperations {
# s8 F2 ]# B! ~, a     protected volatile RestTemplate restTemplate;- s1 ~& q9 n8 X
     protected FilterRestTemplate(RestTemplate restTemplate) {
- c1 B6 l2 D7 z; e             this.restTemplate = restTemplate;
! h; R1 A8 d  j: |, e. ?     }0 g2 D  @0 }2 m, d' k. q) U: {
     @Override
: c, H( G5 ^: A9 Y9 G     public  T getForObject(String url, Class responseType, Object... uriVariables) throws RestClientException {
; \  U, Q% b& z& x: J; W             return restTemplate.getForObject(url,responseType,uriVariables);
" v; f7 g" y! v0 f     }
. ]7 ?2 }! Y. S. D  k  I) J/ j     @Override! R9 R( u% T* x$ |
     public  T getForObject(String url, Class responseType, Map uriVariables) throws RestClientException {
) i" x- A6 D7 k9 Q' I8 N             return restTemplate.getForObject(url,responseType,uriVariables);
) \# o. S2 L, G- t/ G5 m9 M     }
3 A% M# Z) k- T6 S- {     @Override
2 b1 k0 P* |1 G     public  T getForObject(URI url, Class responseType) throws RestClientException {5 Z; K0 R! y* U/ q
             return restTemplate.getForObject(url,responseType);: T9 x; ~& Y% s  v- `. ^) T
     }
/ t* A) r4 W" D2 G" A     @Override
7 q: `4 t3 L1 M) K     public  ResponseEntity getForEntity(String url, Class responseType, Object... uriVariables) throws RestClientException {
" h, d) q6 r! N# b, j! B, G$ v             return restTemplate.getForEntity(url,responseType,uriVariables);
3 G7 m- I/ \$ J) G$ g) M     }5 Y1 ]7 |  D2 N4 k) ?0 S; I
     //其他实现代码略。。。
: t, `7 _# ^0 [0 E: e }
3 |1 ^0 u0 _1 [# b7 x; Y2 h0 n & l, G2 ~% d. q& G; j
我相信你看了以上代码你会和我一样觉得恶心反胃后来我用lombok提供的代理注解优化了我的代码(@Delegate):
! }8 c; S* u$ d
4 b" T7 c& v' U) D; @2 I! t
# p% J2 W1 i" L( T+ x: |* L) w@AllArgsConstructor9 O, c. j* |' d7 z& H: E7 C; z7 F9 y
public abstract class FilterRestTemplate implements RestOperations {
: x  w% d6 g% Y     @Delegate
; N4 l4 I8 @4 r$ o& l     protected volatile RestTemplate restTemplate;
# W8 n* X: Q7 R- D+ M1 |4 \ }
7 p* P$ p$ M  x1 M2 `
: u3 Z2 F7 u+ R) e$ K这几行代码完全替代上述那些冗长的代码。
  z% R5 T5 A; Q) E6 W" A ; F4 k3 \& T% Z1 s
是不是很简洁做一个拥抱lombok的程序员吧。/ J- f" Z8 p" h3 f
' u% x0 ~0 F" x) B, _
重构4 b4 `# P7 P  j0 B8 p2 e

4 f! c5 e$ j. p! V1 s/ D" S0 k需求案例3 O+ D) X; s0 e4 V
3 i; r2 J' v2 f" ?) U
项目需求
$ @5 f6 L; p# F7 T+ k 0 z( N6 l/ U% d1 |& b
项目开发阶段有一个关于下单发货的需求如果今天下午3点前进行下单那么发货时间是明天如果今天下午3点后进行下单那么发货时间是后天如果被确定的时间是周日那么在此时间上再加1天为发货时间。' H6 x  x/ U- P8 {! ~: u. R9 @
1 X# {& S; M: k# d' w
思考与重构! u" V/ v  T% L" h/ c# }
2 X+ _! O! U/ u6 K4 l4 j* P
我相信这个需求看似很简单无论怎么写都可以完成。
8 i9 p" p1 _9 J , N. G# _' D: r5 r) C
很多人可能看到这个需求就动手开始写Calendar或Date进行计算从而完成需求。
! V( v- u' d" A4 } 8 n: r* y# J+ t& Z
而我给的建议是仔细考虑如何写代码然后再去写不是说所有的时间操作都用Calendar或Date去解决一定要看场景。
+ e" h5 h; f1 Y
- Z8 |: z+ ?% v" v+ Y, ^9 g3 `对于时间的计算我们要考虑joda-time这种类似的成熟时间计算框架来写代码它会让代码更加简洁和易读。5 p6 X: N- h: E9 @: |9 U
4 y& U, A# E0 L9 S+ P
请读者先考虑这个需求如何用java代码完成或先写一个你觉得完成这个代码的思路再来看我下边的代码这样你的收获会更多一些:; u6 J. i9 c4 I/ n
. n2 V( Z7 V+ H% q* T9 m

' W- i1 _5 z, T' p4 O$ P9 m8 Ffinal DateTime DISTRIBUTION_TIME_SPLIT_TIME = new DateTime().withTime(15,0,0,0);
, d, c1 m  c1 |* j$ f private Date calculateDistributionTimeByOrderCreateTime(Date orderCreateTime){
% w# n3 _. l+ R8 E/ L/ g     DateTime orderCreateDateTime = new DateTime(orderCreateTime);$ R' D$ D1 n" s
     Date tomorrow = orderCreateDateTime.plusDays(1).toDate();3 W; \& j/ ?7 _4 _. M
     Date theDayAfterTomorrow = orderCreateDateTime.plusDays(2).toDate();
) c: O/ g; t7 }9 ^1 V) u. `     return orderCreateDateTime.isAfter(DISTRIBUTION_TIME_SPLIT_TIME) ? wrapDistributionTime(theDayAfterTomorrow) : wrapDistributionTime(tomorrow);7 `, `0 n( l6 S/ ^4 T
}
' F) [, q4 n, Z, ~' J( n4 c' [ private Date wrapDistributionTime(Date distributionTime){' z5 z/ u6 S0 u3 b, |2 n5 u
     DateTime currentDistributionDateTime = new DateTime(distributionTime);& u% n3 f# }1 l( p9 O
     DateTime plusOneDay = currentDistributionDateTime.plusDays(1);. `# f3 W7 M; ]
     boolean isSunday = (DateTimeConstants.SUNDAY == currentDistributionDateTime.getDayOfWeek());
1 p- x" {& n# y4 b     return isSunday ? plusOneDay.toDate() : currentDistributionDateTime.toDate() ;
- X4 s, |3 s5 n/ w$ J }
* ?" Q9 x( C1 l. K- E& d) ]) p+ Q
6 ^4 M8 e# |9 ?9 l( p5 K读这段代码的时候你会发现我将判断和有可能出现的不同结果都当做一个变量最终做一个三目运算符的方式进行返回这样的优雅和可读性显而易见当然这样的代码不是一蹴而就的我优化了3遍产生的以上代码。读者可根据自己的代码和我写的代码进行对比。2 ?# T8 c7 @# Y) [1 ~0 i- F
1 ?: `5 ?9 g# B" w
提高方法5 F; M# ]( Y/ j1 E, o4 w- {

* x* j9 F" v8 b( e: n1 e3 S+ Q6 L如果你做了3年+的程序员我相信像如上这样的需求你很轻松就能完成但是如果你想做一个会写java的程序员就好好的思考和重构代码吧。
5 t1 U, r/ y, C3 M9 s7 ]9 N
/ Q! h" L8 c+ F( G写代码就如同写字一样同样的字大家都会写但是写出来是否好看就不一定了。如果想把程序写好就要不断的思考和重构敢于尝试敢于创新不要因循守旧一定要做一个优秀的java程序员。! S4 v$ L  {6 s2 k  I1 P
0 X4 z6 [& Z0 T* Y# a
提高代码水平最好的方法就是有条理的重构(注意是有条理的重构): f# t, ~% w" H! z* C) X2 x* P

  Z: Q6 M$ M% U, d7 @- j) U设计模式6 n( Q5 L+ e; o# Y  h5 l

, v4 G2 l+ \( T" x7 O6 K4 g设计模式就是工具而不是提现你是否是高水平程序员的一个指标。
2 T( T) C: ]. N# F4 T+ { ' i( D! H; V1 j# {
我经常会看到某一个程序员兴奋的大喊哪个程序哪个点我用到了设计模式写的多么多么优秀多么多么好。我仔细去翻阅的时候却发现有很多是过度设计的。
6 E! M5 W2 R1 P# j
8 g4 n" ~+ {" q/ q5 F业务驱动技术 or 技术驱动业务
3 p7 q; f  J* l8 e- j* T
- ]) g, o6 L8 C* r+ o& m+ X5 Q业务驱动技术 or 技术驱动业务 其实这是一个一直在争论的话题但是很多人不这么认为我觉得就是大家不愿意承认罢了。我来和大家大概分析一下作为一个java程序员我们应该如何判断自己所处于的位置.% A  }: b) h+ [) W% B. R, `$ o

# l) D/ F  E0 M) b2 f4 y业务驱动技术如果你所在的项目是一个收益很小或者甚至没有收益的项目请不要搞其他创新的东西不要驱动业务要如何如何做而是要熟知业务现在的痛点是什么如何才能帮助业务盈利或者让项目更好更顺利的进行。
; r' G- U2 g- c; a5 J " k5 m$ |* t9 l: o  M& }) G* J
技术驱动业务如果你所在的项目是一个很牛的项目比如淘宝这类的项目我可以在满足业务需求的情况下和业务沟通使用什么样的技术能更好的帮助业务创造收益比如说下单的时候要进队列可能几分钟之后订单状态才能处理完成但是会让用户有更流畅的体验赚取更多的访问流量那么我相信业务愿意被技术驱动会同意订单的延迟问题这样便是技术驱动业务。, Q6 E. o* ]- m0 e/ b6 j" Q* L8 O
1 V9 U) M. o" c1 W2 G% ^
我相信大部分人还都处于业务驱动技术的方向吧。
3 Y; n( ~& N! |: s( U  W2 r8 v7 e 5 W3 o" U  j1 \- e0 i, W4 h' n
所以你既然不能驱动业务那就请拥抱业务变化吧。
* L) V0 W9 M) t
) q0 V: X& m3 U( {* N4 c9 C代码设计
& A0 m0 l0 S: H
' k( A6 T( @3 K/ A: ^9 a! M一直在做java后端的项目经常会有一些变动我相信大家也都遇到过。
$ o+ y1 P1 _$ a# G& x
# T& _- C0 u' D1 [% q比如当我们写一段代码的时候我们考虑将需求映射成代码的状态模式突然有一天状态模式里边又添加了很多行为变化的东西这时候你就挠头了你硬生生的将状态模式中添加过多行为和变化。
* G& X9 W+ v/ `, c4 K
* K( C/ ]6 ~" }5 W慢慢的你会发现这些状态模式其实更像是一簇算法应该使用策略模式这时你应该已经晕头转向了。" T! i( m, M2 ?
& d" r) V7 p  R
说了这么多我的意思是只要你觉得合理就请将状态模式改为策略模式吧所有的模式并不是凭空想象出来的都是基于重构。
" H; d& I+ j2 g. |; `' V) t
: t- W) p7 [4 r9 o) c6 {: ujava编程中没有银弹请拥抱业务变化一直思考重构你就有一个更好的代码设计!
) T. _( ^& ]) D/ C9 @ 7 E! A9 E. n3 Z/ Q; f3 o
你真的优秀吗
- q! T3 G$ T2 t- V: @1 Q # E; L8 [; I" L' i
真不好意思我取了一个这么无聊的标题。$ r2 X' C0 P, w: r2 O

/ q! \) X. B2 n国外流行一种编程方式叫做结对编程我相信国内很多公司都没有这么做我就不在讲述结对编程带来的好处了其实就是一边code review一边互相提高的一个过程。既然做不到这个那如何让自己活在自己的世界中不断提高呢6 n" }8 f, I* E; n6 }+ T9 F

8 Z5 ^9 r( a( F: S& w% s9 G8 ^. g“平时开发的时候做出的代码总认为是正确的而且写法是完美的。”我相信这是大部分人的心声还回到刚刚的问题如何在自己的世界中不断提高呢) h) W2 V9 r2 ~
9 ?( y  ^  }1 f# ^
答案就是:
) T) k  S1 @6 D& d
" {$ |4 X# _& H( W
  • 多看成熟框架的源码3 k* \7 t% ?! @: T
  • 多回头看自己的代码3 v9 l2 a, h; S' \0 H
  • 勤于重构0 s. I$ u% X; B4 l& |

    ' a+ V0 P) A9 S你真的优秀吗如果你每周都完成了学习源码回头看自己代码然后勤于重构我认为你就真的很优秀了。
    6 G, U1 y) E1 B( k4 v3 e( B
    ; L; m  l. q% S; c* l" g3 K# R* p即使也许你只是刚刚入门但是一直坚持你就是一个真的会写java代码的程序员了。* w0 ?, ^7 P( U+ ]- t2 L1 O

    % [/ t3 k3 ]8 |3 v; _: n技能
    ; \$ g% t/ W% B/ b1 O; J; Z+ m 0 P1 Y/ ]6 D/ p5 ^
    UML
    - z4 b( g. t% v . F; n$ w, q, d! H
    不想多讨论UML相关的知识但是我觉得你如果真的会写java请先学会表达自己UML就是你说话的语言做一名优秀的java程序员请至少学会这两种UML图
    * |; ?. y# g) M1 R7 r0 y
    0 H2 @. p7 p9 y( y
  • 类图: m; c4 X- ~6 D) w6 b
  • 时序图% w! Z4 ~3 ^7 @* v4 A% ^

    0 _* n$ }: u5 V5 p2 j& bclean code6 H( d  y4 ]: r  t( l3 k

    $ p3 m, Q- p7 g我认为保持代码的简洁和可读性是代码的最基本保证如果有一天为了程序的效率而降低了这两点我认为是可以谅解的除此之外没有任何理由可以让你任意挥霍你的代码。
    9 x2 |3 b0 b$ v3 A$ O. d
    " X3 Q2 f) n6 }/ K
  • 读者可以看一下Robert C. Martin出版的《Clean Code》代码整洁之道 这本书
    , ?3 B4 W" j2 c9 e& a, i! r, S- I7 f
  • 也可以看一下阿里的Java编码规范
    8 n1 g' z* d8 Z
    - P, i' ]3 s4 @! n% |! Z# B/ l" L

    ) P' A5 u! g4 F$ a$ V1 W无论如何请保持你的代码的整洁。/ R; S# j, R- {- V* w* L' a

    - }6 J* [. k$ f$ u; Vlinux 基础命令
    3 P( b+ A( ?9 |. [2 u: J6 E ( s: I( U/ y$ W2 k
    这点其实和会写java没有关系但是linux很多时候确实承载运行java的容器请学好linux的基础命令。* Z( U7 x8 o0 o0 c1 |9 _2 |

    2 `2 J2 j0 I3 b& x7 d0 p, m5 t# Y总结
    . O6 U' E' ^- P/ p7 v- F% `$ B
    - R) O$ e) E! B9 tJava是一个大体系今天讨论并未涉及框架和架构相关知识只是讨论如何写好代码。- E$ a1 t" }& U

    7 r' {$ s+ A; P6 C本文从写Java程序的小方面一直写到大方面来阐述了如何才能写好java程序并告诉读者们如何才能提高自身的编码水平。
    + K) b$ o0 b: [! J8 M1 e" b6 N
    本文来源csdn,由Java吧转载发布,观点不代表Java吧的立场,转载请标明来源出处:https://www.java8.com
  • 分享到:
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则