➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
Java 開發筆記(六)Java 開發筆記(四)

Java 開發筆記(五)

Continued from previous post:
Java 開發筆記(四)

Table of contents

5 幾款超有用必學 3rd party libraries

今時今日以下幾款 3rd party libraries 已經被廣泛使用,亦係好成熟:
Library 名用途
Lombok可以生成 Java code,令我地寫少啲重複既 boilerplate code。
Apache Commons Lang處理 string。
Apache Commons IO處理檔案。
Google Guava多功能,可以處理 string、collection、thread pool 等等。
Jackson DatabindSerialization / deserialization(object-to-string / string-to-object),喺 web programming 通常用黎處理 JSON 格式既 request body 或者 response body。
JUnitUnit testing 單元測試用。
MockitoUnit testing 單元測試用(輔助)。
仲有好幾隻 Apache libraries 都有特長,例如:
  • 要進行基本既文件操作,如寫入純文字檔、讀取純文字檔、遞歸列出目錄文件等等,可以用 Apache Commons IO
  • 要處理 Base64 既 encoding 可以用 Apache Commons Codec
  • 要發起 HTTP call 可以用 Apache HttpClient Fluent API
註:spring-boot-starter-parent 有對以上部分 libraries 提供 dependency 版本管理, 所以某啲 dependency 寫左落 pom.xml 可以省卻 <version> tag。
Spring 係 framework(較大)而唔算 library(較細小),佢直頭改變左成個 application 既寫法。

5.1 Lombok

由於 Java VO/DTO/entity classes 既 getters、setters 太 boilerplate,而絕大多數情況下,佢地全部都由 IDE 根據 properties 黎 gen 出黎, 根本自己寫黎多舊魚,亦對 version control 係多左一丁點負擔(要用肉眼對啲 code)。
所以就有左 Lombok 呢個 annotation-based 既 compile-time processor。佢會喺 compile time 階段 gen code。 Gen 左出黎既 bytecode,可以用 IDE 或者 decompiler 去睇下有冇 gen 錯 access modifiers、methods 既數量同名稱等等。
唔用 Lombok:
1public class Foo { 2 private String name; 3 private Date dateOfBirth; 4 5 public String getName() { 6 return name; 7 } 8 9 public void setName(String name) { 10 this.name = name; 11 } 12 13 public Date getDateOfBirth() { 14 return dateOfBirth; 15 } 16 17 public void setDateOfBirth(Date dateOfBirth) { 18 this.dateOfBirth = dateOfBirth; 19 } 20}
用左 Lombok:
1@Getter 2@Setter 3@FieldDefaults(level = PRIVATE) 4public class Foo { 5 String name; 6 Date dateOfBirth; 7}

5.1.1 喺 Eclipse 使用 Lombok

想喺 Eclipse 用 Lombok annotations 而 compile 到無問題既話,必須額外安裝 Lombok plugin。
  1. Project Lombok 度下載 Lombok 既 plugin JAR file
  2. Double click 個 JAR,會顯示一個介面
  3. 揀返你個 Eclipse 既 directory
  4. Restart Eclipse

5.2 Jackson Databind

用黎將某個 class 既 object 轉成 string(默認為 JSON format),呢個過程叫 serialization;調返轉就係 deserialization。
Spring Boot 都有用 Jackson Databind 黎做 serialization 同 deserialization。
  1. 先係 deserialization,就係當我地用 POST HTTP API request 去訪問 Spring Boot 既 back-end 時,POST request body 係 JSON 格式, 當 Spring Boot 收到既時候就會將個 JSON 轉成 Java objects,然後進入 controller
  2. 之後係 serialization,就係 Spring Boot 做完曬啲 business logic,controller 返回結果之後, 會轉成 JSON 格式去作為 response body(不限於 POST request)

5.2.1 用 ObjectMapper 黎做 serialization

1public class ObjectMapperSerializationExample { 2 3 public static void main(String[] args) throws Exception { 4 final Foo foo = new Foo("Michael"); 5 System.out.println(convertToString(foo)); // 出 {"name":"Michael"} 6 } 7 8 private static String convertToString(Foo foo) throws Exception { 9 return new ObjectMapper().writeValueAsString(foo); 10 } 11} 12 13class Foo { 14 private String name; 15 16 public Foo(String name) { 17 setName(name); 18 } 19 20 public String getName() { 21 return name; 22 } 23 24 public void setName(String name) { 25 this.name = name; 26 } 27}

5.2.2 用 ObjectMapper 黎做 deserialization

1public class ObjectMapperDeserializationExample { 2 3 public static void main(String[] args) throws Exception { 4 final String json = "{ \"name\": \"Michael\"}"; 5 System.out.println(convertToBar(json).getName()); // 出 Michael 6 } 7 8 private static Bar convertToBar(String barJson) throws Exception { 9 return new ObjectMapper().readValue(barJson, Bar.class); 10 } 11} 12 13class Bar { 14 private String name; 15 16 public String getName() { 17 return name; 18 } 19 20 public void setName(String name) { 21 this.name = name; 22 } 23}

5.3 Apache Commons Lang

呢個 library 提供左 JDK 非常缺乏既 string manipulation methods。用呢啲 methods 我地可以寫少好多 code。 個 API 設計初衷係想消滅 NullPointerException,所以佢既一個好大既賣點就係接受 null arguments。 亦因為係 static methods,佢地唔會好似 instance methods 咁,因為個 string variable null 左而當你一 call method 就 NullPointerException
JDK 既 str.toLowerCase() vs Commons Lang3 既 StringUtils.lowerCase(str), 前者有可能因為 strnull 而爆 NullPointerException;相反,後者容錯。
要輕鬆處理 string 可以用 StringUtils,要處理數字可以用 NumberUtils。 呢啲 utility classes 不需要 new,只需要直接 call 佢地既 static methods 就可以用。
其中較有用既 StringUtils 有以下功能:
Method用途
trim(str)str 既頭同尾空白字元刪走
strip(str)str 既頭同尾空白字元刪走
stripToNull(str)如果 strnull、empty 或 blank,返回 null
leftPad(str, size, padStr)str 既頭部填充 padStr 直至 str 既長度達到 size
rightPad(str, size, padStr)str 既尾部填充 padStr 直至 str 既長度達到 size
isEmpty(str) / isNotEmpty(str)check strnull 或 empty
isBlank(str) / isNotBlank(str)check strnull 或 blank
isAnyEmpty(str1, str2, ...)check 下有冇任何一個 string 係 null 或 empty
isAnyBlank(str1, str2, ...)check 下有冇任何一個 string 係 null 或 blank
defaultIfBlank(str, defaultValue)如果 strnull 或 blank,返回 defaultValue
join(collection, separator)collection 既 elements 串連起成一個 string,每個 element 之間用 separator 分隔

5.4 Google Guava

功能非常豐富,參考:官方 GitHub
有針對 collection data type 既 utility classes:
  • Lists
  • Sets
  • Maps
  • ImmutableList
  • ImmutableSet
  • ImmutableMap

5.5 JUnit 4 或 5

JUnit 係最多人用既 Java unit testing library。
使用:
  • src/test/java 度 create 一個 package,裡面再 create 一個 class
  • 加一個 public void test() {} 既 method,annotate with @Test
  • Method body 打啲 production code,例如你 test 一個 utility class,就用佢既某個 method(你要 test 既果個 method)
  • 之後 call Assert.assertEqualsAssert.assertTrueAssert.assertFalseAssert.assertNullAssert.assertNotNull 黎 compare expected value(你預期既結果)同 actual value(你要 test 既果個 method 實際返回既結果)
例子:
1public class ConfigUtilsTest { 2 3 @Test 4 public void testConfig() { 5 final Config config = ConfigUtils.getConfigData(); 6 Assert.assertNotNull(config); 7 Assert.assertEquals("1.0.0", config.getVersion()); 8 } 9}
只需要喺 IDE 用 JUnit 4 或 5 黎運行呢個 class 就可以見到 JUnit 既 view。綠燈代表 pass;紅燈代表有 test fail 左。
如果係 Maven project 既話,當你行部分 Maven 既 commands,佢會默認咁同我地行 test,而 Maven 係根據 Java class 個名黎睇邊啲係 test class。 所以我地命名所有 test classes 時,都要 Test 字尾。

5.6 Mockito

幻想一個情境,我地寫左一個 service class,呢個 class 充滿 business logic,而啲 business logic 都係都係基於佢既 dependencies 所返回既結果。 我地個 service class 同呢啲 dependencies 係(has-a/composition 關係)。 呢啲 dependencies 最後係會連接 DB 行 SQL 去拎 data,再返回畀個 service class 去通過一連串 business logic 運算而得出結果。
我地想寫一個 unit test 去淨係 test 呢個 class 既 business logic,但如果要連接 DB 先行得到個 test 就變左係 integration test,壞處係時間會長。
為左簡化 dependencies 背後會做既野,我地就用直接假設呢啲 dependencies 會返回我地想佢地返回既結果,從而去單獨地測試呢個 service class。

5.6.1 Mockito 例子

1/** 2 * src/main/java/code/repo/SalesRepository.java 3 */ 4@Repository 5public interface SalesRepository extends JpaRepository<Sales, Long> { 6 7 List<Sales> findAll(); 8} 9 10/** 11 * src/main/java/code/service/SalesService.java 12 */ 13@Service 14public class SalesService { 15 16 @Autowired 17 SalesRepository salesRepository; 18 19 public BigDecimal calculateTotal() { 20 return salesRepository.findAll() 21 .stream() 22 .map(Sales::getAmount) 23 .reduce(BigDecimal.ZERO, BigDecimal::add); 24 } 25} 26 27/** 28 * src/test/java/code/SalesServiceTest.java 29 */ 30public class SalesServiceTest { 31 32 @InjectMocks 33 SalesService salesService; 34 35 @Mock 36 SalesRepository salesRepository; 37 38 @Before 39 public void setUp() { 40 41 // Mockito 會將 @Mock 既值植入去 @InjectMocks 既 object 裡面 42 43// MockitoAnnotations.initMocks(this); // 舊式寫法 44 MockitoAnnotations.openMocks(this).close(); // 新式寫法 45 } 46 47 @Test 48 public void testSalesTotal() { 49 50 final Sales sales1 = new Sales(); 51 sales1.setAmount(new BigDecimal("400")); 52 53 final Sales sales2 = new Sales(); 54 sales2.setAmount(new BigDecimal("600")); 55 56 Mockito.when(salesRepository.findAll()).thenReturn(Arrays.asList(sales1, sales2)); 57 58 final BigDecimal salesTotal = salesService.calculateTotal(); 59 60 Assert.assertEquals(new BigDecimal("1000"), salesTotal); 61 } 62}