精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

騰訊工作近十年大佬:不是我打擊你!你可能真的不會寫Java

開發 前端
本文不是一個吹噓的文章,不會講很多高深的架構,相反,會講解很多基礎的問題和寫法問題,如果讀者自認為基礎問題和寫法問題都是不是問題,那請忽略這篇文章,節省出時間去做一些有意義的事情。

文章核心

其實,本不想把標題寫的那么恐怖,只是發現很多人干了幾年 Java 以后,都自認為是一個不錯的 Java 程序員了,可以拿著上萬的工資都處宣揚自己了,寫這篇文章的目的并不是嘲諷和我一樣做 Java 的同行們,只是希望讀者看到此篇文章后,可以和我一樣,心平氣和的爭取做一個優秀的程序員。

[[269290]]

講述方向

由于一直從事移動互聯網相關工作,Java 開發中經常和移動端打交道或者做一些后端的工作,所以本篇文章更可能涉及和移動端的交互或者與后端的交互方式,筆者希望以自身的一些學習經驗或者開發經驗,可以帶動認真閱讀本篇文章的讀者們,讓大家對 Java 有一個更好的態度去學習它,它不只是一個賺錢的工具而已。

筆者身邊有很多與筆者年齡相仿或年齡更大的朋友或同事,經常有人問我:“你現在還在學習嗎?我覺得沒什么好學的,這些東西都差不多”,我總是回答只要有時間,我就要看一會書,這個時候,大家都會露出一副不屑的眼神或笑容。其實,非常能理解身邊朋友或同事的看法,以目前狀態來講,大多都是工作至少 5 年的程序員了,對于公司大大小小的業務需要,以目前的知識儲備來講,都可以輕松應對,“沒有什么好學的”其實這句話沒有多大的問題,但是,如果你對編程還有一點點興趣,只是不知道如何努力或改進,希望本篇文章可以幫到你。

技術點

本文不是一個吹噓的文章,不會講很多高深的架構,相反,會講解很多基礎的問題和寫法問題,如果讀者自認為基礎問題和寫法問題都是不是問題,那請忽略這篇文章,節省出時間去做一些有意義的事情。

開發工具

不知道有多少”老”程序員還在使用 Eclipse,這些程序員們要不就是因循守舊,要不就是根本就不知道其他好的開發工具的存在,Eclipse 吃內存卡頓的現象以及各種偶然莫名異常的出現,都告知我們是時候尋找新的開發工具了。

更換 IDE

根本就不想多解釋要換什么樣的 IDE,如果你想成為一個優秀的 Java 程序員,請更換 IntelliJ IDEA。使用 IDEA 的好處,請搜索谷歌。

別告訴我快捷鍵不好用

更換 IDE 不在我本文的重點內容中,所以不想用太多的篇幅去寫為什么更換IDE。在這里,我只能告訴你,更換 IDE 只為了更好、更快的寫好 Java 代碼。原因略。

別告訴我快捷鍵不好用,請嘗試新事物。

bean

bean 使我們使用最多的模型之一,我將以大篇幅去講解 bean,希望讀者好好體會。

domain 包名

根據很多 Java 程序員的”經驗”來看,一個數據庫表則對應著一個 domain 對象,所以很多程序員在寫代碼時,包名則使用:com.xxx.domain ,這樣寫好像已經成為了行業的一種約束,數據庫映射對象就應該是 domain。但是你錯了,domain 是一個領域對象,往往我們再做傳統 Java 軟件 Web 開發中,這些 domain 都是貧血模型,是沒有行為的,或是沒有足夠的領域模型的行為的,所以,以這個理論來講,這些 domain 都應該是一個普通的 entity 對象,并非領域對象,所以請把包名改為:com.xxx.entity。

如果你還不理解我說的話,請看一下 Vaughn Vernon 出的一本叫做《IMPLEMENTING DOMAIN-DRIVEN DESIGN》(實現領域驅動設計)這本書,書中講解了貧血模型與領域模型的區別,相信你會受益匪淺。

DTO

數據傳輸我們應該使用 DTO 對象作為傳輸對象,這是我們所約定的,因為很長時間我一直都在做移動端 API 設計的工作,有很多人告訴我,他們認為只有給手機端傳輸數據的時候(input or output),這些對象成為 DTO 對象。請注意!這種理解是錯誤的,只要是用于網絡傳輸的對象,我們都認為他們可以當做是 DTO 對象,比如電商平臺中,用戶進行下單,下單后的數據,訂單會發到 OMS 或者 ERP 系統,這些對接的返回值以及入參也叫 DTO 對象。

我們約定某對象如果是 DTO 對象,就將名稱改為 XXDTO,比如訂單下發OMS:OMSOrderInputDTO。

DTO 轉化

正如我們所知,DTO 為系統與外界交互的模型對象,那么肯定會有一個步驟是將 DTO 對象轉化為 BO 對象或者是普通的 entity 對象,讓 service 層去處理。

場景

比如添加會員操作,由于用于演示,我只考慮用戶的一些簡單數據,當后臺管理員點擊添加用戶時,只需要傳過來用戶的姓名和年齡就可以了,后端接受到數據后,將添加創建時間和更新時間和默認密碼三個字段,然后保存數據庫。

  1. @RequestMapping("/v1/api/user"
  2. @RestController 
  3. public class UserApi { 
  4.  @Autowired 
  5.  private UserService userService; 
  6.  @PostMapping 
  7.  public User addUser(UserInputDTO userInputDTO){ 
  8.  User user = new User(); 
  9.  user.setUsername(userInputDTO.getUsername()); 
  10.  user.setAge(userInputDTO.getAge()); 
  11.  return userService.addUser(user); 
  12.  } 

我們只關注一下上述代碼中的轉化代碼,其他內容請忽略:

  1. User user = new User(); 
  2. user.setUsername(userInputDTO.getUsername()); 
  3. user.setAge(userInputDTO.getAge()); 

請使用工具

上邊的代碼,從邏輯上講,是沒有問題的,只是這種寫法讓我很厭煩,例子中只有兩個字段,如果有 20 個字段,我們要如何做呢? 一個一個進行 set 數據嗎?當然,如果你這么做了,肯定不會有什么問題,但是,這肯定不是一個最優的做法。

網上有很多工具,支持淺拷貝或深拷貝的 Utils。舉個例子,我們可以使用 org.springframework.beans.BeanUtils#copyProperties 對代碼進行重構和優化:

  1. @PostMapping 
  2. public User addUser(UserInputDTO userInputDTO){ 
  3.  User user = new User(); 
  4.  BeanUtils.copyProperties(userInputDTO,user); 
  5.  return userService.addUser(user); 

BeanUtils.copyProperties 是一個淺拷貝方法,復制屬性時,我們只需要把 DTO 對象和要轉化的對象兩個的屬性值設置為一樣的名稱,并且保證一樣的類型就可以了。如果你在做 DTO 轉化的時候一直使用 set 進行屬性賦值,那么請嘗試這種方式簡化代碼,讓代碼更加清晰!

轉化的語義

上邊的轉化過程,讀者看后肯定覺得優雅很多,但是我們再寫 Java 代碼時,更多的需要考慮語義的操作,再看上邊的代碼:

  1. User user = new User(); 
  2. BeanUtils.copyProperties(userInputDTO,user); 

雖然這段代碼很好的簡化和優化了代碼,但是他的語義是有問題的,我們需要提現一個轉化過程才好,所以代碼改成如下:

  1. @PostMapping 
  2.  public User addUser(UserInputDTO userInputDTO){ 
  3.  User user = convertFor(userInputDTO); 
  4.  return userService.addUser(user); 
  5.  } 
  6.  private User convertFor(UserInputDTO userInputDTO){ 
  7.  User user = new User(); 
  8.  BeanUtils.copyProperties(userInputDTO,user); 
  9.  return user
  10.  } 

這是一個更好的語義寫法,雖然他麻煩了些,但是可讀性大大增加了,在寫代碼時,我們應該盡量把語義層次差不多的放到一個方法中,比如:

  1. User user = convertFor(userInputDTO); 
  2. return userService.addUser(user); 

這兩段代碼都沒有暴露實現,都是在講如何在同一個方法中,做一組相同層次的語義操作,而不是暴露具體的實現。

如上所述,是一種重構方式,讀者可以參考 Martin Fowler 的《Refactoring Imporving the Design of Existing Code》(重構 改善既有代碼的設計) 這本書中的 Extract Method 重構方式。

抽象接口定義

當實際工作中,完成了幾個 API 的 DTO 轉化時,我們會發現,這樣的操作有很多很多,那么應該定義好一個接口,讓所有這樣的操作都有規則的進行。

如果接口被定義以后,那么 convertFor 這個方法的語義將產生變化,它將是一個實現類。

看一下抽象后的接口:

  1. public interface DTOConvert<S,T> { 
  2.  T convert(S s); 

雖然這個接口很簡單,但是這里告訴我們一個事情,要去使用泛型,如果你是一個優秀的 Java 程序員,請為你想做的抽象接口,做好泛型吧。

我們再來看接口實現:

  1. public class UserInputDTOConvert implements DTOConvert { 
  2. @Override 
  3. public User convert(UserInputDTO userInputDTO) { 
  4. User user = new User(); 
  5. BeanUtils.copyProperties(userInputDTO,user); 
  6. return user

我們這樣重構后,我們發現現在的代碼是如此的簡潔,并且那么的規范:

  1. @RequestMapping("/v1/api/user"
  2. @RestController 
  3. public class UserApi { 
  4.  @Autowired 
  5.  private UserService userService; 
  6.  @PostMapping 
  7.  public User addUser(UserInputDTO userInputDTO){ 
  8.  User user = new UserInputDTOConvert().convert(userInputDTO); 
  9.  return userService.addUser(user); 
  10.  } 

review code

如果你是一個優秀的 Java 程序員,我相信你應該和我一樣,已經數次重復 review 過自己的代碼很多次了。

我們再看這個保存用戶的例子,你將發現,API 中返回值是有些問題的,問題就在于不應該直接返回 User 實體,因為如果這樣的話,就暴露了太多實體相關的信息,這樣的返回值是不安全的,所以我們更應該返回一個 DTO 對象,我們可稱它為 UserOutputDTO:

  1. @PostMapping 
  2. public UserOutputDTO addUser(UserInputDTO userInputDTO){ 
  3.  User user = new UserInputDTOConvert().convert(userInputDTO); 
  4.  User saveUserResult = userService.addUser(user); 
  5.  UserOutputDTO result = new UserOutDTOConvert().convertToUser(saveUserResult); 
  6.  return result; 

這樣你的 API 才更健全。

不知道在看完這段代碼之后,讀者有是否發現還有其他問題的存在,作為一個優秀的 Java 程序員,請看一下這段我們剛剛抽象完的代碼:

  1. User user = new UserInputDTOConvert().convert(userInputDTO); 

你會發現,new 這樣一個 DTO 轉化對象是沒有必要的,而且每一個轉化對象都是由在遇到 DTO 轉化的時候才會出現,那我們應該考慮一下,是否可以將這個類和 DTO 進行聚合呢,看一下我的聚合結果:

  1. public class UserInputDTO { 
  2. private String username; 
  3. private int age; 
  4.  public String getUsername() { 
  5.  return username; 
  6.  } 
  7.  public void setUsername(String username) { 
  8.  this.username = username; 
  9.  } 
  10.  public int getAge() { 
  11.  return age; 
  12.  } 
  13.  public void setAge(int age) { 
  14.  this.age = age; 
  15.  } 
  16.  public User convertToUser(){ 
  17.  UserInputDTOConvert userInputDTOConvert = new UserInputDTOConvert(); 
  18.  User convert = userInputDTOConvert.convert(this); 
  19.  return convert
  20.  } 
  21.  private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> { 
  22.  @Override 
  23.  public User convert(UserInputDTO userInputDTO) { 
  24.  User user = new User(); 
  25.  BeanUtils.copyProperties(userInputDTO,user); 
  26.  return user
  27.  } 
  28.  } 

然后 API 中的轉化則由:

  1. User user = new UserInputDTOConvert().convert(userInputDTO); 
  2. User saveUserResult = userService.addUser(user); 

變成了:

  1. User user = userInputDTO.convertToUser(); 
  2. User saveUserResult = userService.addUser(user); 

我們再 DTO 對象中添加了轉化的行為,我相信這樣的操作可以讓代碼的可讀性變得更強,并且是符合語義的。

再查工具類

再來看 DTO 內部轉化的代碼,它實現了我們自己定義的 DTOConvert 接口,但是這樣真的就沒有問題,不需要再思考了嗎?

我覺得并不是,對于 Convert 這種轉化語義來講,很多工具類中都有這樣的定義,這中 Convert 并不是業務級別上的接口定義,它只是用于普通 bean 之間轉化屬性值的普通意義上的接口定義,所以我們應該更多的去讀其他含有 Convert 轉化語義的代碼。

我仔細閱讀了一下 GUAVA 的源碼,發現了 com.google.common.base.Convert 這樣的定義:

  1. public abstract class Converter<A, B> implements Function<A, B> { 
  2.  protected abstract B doForward(A a); 
  3.  protected abstract A doBackward(B b); 
  4.  //其他略 

從源碼可以了解到,GUAVA 中的 Convert 可以完成正向轉化和逆向轉化,繼續修改我們 DTO 中轉化的這段代碼:

  1. private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> { 
  2.  @Override 
  3.  public User convert(UserInputDTO userInputDTO) { 
  4.  User user = new User(); 
  5.  BeanUtils.copyProperties(userInputDTO,user); 
  6.  return user
  7.  } 

修改后:

  1. private static class UserInputDTOConvert extends Converter<UserInputDTO, User> { 
  2.  @Override 
  3.  protected User doForward(UserInputDTO userInputDTO) { 
  4.  User user = new User(); 
  5.  BeanUtils.copyProperties(userInputDTO,user); 
  6.  return user
  7.  } 
  8.  @Override 
  9.  protected UserInputDTO doBackward(User user) { 
  10.  UserInputDTO userInputDTO = new UserInputDTO(); 
  11.  BeanUtils.copyProperties(user,userInputDTO); 
  12.  return userInputDTO; 
  13.  } 
  14.  } 

看了這部分代碼以后,你可能會問,那逆向轉化會有什么用呢?其實我們有很多小的業務需求中,入參和出參是一樣的,那么我們變可以輕松的進行轉化,我將上邊所提到的 UserInputDTO 和 UserOutputDTO 都轉成 UserDTO 展示給大家。

DTO:

  1. public class UserDTO { 
  2.  private String username; 
  3.  private int age; 
  4.  public String getUsername() { 
  5.  return username; 
  6.  } 
  7.  public void setUsername(String username) { 
  8.  this.username = username; 
  9.  } 
  10.  public int getAge() { 
  11.  return age; 
  12.  } 
  13.  public void setAge(int age) { 
  14.  this.age = age; 
  15.  } 
  16.  public User convertToUser(){ 
  17.  UserDTOConvert userDTOConvert = new UserDTOConvert(); 
  18.  User convert = userDTOConvert.convert(this); 
  19.  return convert
  20.  } 
  21.  public UserDTO convertFor(User user){ 
  22.  UserDTOConvert userDTOConvert = new UserDTOConvert(); 
  23.  UserDTO convert = userDTOConvert.reverse().convert(user); 
  24.  return convert
  25.  } 
  26.  private static class UserDTOConvert extends Converter<UserDTO, User> { 
  27.  @Override 
  28.  protected User doForward(UserDTO userDTO) { 
  29.  User user = new User(); 
  30.  BeanUtils.copyProperties(userDTO,user); 
  31.  return user
  32.  } 
  33.  @Override 
  34.  protected UserDTO doBackward(User user) { 
  35.  UserDTO userDTO = new UserDTO(); 
  36.  BeanUtils.copyProperties(user,userDTO); 
  37.  return userDTO; 
  38.  } 
  39.  } 

API:

  1. @PostMapping 
  2.  public UserDTO addUser(UserDTO userDTO){ 
  3.  User user = userDTO.convertToUser(); 
  4.  User saveResultUser = userService.addUser(user); 
  5.  UserDTO result = userDTO.convertFor(saveResultUser); 
  6.  return result; 
  7.  } 

當然,上述只是表明了轉化方向的正向或逆向,很多業務需求的出參和入參的 DTO 對象是不同的,那么你需要更明顯的告訴程序:逆向是無法調用的:

  1. private static class UserDTOConvert extends Converter<UserDTO, User> { 
  2.  @Override 
  3.  protected User doForward(UserDTO userDTO) { 
  4.  User user = new User(); 
  5.  BeanUtils.copyProperties(userDTO,user); 
  6.  return user
  7.  } 
  8.  @Override 
  9.  protected UserDTO doBackward(User user) { 
  10.  throw new AssertionError("不支持逆向轉化方法!"); 
  11.  } 
  12.  } 

看一下 doBackward 方法,直接拋出了一個斷言異常,而不是業務異常,這段代碼告訴代碼的調用者,這個方法不是準你調用的,如果你調用,我就”斷言”你調用錯誤了。

關于異常處理的更詳細介紹,可以參考我之前的文章:如何優雅的設計 Java 異常(http://lrwinx.github.io/2016/04/28/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E8%AE%BE%E8%AE%A1java%E5%BC%82%E5%B8%B8/) ,應該可以幫你更好的理解異常。

bean 的驗證

如果你認為我上邊寫的那個添加用戶 API 寫的已經非常完美了,那只能說明你還不是一個優秀的程序員。我們應該保證任何數據的入參到方法體內都是合法的。

為什么要驗證

很多人會告訴我,如果這些 API 是提供給前端進行調用的,前端都會進行驗證啊,你為什還要驗證?

其實答案是這樣的,我從不相信任何調用我 API 或者方法的人,比如前端驗證失敗了,或者某些人通過一些特殊的渠道(比如 Charles 進行抓包),直接將數據傳入到我的 API,那我仍然進行正常的業務邏輯處理,那么就有可能產生臟數據!

“對于臟數據的產生一定是致命”,這句話希望大家牢記在心,再小的臟數據也有可能讓你找幾個通宵!

jsr 303驗證

hibernate 提供的 jsr 303 實現,我覺得目前仍然是很優秀的,具體如何使用,我不想講,因為谷歌上你可以搜索出很多答案!

再以上班的 API 實例進行說明,我們現在對 DTO 數據進行檢查:

  1. public class UserDTO { 
  2.  @NotNull 
  3.  private String username; 
  4.  @NotNull 
  5.  private int age; 
  6.  //其他代碼略 

API 驗證:

  1. @PostMapping 
  2.  public UserDTO addUser(@Valid UserDTO userDTO){ 
  3.  User user = userDTO.convertToUser(); 
  4.  User saveResultUser = userService.addUser(user); 
  5.  UserDTO result = userDTO.convertFor(saveResultUser); 
  6.  return result; 
  7.  } 

我們需要將驗證結果傳給前端,這種異常應該轉化為一個 api 異常(帶有錯誤碼的異常)。

  1. @PostMapping 
  2. public UserDTO addUser(@Valid UserDTO userDTO, BindingResult bindingResult){ 
  3.  checkDTOParams(bindingResult); 
  4.  User user = userDTO.convertToUser(); 
  5.  User saveResultUser = userService.addUser(user); 
  6.  UserDTO result = userDTO.convertFor(saveResultUser); 
  7.  return result; 
  8. private void checkDTOParams(BindingResult bindingResult){ 
  9.  if(bindingResult.hasErrors()){ 
  10.  //throw new 帶驗證碼的驗證錯誤異常 
  11.  } 

BindingResult 是 Spring MVC 驗證 DTO 后的一個結果集,可以參考spring 官方文檔(http://spring.io/)。

檢查參數后,可以拋出一個“帶驗證碼的驗證錯誤異常”,具體異常設計可以參考如何優雅的設計 Java 異常(http://lrwinx.github.io/2016/04/28/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E8%AE%BE%E8%AE%A1java%E5%BC%82%E5%B8%B8/)。

擁抱 lombok

上邊的 DTO 代碼,已經讓我看的很累了,我相信讀者也是一樣,看到那么多的 Getter 和 Setter 方法,太煩躁了,那時候有什么方法可以簡化這些呢。

請擁抱 lombok,它會幫助我們解決一些讓我們很煩躁的問題

去掉 Setter 和 Getter

其實這個標題,我不太想說,因為網上太多,但是因為很多人告訴我,他們根本就不知道 lombok 的存在,所以為了讓讀者更好的學習,我愿意寫這樣一個例子:

  1. @Setter 
  2. @Getter 
  3. public class UserDTO { 
  4.  @NotNull 
  5.  private String username; 
  6.  @NotNull 
  7.  private int age; 
  8.  public User convertToUser(){ 
  9.  UserDTOConvert userDTOConvert = new UserDTOConvert(); 
  10.  User convert = userDTOConvert.convert(this); 
  11.  return convert
  12.  } 
  13.  public UserDTO convertFor(User user){ 
  14.  UserDTOConvert userDTOConvert = new UserDTOConvert(); 
  15.  UserDTO convert = userDTOConvert.reverse().convert(user); 
  16.  return convert
  17.  } 
  18.  private static class UserDTOConvert extends Converter<UserDTO, User> { 
  19.  @Override 
  20.  protected User doForward(UserDTO userDTO) { 
  21.  User user = new User(); 
  22.  BeanUtils.copyProperties(userDTO,user); 
  23.  return user
  24.  } 
  25.  @Override 
  26.  protected UserDTO doBackward(User user) { 
  27.  throw new AssertionError("不支持逆向轉化方法!"); 
  28.  } 
  29.  } 

看到了吧,煩人的 Getter 和 Setter 方法已經去掉了。

但是上邊的例子根本不足以體現 lombok 的強大。我希望寫一些網上很難查到,或者很少人進行說明的 lombok 的使用以及在使用時程序語義上的說明。

比如:@Data,@AllArgsConstructor,@NoArgsConstructor..這些我就不進行一一說明了,請大家自行查詢資料。

bean 中的鏈式風格

什么是鏈式風格?我來舉個例子,看下面這個 Student 的 bean:

  1. public class Student { 
  2.  private String name
  3.  private int age; 
  4.  public String getName() { 
  5.  return name
  6.  } 
  7.  public Student setName(String name) { 
  8.  this.name = name
  9.  return this; 
  10.  } 
  11.  public int getAge() { 
  12.  return age; 
  13.  } 
  14.  public Student setAge(int age) { 
  15.  return this; 
  16.  } 

仔細看一下 set 方法,這樣的設置便是 chain 的 style,調用的時候,可以這樣使用:

  1. Student student = new Student() 
  2.  .setAge(24) 
  3.  .setName("zs"); 

相信合理使用這樣的鏈式代碼,會更多的程序帶來很好的可讀性,那看一下如果使用 lombok 進行改善呢,請使用 @Accessors(chain = true),看如下代碼:

  1. @Accessors(chain = true
  2. @Setter 
  3. @Getter 
  4. public class Student { 
  5.  private String name
  6.  private int age; 

這樣就完成了一個對于 bean 來講很友好的鏈式操作。

靜態構造方法

靜態構造方法的語義和簡化程度真的高于直接去 new 一個對象。比如 new 一個 List 對象,過去的使用是這樣的:

  1. List<String> list = new ArrayList<>(); 

看一下 guava 中的創建方式:

  1. List<String> list = Lists.newArrayList(); 

Lists 命名是一種約定(俗話說:約定優于配置),它是指 Lists 是 List 這個類的一個工具類,那么使用 List 的工具類去產生 List,這樣的語義是不是要比直接 new 一個子類來的更直接一些呢,答案是肯定的,再比如如果有一個工具類叫做 Maps,那你是否想到了創建 Map 的方法呢:

  1. HashMap<String, String> objectObjectHashMap = Maps.newHashMap(); 

好了,如果你理解了我說的語義,那么,你已經向成為 Java 程序員更近了一步了。

再回過頭來看剛剛的 Student,很多時候,我們去寫 Student 這個 bean 的時候,他會有一些必輸字段,比如 Student 中的 name 字段,一般處理的方式是將 name 字段包裝成一個構造方法,只有傳入 name 這樣的構造方法,才能創建一個 Student 對象。

接上上邊的靜態構造方法和必傳參數的構造方法,使用 lombok 將更改成如下寫法(@RequiredArgsConstructor 和 @NonNull):

  1. @Accessors(chain = true
  2. @Setter 
  3. @Getter 
  4. @RequiredArgsConstructor(staticName = "ofName"
  5. public class Student { 
  6.  @NonNull private String name
  7.  private int age; 

測試代碼:

  1. Student student = Student.ofName("zs"); 

這樣構建出的 bean 語義是否要比直接 new 一個含參的構造方法(包含 name 的構造方法)要好很多。

當然,看過很多源碼以后,我想相信將靜態構造方法 ofName 換成 of 會先的更加簡潔:

  1. @Accessors(chain = true
  2. @Setter 
  3. @Getter 
  4. @RequiredArgsConstructor(staticName = "of"
  5. public class Student { 
  6.  @NonNull private String name
  7.  private int age; 

測試代碼:

  1. Student student = Student.of("zs"); 

當然他仍然是支持鏈式調用的:

  1. Student student = Student.of("zs").setAge(24); 

這樣來寫代碼,真的很簡潔,并且可讀性很強。

使用 builder

Builder 模式我不想再多解釋了,讀者可以看一下《Head First》(設計模式) 的建造者模式。

今天其實要說的是一種變種的 builder 模式,那就是構建 bean 的 builder 模式,其實主要的思想是帶著大家一起看一下 lombok 給我們帶來了什么。

看一下 Student 這個類的原始 builder 狀態:

  1. public class Student { 
  2.  private String name
  3.  private int age; 
  4.  public String getName() { 
  5.  return name
  6.  } 
  7.  public void setName(String name) { 
  8.  this.name = name
  9.  } 
  10.  public int getAge() { 
  11.  return age; 
  12.  } 
  13.  public void setAge(int age) { 
  14.  this.age = age; 
  15.  } 
  16.  public static Builder builder(){ 
  17.  return new Builder(); 
  18.  } 
  19.  public static class Builder{ 
  20.  private String name
  21.  private int age; 
  22.  public Builder name(String name){ 
  23.  this.name = name
  24.  return this; 
  25.  } 
  26.  public Builder age(int age){ 
  27.  this.age = age; 
  28.  return this; 
  29.  } 
  30.  public Student build(){ 
  31.  Student student = new Student(); 
  32.  student.setAge(age); 
  33.  student.setName(name); 
  34.  return student; 
  35.  } 
  36.  } 

調用方式:

  1. Student student = Student.builder().name("zs").age(24).build(); 

這樣的 builder 代碼,讓我是在惡心難受,于是我打算用 lombok 重構這段代碼:

  1. @Builder 
  2. public class Student { 
  3.  private String name
  4.  private int age; 

調用方式:

  1. Student student = Student.builder().name("zs").age(24).build(); 

代理模式

正如我們所知的,在程序中調用 rest 接口是一個常見的行為動作,如果你和我一樣使用過 spring 的 RestTemplate,我相信你會我和一樣,對他拋出的非 http 狀態碼異常深惡痛絕。

所以我們考慮將 RestTemplate 最為底層包裝器進行包裝器模式的設計:

  1. public abstract class FilterRestTemplate implements RestOperations { 
  2.  protected volatile RestTemplate restTemplate; 
  3.  protected FilterRestTemplate(RestTemplate restTemplate){ 
  4.  this.restTemplate = restTemplate; 
  5.  } 
  6.  //實現RestOperations所有的接口 

然后再由擴展類對 FilterRestTemplate 進行包裝擴展:

  1. public class ExtractRestTemplate extends FilterRestTemplate { 
  2.  private RestTemplate restTemplate; 
  3.  public ExtractRestTemplate(RestTemplate restTemplate) { 
  4.  super(restTemplate); 
  5.  this.restTemplate = restTemplate; 
  6.  } 
  7.  public <T> RestResponseDTO<T> postForEntityWithNoException(String url, Object request, Class<T> responseType, Object... uriVariables) 
  8.  throws RestClientException { 
  9.  RestResponseDTO<T> restResponseDTO = new RestResponseDTO<T>(); 
  10.  ResponseEntity<T> tResponseEntity; 
  11.  try { 
  12.  tResponseEntity = restTemplate.postForEntity(url, request, responseType, uriVariables); 
  13.  restResponseDTO.setData(tResponseEntity.getBody()); 
  14.  restResponseDTO.setMessage(tResponseEntity.getStatusCode().name()); 
  15.  restResponseDTO.setStatusCode(tResponseEntity.getStatusCodeValue()); 
  16.  }catch (Exception e){ 
  17.  restResponseDTO.setStatusCode(RestResponseDTO.UNKNOWN_ERROR); 
  18.  restResponseDTO.setMessage(e.getMessage()); 
  19.  restResponseDTO.setData(null); 
  20.  } 
  21.  return restResponseDTO; 
  22.  } 

包裝器 ExtractRestTemplate 很完美的更改了異常拋出的行為,讓程序更具有容錯性。在這里我們不考慮 ExtractRestTemplate 完成的功能,讓我們把焦點放在 FilterRestTemplate 上,“實現 RestOperations 所有的接口”,這個操作絕對不是一時半會可以寫完的,當時在重構之前我幾乎寫了半個小時,如下:

  1. public abstract class FilterRestTemplate implements RestOperations { 
  2.  protected volatile RestTemplate restTemplate; 
  3.  protected FilterRestTemplate(RestTemplate restTemplate) { 
  4.  this.restTemplate = restTemplate; 
  5.  } 
  6.  @Override 
  7.  public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { 
  8.  return restTemplate.getForObject(url,responseType,uriVariables); 
  9.  } 
  10.  @Override 
  11.  public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { 
  12.  return restTemplate.getForObject(url,responseType,uriVariables); 
  13.  } 
  14.  @Override 
  15.  public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException { 
  16.  return restTemplate.getForObject(url,responseType); 
  17.  } 
  18.  @Override 
  19.  public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { 
  20.  return restTemplate.getForEntity(url,responseType,uriVariables); 
  21.  } 
  22.  //其他實現代碼略。。。 

我相信你看了以上代碼,你會和我一樣覺得惡心反胃,后來我用 lombok 提供的代理注解優化了我的代碼(@Delegate):

  1. @AllArgsConstructor 
  2. public abstract class FilterRestTemplate implements RestOperations { 
  3.  @Delegate 
  4.  protected volatile RestTemplate restTemplate; 

這幾行代碼完全替代上述那些冗長的代碼。

是不是很簡潔,做一個擁抱 lombok 的程序員吧。

重構

需求案例

項目需求

項目開發階段,有一個關于下單發貨的需求:如果今天下午 3 點前進行下單,那么發貨時間是明天,如果今天下午 3 點后進行下單,那么發貨時間是后天,如果被確定的時間是周日,那么在此時間上再加 1 天為發貨時間。

思考與重構

我相信這個需求看似很簡單,無論怎么寫都可以完成。

很多人可能看到這個需求,就動手開始寫 Calendar 或 Date 進行計算,從而完成需求。

而我給的建議是,仔細考慮如何寫代碼,然后再去寫,不是說所有的時間操作都用 Calendar 或 Date 去解決,一定要看場景。

對于時間的計算我們要考慮 joda-time 這種類似的成熟時間計算框架來寫代碼,它會讓代碼更加簡潔和易讀。

請讀者先考慮這個需求如何用 Java 代碼完成,或先寫一個你覺得完成這個代碼的思路,再來看我下邊的代碼,這樣,你的收獲會更多一些:

  1. final DateTime DISTRIBUTION_TIME_SPLIT_TIME = new DateTime().withTime(15,0,0,0); 
  2. private Date calculateDistributionTimeByOrderCreateTime(Date orderCreateTime){ 
  3.  DateTime orderCreateDateTime = new DateTime(orderCreateTime); 
  4.  Date tomorrow = orderCreateDateTime.plusDays(1).toDate(); 
  5.  Date theDayAfterTomorrow = orderCreateDateTime.plusDays(2).toDate(); 
  6.  return orderCreateDateTime.isAfter(DISTRIBUTION_TIME_SPLIT_TIME) ? wrapDistributionTime(theDayAfterTomorrow) : wrapDistributionTime(tomorrow); 
  7. private Date wrapDistributionTime(Date distributionTime){ 
  8.  DateTime currentDistributionDateTime = new DateTime(distributionTime); 
  9.  DateTime plusOneDay = currentDistributionDateTime.plusDays(1); 
  10.  boolean isSunday = (DateTimeConstants.SUNDAY == currentDistributionDateTime.getDayOfWeek()); 
  11.  return isSunday ? plusOneDay.toDate() : currentDistributionDateTime.toDate() ; 

讀這段代碼的時候,你會發現,我將判斷和有可能出現的不同結果都當做一個變量,最終做一個三目運算符的方式進行返回,這樣的優雅和可讀性顯而易見,當然這樣的代碼不是一蹴而就的,我優化了 3 遍產生的以上代碼。讀者可根據自己的代碼和我寫的代碼進行對比。

提高方法

如果你做了 3 年+的程序員,我相信像如上這樣的需求,你很輕松就能完成,但是如果你想做一個會寫 Java 的程序員,就好好的思考和重構代碼吧。

寫代碼就如同寫字一樣,同樣的字,大家都會寫,但是寫出來是否好看就不一定了。如果想把程序寫好,就要不斷的思考和重構,敢于嘗試,敢于創新,不要因循守舊,一定要做一個優秀的 Java 程序員。

提高代碼水平最好的方法就是有條理的重構!(注意:是有條理的重構)

設計模式

設計模式就是工具,而不是提現你是否是高水平程序員的一個指標。

我經常會看到某一個程序員興奮的大喊,哪個程序哪個點我用到了設計模式,寫的多么多么優秀,多么多么好。我仔細去翻閱的時候,卻發現有很多是過度設計的。

業務驅動技術 or 技術驅動業務

業務驅動技術 or 技術驅動業務 ? 其實這是一個一直在爭論的話題,但是很多人不這么認為,我覺得就是大家不愿意承認罷了。我來和大家大概分析一下作為一個 Java 程序員,我們應該如何判斷自己所處于的位置.

業務驅動技術:如果你所在的項目是一個收益很小或者甚至沒有收益的項目,請不要搞其他創新的東西,不要驅動業務要如何如何做,而是要熟知業務現在的痛點是什么?如何才能幫助業務盈利或者讓項目更好,更順利的進行。

技術驅動業務:如果你所在的項目是一個很牛的項目,比如淘寶這類的項目,我可以在滿足業務需求的情況下,和業務溝通,使用什么樣的技術能更好的幫助業務創造收益,比如說下單的時候要進隊列,可能幾分鐘之后訂單狀態才能處理完成,但是會讓用戶有更流暢的體驗,賺取更多的訪問流量,那么我相信業務愿意被技術驅動,會同意訂單的延遲問題,這樣便是技術驅動業務。

我相信大部分人還都處于業務驅動技術的方向吧。

所以你既然不能驅動業務,那就請擁抱業務變化吧。

代碼設計

一直在做 Java 后端的項目,經常會有一些變動,我相信大家也都遇到過。

比如當我們寫一段代碼的時候,我們考慮將需求映射成代碼的狀態模式,突然有一天,狀態模式里邊又添加了很多行為變化的東西,這時候你就撓頭了,你硬生生的將狀態模式中添加過多行為和變化。

慢慢的你會發現這些狀態模式,其實更像是一簇算法,應該使用策略模式,這時你應該已經暈頭轉向了。

說了這么多,我的意思是,只要你覺得合理,就請將狀態模式改為策略模式吧,所有的模式并不是憑空想象出來的,都是基于重構。

Java 編程中沒有銀彈,請擁抱業務變化,一直思考重構,你就有一個更好的代碼設計!

你真的優秀嗎?

真不好意思,我取了一個這么無聊的標題。

國外流行一種編程方式,叫做結對編程,我相信國內很多公司都沒有這么做,我就不在講述結對編程帶來的好處了,其實就是一邊 code review,一邊互相提高的一個過程。既然做不到這個,那如何讓自己活在自己的世界中不斷提高呢?

“平時開發的時候,做出的代碼總認為是正確的,而且寫法是完美的。”,我相信這是大部分人的心聲,還回到剛剛的問題,如何在自己的世界中不斷提高呢?

答案就是:

  1. 多看成熟框架的源碼
  2. 多回頭看自己的代碼
  3. 勤于重構

你真的優秀嗎? 如果你每周都完成了學習源碼,回頭看自己代碼,然后勤于重構,我認為你就真的很優秀了。

即使也許你只是剛剛入門,但是一直堅持,你就是一個真的會寫java代碼的程序員了。

技能

UML

不想多討論 UML 相關的知識,但是我覺得你如果真的會寫 Java,請先學會表達自己,UML 就是你說話的語言,做一名優秀的 Java 程序員,請至少學會這兩種 UML 圖:

  1. 類圖
  2. 時序圖

clean code

我認為保持代碼的簡潔和可讀性是代碼的最基本保證,如果有一天為了程序的效率而降低了這兩點,我認為是可以諒解的,除此之外,沒有任何理由可以讓你任意揮霍你的代碼。

讀者可以看一下 Robert C. Martin 出版的《Clean Code》(代碼整潔之道) 這本書

  1. 可以參考美團文章聊聊 clean code(http://tech.meituan.com/clean-code.html);
  2. 也可以看一下阿里的 Java 編碼規范(https://yq.aliyun.com/articles/69327?spm=5176.100239.topwz.1.om5dRN)。
  3. 無論如何,請保持你的代碼的整潔。

Linux 基礎命令

這點其實和會寫 Java 沒有關系,但是 Linux 很多時候確實承載運行 Java 的容器,請學好 Linux 的基礎命令。

責任編輯:未麗燕 來源: 今日頭條
相關推薦

2019-08-27 08:24:17

簡歷技能工作

2015-03-16 11:33:16

程序員代碼bug

2024-02-05 10:10:06

Vue策略編譯

2019-12-27 08:33:45

Java工具IDE

2016-04-20 11:08:57

代碼歷史新功能

2020-08-31 08:53:09

騰訊員工反思

2019-05-13 14:20:19

正則表達式JavaScript前端

2012-01-09 10:21:00

2015-09-16 09:51:47

2017-01-15 13:37:05

2017-01-15 11:38:24

2019-10-09 13:17:49

智能手機舊手機系統

2010-12-24 11:53:16

2013-01-07 11:30:40

CES手機展會

2021-04-05 18:10:04

網絡安全數據泄露Facebook

2018-08-29 18:53:44

2021-12-09 17:21:48

TypeScript TS 前端

2021-12-30 14:37:23

人工智能AI

2018-12-17 08:56:30

CPUAMDIntel

2014-11-11 14:52:28

程序員工程師
點贊
收藏

51CTO技術棧公眾號

中文字幕精品在线不卡| 日本精品黄色| 欧美日韩精品在线视频| 女同一区二区| 6—12呦国产精品| 亚洲激情久久| 精品视频在线播放色网色视频| 人人爽人人av| 在线中文字幕第一页| 99久久精品免费| 国产精品久久一区主播| 欧美黄色免费在线观看| 天堂一区二区三区四区| 欧美老女人在线| 日日橹狠狠爱欧美超碰| 欧美成年黄网站色视频| 91在线国产福利| 91免费在线视频| 波多野结衣网站| 欧美三级第一页| 色婷婷综合成人av| 四虎永久免费影院| 超碰成人在线观看| 91精品免费在线| 另类小说第一页| 岛国毛片av在线| 亚洲狼人国产精品| 亚洲精品在线免费看| 五月天福利视频| 国产黄色91视频| 国产欧美日韩精品专区| 亚洲伊人成人网| 伊人久久婷婷| 欧美日韩成人黄色| 黄色片子在线观看| 久久国产电影| 在线播放精品一区二区三区| 老牛影视av老牛影视av| 动漫3d精品一区二区三区乱码| 欧美日韩国产一级| 波多野结衣天堂| 亚洲a∨精品一区二区三区导航| 亚洲成a人片在线不卡一二三区| 少妇熟女一区二区| 亚洲成a人v欧美综合天堂麻豆| 久久久久久麻豆| 欧美日韩在线高清| 男人av在线| 91影院在线免费观看| 国产手机精品在线| 色婷婷在线视频| av成人老司机| 国内外成人免费视频| 丰满人妻一区二区三区无码av | 国产欧美日韩影院| 亚洲欧美日韩高清| 久久美女免费视频| 久久视频在线| 久久中文字幕在线视频| 午夜精品一区二区三区视频| 欧美一区在线看| 欧美福利视频在线| 日韩免费av片| 久久久久久自在自线| 国产成人精品午夜| 在线观看中文字幕码| 九九国产精品视频| 69174成人网| 日韩专区第一页| 337p粉嫩大胆色噜噜噜噜亚洲| 欧美高清视频一区| 99视频在线观看地址| 亚洲丝袜精品丝袜在线| 精品视频在线观看一区二区| 九色porny自拍视频在线观看| 懂色aⅴ精品一区二区三区蜜月| 大肉大捧一进一出好爽视频| 秋霞国产精品| 欧美成人a∨高清免费观看| 国产精品成人99一区无码 | 国产精品一级二级三级| 国产女主播一区二区| 欧洲亚洲在线| 中文字幕日韩av资源站| 久久亚洲国产成人精品无码区 | 欧美ab在线视频| 97久久精品国产| 中文字幕你懂的| 国产福利电影一区二区三区| 精品国产乱码久久久久久郑州公司| 美女毛片在线看| 综合av第一页| 欧美一级在线看| 韩国三级成人在线| 亚洲欧美激情一区| 国产一区二区视频在线观看免费| 99亚洲伊人久久精品影院红桃| 国产精品久久久久久久久久ktv| 成 人 黄 色 片 在线播放| 久久这里只精品最新地址| 国产91av视频在线观看| 国产99在线| 欧美肥胖老妇做爰| 亚洲专区区免费| 亚洲欧美文学| 国产成人综合av| 成人免费一级视频| 中文字幕日韩欧美一区二区三区| 国模无码视频一区二区三区| 国产激情一区| 亚洲人成自拍网站| 久久久精品国产sm调教| 毛片av一区二区| 精品在线观看一区二区| 蜜臀av在线| 欧美日本在线看| 国产精品毛片一区二区| 最新日韩在线| 成人av蜜桃| 黄网页免费在线观看| 欧美综合一区二区| 午夜一区二区三区免费| 一区二区视频欧美| 亚洲xxx自由成熟| 在线观看a视频| 在线看不卡av| 午夜一区二区三区免费| 亚洲精品国产日韩| 国产91亚洲精品一区二区三区| 午夜毛片在线| 欧美色老头old∨ideo| 国产精品九九视频| 黄色成人av网站| 99久久国产免费免费| 麻豆影视国产在线观看| 欧美性猛交一区二区三区精品| av网站免费在线播放| 国产日产高清欧美一区二区三区| 91麻豆蜜桃| 在线看一级片| 91精品国产欧美一区二区成人| 四虎地址8848| 久久国产综合精品| 在线不卡日本| 日本欧美在线| 欧美成人精品不卡视频在线观看| 亚洲天堂avav| 国产精品免费aⅴ片在线观看| 亚洲精品高清无码视频| 国产欧美日韩| 国产精品草莓在线免费观看| 成人在线免费看| 欧美亚洲高清一区二区三区不卡| 欧美熟妇激情一区二区三区| 日本女优在线视频一区二区| 亚洲国产精品一区在线观看不卡| 91国拍精品国产粉嫩亚洲一区| 在线精品视频视频中文字幕| 中文字幕人妻一区二区在线视频| 国产精品理伦片| 亚洲在线观看网站| 精品99视频| 久久久com| 四虎4545www精品视频| 综合av色偷偷网| 国产视频在线观看视频| 亚洲一线二线三线久久久| 国产精品手机在线观看| 久久精品国产清高在天天线| 天堂资源在线亚洲视频| 欧美亚洲人成在线| 欧美人与性动交a欧美精品| 囯产精品久久久久久| 欧美日韩亚洲一区二| 人人妻人人澡人人爽| 国产一区二区三区黄视频| 国产91视频一区| 亚洲视频分类| 国产伊人精品在线| 高清毛片在线观看| 色综合影院在线| 风流少妇一区二区三区91| 色www精品视频在线观看| 亚洲最大的黄色网址| av在线这里只有精品| 爱情岛论坛成人| 伊人成年综合电影网| 亚洲国产精品综合| 欧美毛片免费观看| 国产日本欧美视频| 男人av在线播放| 日韩中文视频免费在线观看| 日本高清视频www| 欧美日韩黄视频| 日韩欧美一级视频| 亚洲欧美另类综合偷拍| 精品少妇人妻一区二区黑料社区 | 成人精品久久久| 三级在线观看视频| 久热精品在线视频| 香蕉视频禁止18| 国产在线视频网站| 欧美一区二区三区在线电影| 草久久免费视频| 亚洲免费观看高清完整| 一区二区精品免费| 成人午夜在线播放| www.色就是色.com| 另类激情亚洲| 亚洲 自拍 另类小说综合图区| 区一区二视频| 日本福利一区二区三区| xvideos.蜜桃一区二区| 成人黄色av播放免费| 欧美大片高清| 欧美精品成人91久久久久久久| 不卡在线视频| 亚洲美女性视频| 女人18毛片水真多18精品| 在线不卡一区二区| 亚洲中文无码av在线| 婷婷成人综合网| 毛片aaaaa| 亚洲欧美精品午睡沙发| 国产农村妇女精品一区| 久久久久久久久久久电影| 天堂www中文在线资源| 国产精品2024| 在线观看中文av| 精品亚洲国产成人av制服丝袜| 日本精品一区二区三区四区| 亚洲每日在线| 丝袜人妻一区二区三区| 欧美/亚洲一区| 亚洲第一综合网站| 天天超碰亚洲| 椎名由奈jux491在线播放| 精品久久视频| 日韩精品久久一区二区三区| 米奇777超碰欧美日韩亚洲| 精品一区久久久久久| 欧美性生活一级片| 精品日韩美女| 日韩丝袜视频| 欧美精品一区二区三区四区五区| 群体交乱之放荡娇妻一区二区| 国产精品久久久久久久小唯西川 | 色是在线视频| 91精品国产乱码久久久久久蜜臀| 日本大片在线播放| 欧美国产视频一区二区| 怡红院av在线| 久久免费国产精品1| 国产天堂在线播放视频| 国产+成+人+亚洲欧洲| 国产一二在线播放| 欧洲日本亚洲国产区| 国产精品亚洲一区二区三区在线观看 | 欧美日韩国产高清一区二区三区 | 国产成人无码av| 色老汉av一区二区三区| 中文字幕一区二区三区人妻四季| 欧美日韩一区二区在线视频| 91精东传媒理伦片在线观看| 欧美一区二区在线看| 全部免费毛片在线播放一个| 日韩国产精品一区| 国产一区精品| 久久国产精品偷| 国产高清自产拍av在线| 日韩免费观看高清| 亚洲福利影视| 国产伦精品一区二区三区照片| 欧美人妖在线观看| 日韩欧美亚洲日产国| 亚洲国产精品日韩专区av有中文| 免费在线看黄色片| 另类av一区二区| 天天色天天干天天色| 成人动漫一区二区在线| 影音先锋制服丝袜| 国产精品乱码一区二区三区软件 | 成人3d动漫一区二区三区91| 日本在线中文字幕一区| 一本久道久久综合| 欧美午夜不卡| 日日噜噜夜夜狠狠| 国产91精品在线观看| av女人的天堂| 一区二区成人在线| 久操视频在线免费观看| 日韩一区二区三区三四区视频在线观看 | 好吊操这里只有精品| 欧美在线观看视频一区二区三区| 99在线观看免费| 亚洲天堂男人的天堂| 99热国产在线中文| 国产精品九九九| 老司机成人在线| 小说区视频区图片区| 国产精品免费看| 中文字幕久久久久久久| 久久网站最新地址| www欧美com| 欧洲视频一区二区| 婷婷久久久久久| 久久艹在线视频| 国产精品久久久久久久久久齐齐| 国产精品日本一区二区| 中文字幕一区二区av | 成人国产综合| 国内精品视频在线播放| 欧美区一区二| www.久久久久久久久久久| 久久噜噜亚洲综合| 国产系列精品av| 欧美一区二区三区免费| аⅴ资源新版在线天堂| 琪琪亚洲精品午夜在线| av毛片精品| wwwwww欧美| 国产高清亚洲一区| 老司机成人免费视频| 欧美网站一区二区| 国产亚洲依依| 欧洲亚洲妇女av| 欧美日韩一区二区三区四区不卡| 黄黄视频在线观看| 麻豆成人在线观看| jizz日本在线播放| 91成人免费网站| 黄色软件在线观看| 欧美在线欧美在线| 蜜乳av综合| 国产福利一区视频| 久久你懂得1024| 国产91精品一区| 精品一区二区三区四区| 黄色漫画在线免费看| 国产在线播放一区二区| 伊人影院久久| 人妻av一区二区| 午夜精品一区二区三区电影天堂 | 亚洲视频自拍偷拍| 三上悠亚一区二区| 日韩精品久久久| 精品综合免费视频观看| 影音先锋男人资源在线观看| 欧美日韩国产系列| a级影片在线观看| av成人午夜| aa国产精品| 久久丫精品国产亚洲av不卡| 色素色在线综合| 91这里只有精品| 成人做爰www免费看视频网站| 希岛爱理av一区二区三区| 日韩视频在线观看一区二区三区| 亚洲欧洲www| 亚洲精品福利网站| 91禁外国网站| 欧美综合在线视频观看| 国产无遮挡猛进猛出免费软件| 亚洲人成7777| 亚洲乱码精品久久久久..| 91国在线精品国内播放| 九九免费精品视频在线观看| 亚洲欧美自拍另类日韩| 亚洲人成精品久久久久| 天堂网在线观看视频| 国产97色在线| 婷婷激情综合| 中文字幕乱视频| 在线免费观看日韩欧美| 超碰超碰在线| 九九九九九精品| 美国毛片一区二区| 国产小视频在线观看免费| 亚洲精品之草原avav久久| 黄色精品视频网站| 精品一二三四五区| 久久精品亚洲乱码伦伦中文| 国产精品怡红院| 97在线视频观看| 日韩在线综合| 熟女人妻在线视频| 欧美日韩中文字幕一区二区| 欧美极品少妇videossex| 欧美凹凸一区二区三区视频| 激情欧美一区二区| 亚洲精品1区2区3区| 日韩一区av在线| 欧美韩一区二区| 亚欧激情乱码久久久久久久久| 亚洲一区二区三区小说| 超碰免费97在线观看| 精品一区二区日本| 国产一区二区三区精品视频| 青青视频在线免费观看| 欧美另类第一页|