本文不是一個吹噓的文章,不會講不少高深的架構,相反,會講解不少基礎的問題和寫法問題,若是讀者自認爲基礎問題和寫法問題都是否是問題,那請忽略這篇文章,節省出時間去作一些有意義的事情。前端
不知道有多少」老」程序員還在使用 Eclipse,這些程序員們要不就是因循守舊,要不就是根本就不知道其餘好的開發工具的存在,Eclipse 吃內存卡頓的現象以及各類偶然莫名異常的出現,都告知咱們是時候尋找新的開發工具了。java
根本就不想多解釋要換什麼樣的 IDE,若是你想成爲一個優秀的 Java 程序員,請更換 IntelliJ IDEA。使用 IDEA 的好處,請搜索谷歌。程序員
更換 IDE 不在我本文的重點內容中,因此不想用太多的篇幅去寫爲何更換IDE。在這裏,我只能告訴你,更換 IDE 只爲了更好、更快的寫好 Java 代碼。緣由略。面試
別告訴我快捷鍵很差用,請嘗試新事物。算法
bean 使咱們使用最多的模型之一,我將以大篇幅去講解 bean,但願讀者好好體會。spring
根據不少 Java 程序員的」經驗」來看,一個數據庫表則對應着一個 domain 對象,因此不少程序員在寫代碼時,包名則使用:com.xxx.domain ,這樣寫好像已經成爲了行業的一種約束,數據庫映射對象就應該是 domain。可是你錯了,domain 是一個領域對象,每每咱們再作傳統 Java 軟件 Web 開發中,這些 domain 都是貧血模型,是沒有行爲的,或是沒有足夠的領域模型的行爲的,因此,以這個理論來說,這些 domain 都應該是一個普通的 entity 對象,並不是領域對象,因此請把包名改成:com.xxx.entity。數據庫
若是你還不理解我說的話,請看一下 Vaughn Vernon 出的一本叫作《IMPLEMENTING DOMAIN-DRIVEN DESIGN》(實現領域驅動設計)這本書,書中講解了貧血模型與領域模型的區別,相信你會受益不淺。編程
數據傳輸咱們應該使用 DTO 對象做爲傳輸對象,這是咱們所約定的,由於很長時間我一直都在作移動端 API 設計的工做,有不少人告訴我,他們認爲只有給手機端傳輸數據的時候(input or output),這些對象成爲 DTO 對象。請注意!這種理解是錯誤的,只要是用於網絡傳輸的對象,咱們都認爲他們能夠當作是 DTO 對象,好比電商平臺中,用戶進行下單,下單後的數據,訂單會發到 OMS 或者 ERP 系統,這些對接的返回值以及入參也叫 DTO 對象。後端
咱們約定某對象若是是 DTO 對象,就將名稱改成 XXDTO,好比訂單下發OMS:OMSOrderInputDTO。設計模式
正如咱們所知,DTO 爲系統與外界交互的模型對象,那麼確定會有一個步驟是將 DTO 對象轉化爲 BO 對象或者是普通的 entity 對象,讓 service 層去處理。
好比添加會員操做,因爲用於演示,我只考慮用戶的一些簡單數據,當後臺管理員點擊添加用戶時,只須要傳過來用戶的姓名和年齡就能夠了,後端接受到數據後,將添加建立時間和更新時間和默認密碼三個字段,而後保存數據庫。
@RequestMapping("/v1/api/user")
@RestController
public class UserApi {
@Autowired
private UserService userService;
@PostMapping
public User addUser(UserInputDTO userInputDTO){
User user = new User();
user.setUsername(userInputDTO.getUsername());
user.setAge(userInputDTO.getAge());
return userService.addUser(user);
}
}
複製代碼
咱們只關注一下上述代碼中的轉化代碼,其餘內容請忽略:
User user = new User();
user.setUsername(userInputDTO.getUsername());
user.setAge(userInputDTO.getAge());
複製代碼
上邊的代碼,從邏輯上講,是沒有問題的,只是這種寫法讓我很厭煩,例子中只有兩個字段,若是有 20 個字段,咱們要如何作呢? 一個一個進行 set 數據嗎?固然,若是你這麼作了,確定不會有什麼問題,可是,這確定不是一個最優的作法。
網上有不少工具,支持淺拷貝或深拷貝的 Utils。舉個例子,咱們可使用 org.springframework.beans.BeanUtils#copyProperties 對代碼進行重構和優化:
@PostMapping
public User addUser(UserInputDTO userInputDTO){
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return userService.addUser(user);
}
複製代碼
BeanUtils.copyProperties 是一個淺拷貝方法,複製屬性時,咱們只須要把 DTO 對象和要轉化的對象兩個的屬性值設置爲同樣的名稱,而且保證同樣的類型就能夠了。若是你在作 DTO 轉化的時候一直使用 set 進行屬性賦值,那麼請嘗試這種方式簡化代碼,讓代碼更加清晰!
上邊的轉化過程,讀者看後確定以爲優雅不少,可是咱們再寫 Java 代碼時,更多的須要考慮語義的操做,再看上邊的代碼:
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
複製代碼
雖然這段代碼很好的簡化和優化了代碼,可是他的語義是有問題的,咱們須要提現一個轉化過程纔好,因此代碼改爲以下:
@PostMapping
public User addUser(UserInputDTO userInputDTO){
User user = convertFor(userInputDTO);
return userService.addUser(user);
}
private User convertFor(UserInputDTO userInputDTO){
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
複製代碼
這是一個更好的語義寫法,雖然他麻煩了些,可是可讀性大大增長了,在寫代碼時,咱們應該儘可能把語義層次差很少的放到一個方法中,好比:
User user = convertFor(userInputDTO);
return userService.addUser(user);
複製代碼
這兩段代碼都沒有暴露實現,都是在講如何在同一個方法中,作一組相同層次的語義操做,而不是暴露具體的實現。
如上所述,是一種重構方式,讀者能夠參考 Martin Fowler 的《Refactoring Imporving the Design of Existing Code》(重構 改善既有代碼的設計) 這本書中的 Extract Method 重構方式。
當實際工做中,完成了幾個 API 的 DTO 轉化時,咱們會發現,這樣的操做有不少不少,那麼應該定義好一個接口,讓全部這樣的操做都有規則的進行。
若是接口被定義之後,那麼 convertFor 這個方法的語義將產生變化,它將是一個實現類。
看一下抽象後的接口:
public interface DTOConvert<S,T> {
T convert(S s);
}
複製代碼
雖然這個接口很簡單,可是這裏告訴咱們一個事情,要去使用泛型,若是你是一個優秀的 Java 程序員,請爲你想作的抽象接口,作好泛型吧。
咱們再來看接口實現:
public class UserInputDTOConvert implements DTOConvert {
@Override
public User convert(UserInputDTO userInputDTO) {
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
}
複製代碼
咱們這樣重構後,咱們發現如今的代碼是如此的簡潔,而且那麼的規範:
@RequestMapping("/v1/api/user")
@RestController
public class UserApi {
@Autowired
private UserService userService;
@PostMapping
public User addUser(UserInputDTO userInputDTO){
User user = new UserInputDTOConvert().convert(userInputDTO);
return userService.addUser(user);
}
}
複製代碼
若是你是一個優秀的 Java 程序員,我相信你應該和我同樣,已經數次重複 review 過本身的代碼不少次了。
咱們再看這個保存用戶的例子,你將發現,API 中返回值是有些問題的,問題就在於不該該直接返回 User 實體,由於若是這樣的話,就暴露了太多實體相關的信息,這樣的返回值是不安全的,因此咱們更應該返回一個 DTO 對象,咱們可稱它爲 UserOutputDTO:
@PostMapping
public UserOutputDTO addUser(UserInputDTO userInputDTO){
User user = new UserInputDTOConvert().convert(userInputDTO);
User saveUserResult = userService.addUser(user);
UserOutputDTO result = new UserOutDTOConvert().convertToUser(saveUserResult);
return result;
}
複製代碼
這樣你的 API 才更健全。
不知道在看完這段代碼以後,讀者有是否發現還有其餘問題的存在,做爲一個優秀的 Java 程序員,請看一下這段咱們剛剛抽象完的代碼:
User user = new UserInputDTOConvert().convert(userInputDTO);
複製代碼
你會發現,new 這樣一個 DTO 轉化對象是沒有必要的,並且每個轉化對象都是由在遇到 DTO 轉化的時候纔會出現,那咱們應該考慮一下,是否能夠將這個類和 DTO 進行聚合呢,看一下個人聚合結果:
public class UserInputDTO {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User convertToUser(){
UserInputDTOConvert userInputDTOConvert = new UserInputDTOConvert();
User convert = userInputDTOConvert.convert(this);
return convert;
}
private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> {
@Override
public User convert(UserInputDTO userInputDTO) {
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
}
}
複製代碼
而後 API 中的轉化則由:
User user = new UserInputDTOConvert().convert(userInputDTO);
User saveUserResult = userService.addUser(user);
複製代碼
變成了:
User user = userInputDTO.convertToUser();
User saveUserResult = userService.addUser(user);
複製代碼
咱們再 DTO 對象中添加了轉化的行爲,我相信這樣的操做可讓代碼的可讀性變得更強,而且是符合語義的。
再來看 DTO 內部轉化的代碼,它實現了咱們本身定義的 DTOConvert 接口,可是這樣真的就沒有問題,不須要再思考了嗎?
我以爲並非,對於 Convert 這種轉化語義來說,不少工具類中都有這樣的定義,這中 Convert 並非業務級別上的接口定義,它只是用於普通 bean 之間轉化屬性值的普通意義上的接口定義,因此咱們應該更多的去讀其餘含有 Convert 轉化語義的代碼。
我仔細閱讀了一下 GUAVA 的源碼,發現了 com.google.common.base.Convert 這樣的定義:
public abstract class Converter<A, B> implements Function<A, B> {
protected abstract B doForward(A a);
protected abstract A doBackward(B b);
//其餘略
}
複製代碼
從源碼能夠了解到,GUAVA 中的 Convert 能夠完成正向轉化和逆向轉化,繼續修改咱們 DTO 中轉化的這段代碼:
private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> {
@Override
public User convert(UserInputDTO userInputDTO) {
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
}
複製代碼
修改後:
private static class UserInputDTOConvert extends Converter<UserInputDTO, User> {
@Override
protected User doForward(UserInputDTO userInputDTO) {
User user = new User();
BeanUtils.copyProperties(userInputDTO,user);
return user;
}
@Override
protected UserInputDTO doBackward(User user) {
UserInputDTO userInputDTO = new UserInputDTO();
BeanUtils.copyProperties(user,userInputDTO);
return userInputDTO;
}
}
複製代碼
看了這部分代碼之後,你可能會問,那逆向轉化會有什麼用呢?其實咱們有不少小的業務需求中,入參和出參是同樣的,那麼咱們變能夠輕鬆的進行轉化,我將上邊所提到的 UserInputDTO 和 UserOutputDTO 都轉成 UserDTO 展現給你們。
**DTO:**
public class UserDTO {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = 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) {
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user,userDTO);
return userDTO;
}
}
}
複製代碼
API:
@PostMapping
public UserDTO addUser(UserDTO userDTO){
User user = userDTO.convertToUser();
User saveResultUser = userService.addUser(user);
UserDTO result = userDTO.convertFor(saveResultUser);
return result;
}
複製代碼
固然,上述只是代表了轉化方向的正向或逆向,不少業務需求的出參和入參的 DTO 對象是不一樣的,那麼你須要更明顯的告訴程序:逆向是沒法調用的:
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("不支持逆向轉化方法!");
}
}
複製代碼
看一下 doBackward 方法,直接拋出了一個斷言異常,而不是業務異常,這段代碼告訴代碼的調用者,這個方法不是準你調用的,若是你調用,我就」斷言」你調用錯誤了。
說到這裏順便給你們推薦一個Java架構方面的交流學習羣:867923845,點擊當即加入裏面會免費分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系。還能領取免費的學習資源和前輩的面試經驗和麪試題,相信對於已經工做和遇到技術瓶頸的碼友,在這個羣裏會有你須要的內容。
若是你認爲我上邊寫的那個添加用戶 API 寫的已經很是完美了,那隻能說明你還不是一個優秀的程序員。咱們應該保證任何數據的入參到方法體內都是合法的。
不少人會告訴我,若是這些 API 是提供給前端進行調用的,前端都會進行驗證啊,你爲什還要驗證?
其實答案是這樣的,我從不相信任何調用我 API 或者方法的人,好比前端驗證失敗了,或者某些人經過一些特殊的渠道(好比 Charles 進行抓包),直接將數據傳入到個人 API,那我仍然進行正常的業務邏輯處理,那麼就有可能產生髒數據!
「對於髒數據的產生必定是致命」,這句話但願你們牢記在心,再小的髒數據也有可能讓你找幾個通宵!
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,它會幫助咱們解決一些讓咱們很煩躁的問題
其實這個標題,我不太想說,由於網上太多,可是由於不少人告訴我,他們根本就不知道 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..這些我就不進行一一說明了,請你們自行查詢資料。
什麼是鏈式風格?我來舉個例子,看下面這個 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 模式我不想再多解釋了,讀者能夠看一下《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;
}
複製代碼
這幾行代碼徹底替代上述那些冗長的代碼。
是否是很簡潔,作一個擁抱 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 技術驅動業務 ? 其實這是一個一直在爭論的話題,可是不少人不這麼認爲,我以爲就是你們不肯意認可罷了。我來和你們大概分析一下做爲一個 Java 程序員,咱們應該如何判斷本身所處於的位置.
業務驅動技術:若是你所在的項目是一個收益很小或者甚至沒有收益的項目,請不要搞其餘創新的東西,不要驅動業務要如何如何作,而是要熟知業務如今的痛點是什麼?如何才能幫助業務盈利或者讓項目更好,更順利的進行。
技術驅動業務:若是你所在的項目是一個很牛的項目,好比淘寶這類的項目,我能夠在知足業務需求的狀況下,和業務溝通,使用什麼樣的技術能更好的幫助業務創造收益,好比說下單的時候要進隊列,可能幾分鐘以後訂單狀態才能處理完成,可是會讓用戶有更流暢的體驗,賺取更多的訪問流量,那麼我相信業務願意被技術驅動,會贊成訂單的延遲問題,這樣即是技術驅動業務。
我相信大部分人還都處於業務驅動技術的方向吧。
因此你既然不能驅動業務,那就請擁抱業務變化吧。
一直在作 Java 後端的項目,常常會有一些變更,我相信你們也都遇到過。
好比當咱們寫一段代碼的時候,咱們考慮將需求映射成代碼的狀態模式,忽然有一天,狀態模式裏邊又添加了不少行爲變化的東西,這時候你就撓頭了,你硬生生的將狀態模式中添加過多行爲和變化。
慢慢的你會發現這些狀態模式,其實更像是一簇算法,應該使用策略模式,這時你應該已經暈頭轉向了。
說了這麼多,個人意思是,只要你以爲合理,就請將狀態模式改成策略模式吧,全部的模式並非憑空想象出來的,都是基於重構。
Java 編程中沒有銀彈,請擁抱業務變化,一直思考重構,你就有一個更好的代碼設計!
真很差意思,我取了一個這麼無聊的標題。
國外流行一種編程方式,叫作結對編程,我相信國內不少公司都沒有這麼作,我就不在講述結對編程帶來的好處了,其實就是一邊 code review,一邊互相提升的一個過程。既然作不到這個,那如何讓本身活在本身的世界中不斷提升呢?
「平時開發的時候,作出的代碼總認爲是正確的,並且寫法是完美的。」,我相信這是大部分人的心聲,還回到剛剛的問題,如何在本身的世界中不斷提升呢?
答案就是:
即便也許你只是剛剛入門,可是一直堅持,你就是一個真的會寫java代碼的程序員了。