Skip to content

บทที่ 14 — Annotations, Modules, Build Tools, Javadoc

← บทที่ 13 | สารบัญ | บทที่ 15 →

📓 โซนอ้างอิง—เปิดตอนต้องใช้ (บท 11-19) บทนี้อยู่ในโซนที่ 2 มือใหม่ข้ามไปก่อนได้ — แต่ส่วน "Build Tools" (Maven/Gradle) จะได้ใช้ค่อนข้างเร็วตอนเริ่มโปรเจกต์จริง/ขึ้น Spring Boot ส่วน Modules (JPMS) กับการเขียน annotation เองนั้นลึกและเจอไม่บ่อย — ข้ามได้สบาย ๆ

บทนี้รวม "เครื่องมือรอบ ๆ ภาษา" ที่ใช้ในทุกโปรเจกต์จริง:

  • Annotations (แอนโนเทชัน = ป้ายกำกับบนโค้ดที่ compiler/framework/เครื่องมือต่าง ๆ อ่านได้) — อ่าน + เขียนเอง
  • Modules — Java 9+ module system (JPMS = Java Platform Module System = ระบบแบ่งโค้ดเป็นโมดูลของ Java)
  • Build Tools (เครื่องมือ build = ตัวรวบรวม/คอมไพล์/จัดการ library ของโปรเจกต์) — Maven vs Gradle
  • Javadoc — เขียน document ในโค้ด

ศัพท์ที่จะเจอบ่อยในบทนี้: metadata (ข้อมูลกำกับข้อมูลอีกที — ข้อมูลที่บอก "ข้อมูลนี้คืออะไร"), annotation processor (ตัวประมวลผล annotation ตอน compile เพื่อสร้างโค้ด/ตรวจสอบ), dependency (ดีเพนเดนซี = library ที่โปรเจกต์เราพึ่งพา), artifact (ผลผลิตที่ build ออกมา เช่นไฟล์ .jar), Maven/Gradle (เครื่องมือ build ยอดนิยม 2 ตัวของ Java)


Part 1: Annotations

1. Annotation คืออะไร

Annotation = "metadata" บนโค้ด — ส่วนใหญ่ไม่เปลี่ยน logic การทำงาน แค่เป็นป้ายให้ tool/framework เอาไปอ่านต่อ

ข้อยกเว้นคือ annotation processor บางตัวที่ generate โค้ดใหม่ตอน compile จริง ๆ เช่น Lombok (library ที่ generate getter/setter/constructor ให้อัตโนมัติ — ไม่ต้องเขียนเอง) และ MapStruct (library ที่ generate โค้ดแปลง object จาก type หนึ่งไปอีก type — ใช้บ่อยใน Spring Boot)

java
@Override                                     // บอก compiler ว่าตั้งใจ override
public String toString() { ... }

@Deprecated                                   // บอก IDE ว่า method เก่า
public void oldMethod() { ... }

@SuppressWarnings("unchecked")                // ปิด warning
public List<String> cast(List raw) { ... }

ในงานจริงเจอบ่อยจาก:

  • Spring: @RestController, @Autowired, @Service — annotation เหล่านี้จะอธิบายละเอียดในบท Spring Boot (โดยเฉพาะ @Autowired บน field เป็น anti-pattern = รูปแบบที่ดูง่ายแต่มีปัญหาซ่อนอยู่ — รายละเอียดในบท Spring)
  • JPA: @Entity, @Id, @Column
  • JUnit: @Test, @BeforeEach
  • Jackson: @JsonProperty, @JsonIgnore

2. Annotation Built-in ของ Java

Java มี annotation มาตรฐานที่ใช้บ่อย — @Override (ยืนยันว่า override ถูก), @Deprecated (เลิกใช้), @SuppressWarnings (ปิด warning), @FunctionalInterface (interface สำหรับ lambda) ควรรู้จักทั้งหมดเพราะเจอตลอด:

java
@Override                       // method นี้ override จาก parent — ถ้าพิมพ์ผิด compile fail
@Deprecated(since = "21", forRemoval = true)
@SuppressWarnings({"unchecked", "deprecation"})
@FunctionalInterface            // interface นี้ใช้เป็น lambda ได้ (มี abstract method 1 ตัว)
@SafeVarargs                    // generic varargs ปลอดภัย

3. Annotation ทำงานยังไง

มี 3 retention policy (นโยบายการเก็บรักษา annotation):

Retentionอยู่ถึงไหนตัวอย่าง
SOURCEแค่ compile time → หาย@Override, @SuppressWarnings
CLASSอยู่ใน .class — runtime อ่านไม่ได้default
RUNTIMEอยู่ตลอด — reflection อ่านได้@Test, @Autowired

Framework ใช้ RUNTIME เพื่อ scan ตอน app start

⚠️ กับดักที่พลาดบ่อย: ถ้าเขียน annotation เองแล้วไม่ระบุ @Retention(RUNTIME) มันจะหายไปตั้งแต่ compile time หรืออยู่ใน .class แบบ reflection อ่านไม่ได้ (default = CLASS) — โค้ด framework ที่ scan หา annotation ตอน runtime จะ "หาไม่เจอ" แบบเงียบ ๆ ไม่มี error เตือน ต้องระบุ RUNTIME ตรง ๆ เสมอถ้าจะให้ Spring/JUnit-style scanning ทำงาน


4. เขียน Annotation เอง

java
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)              // runtime อ่านได้
@Target(ElementType.METHOD)                       // ใช้บน method เท่านั้น
public @interface Loggable {
    String level() default "INFO";
    boolean includeArgs() default false;
}

ใช้:

java
public class UserService {

    private final UserRepository repo; // ... (inject มาทาง constructor)

    @Loggable(level = "DEBUG", includeArgs = true)
    public User findUser(Long id) {
        return repo.findById(id);
    }
}

@Target มีอะไรบ้าง

java
ElementType.TYPE              // class, interface, enum
ElementType.FIELD             // field
ElementType.METHOD            // method
ElementType.PARAMETER         // parameter
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE   // @interface
ElementType.PACKAGE
ElementType.TYPE_PARAMETER    // <T> generic
ElementType.TYPE_USE          // ทุกที่ที่เขียน type — @NonNull String s, List<@NonNull String>
ElementType.MODULE
ElementType.RECORD_COMPONENT  // component ของ record (Java 16+)

Meta-annotation อื่น ๆ

Meta-annotationทำอะไร
@Retention(SOURCE/CLASS/RUNTIME)annotation อยู่ถึงไหน — SOURCE = compile แล้วหาย, CLASS = อยู่ใน .class ไม่อ่าน runtime (default), RUNTIME = reflection อ่านได้
@Target(...)ใช้บนอะไรได้ (ดูตาราง)
@Documentedรวมใน Javadoc
@Inheritedsub-class สืบทอด annotation จาก super-class
@Repeatable(C.class)annotation ใส่ซ้ำได้บน element เดียว

@Inherited — annotation สืบทอด

java
import java.lang.annotation.*;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Audited {}

@Audited
class Base {}

class Child extends Base {}    // ✅ ถือว่ามี @Audited (เพราะ Base มี + @Inherited)

⚠️ @Inherited ทำงานเฉพาะ class inheritance — ไม่มีผลกับ interface

@Repeatable — ใส่ annotation ซ้ำได้ (Java 8+)

java
@Repeatable(Schedules.class)
@Retention(RUNTIME)
@interface Schedule {
    String day();
}

@Retention(RUNTIME)
@interface Schedules {                                       // container
    Schedule[] value();
}

@Schedule(day = "Monday")
@Schedule(day = "Wednesday")
public void task() { }

// อ่านทั้งหมด (ต้องดึง Method object ผ่าน reflection ก่อน)
Method method = MyClass.class.getMethod("task"); // MyClass = class ที่มี method task()
Schedule[] all = method.getAnnotationsByType(Schedule.class);

5. อ่าน Annotation ด้วย Reflection

java
import java.lang.reflect.*;

Class<?> clazz = UserService.class;

for (Method m : clazz.getDeclaredMethods()) {
    Loggable ann = m.getAnnotation(Loggable.class);
    if (ann != null) {
        System.out.println(m.getName() + " — level=" + ann.level()
            + ", includeArgs=" + ann.includeArgs());
    }
}

ในงานจริง framework (Spring, JUnit) ทำตรงนี้ให้ — เราไม่ค่อยเขียน reflection เอง


6. ตัวอย่างจริง — @RequiresAuth

ใน Spring (ตัวอย่างจริงเขียนเอง):

java
@Retention(RUNTIME)
@Target(METHOD)
public @interface RequiresAuth {
    String[] roles() default {};
}

// ใช้
@RequiresAuth(roles = {"ADMIN"})
public void deleteUser(Long id) { ... }

แล้วเขียน AOP aspect หรือ interceptor อ่าน annotation → check role

AOP (Aspect-Oriented Programming) = เทคนิคใส่โค้ดพิเศษ (เช่น logging, security check) รอบ method โดยไม่แก้ method ตรง ๆ

  • aspect = ส่วนของโค้ดที่ตัดขวางหลาย class พร้อมกัน — เขียนครั้งเดียวแล้ว Spring ฉีดเข้าทุก method ที่มี annotation ให้อัตโนมัติ
  • interceptor = ตัวดักจับ request/response ก่อนถึง method จริง
    จะเจอ AOP ในบท Spring Boot ที่เรียนต่อไป

Part 2: Java Modules (Java 9+)

7. ปัญหาก่อนมี Module

ก่อน Java 9:

  • ไม่มีระบบ "module" — แค่ classpath (เส้นทางที่ JVM ใช้ค้นหา .class file และ library — คล้าย PATH ของ OS แต่ JVM ใช้ค้นหาไฟล์ .class แทน — ทุกอย่างอยู่ในกองเดียวกัน ใครก็เข้าถึงได้ทุก class) ใหญ่ ๆ
  • ทุก public class ใน library = ใครก็เรียกได้ (encapsulation พัง)
  • JDK เอง ใหญ่ขึ้นเรื่อย ๆ — ทุกคนได้ทั้งก้อน (ทำ container เล็กยาก)

Java 9 มี JPMS (Java Platform Module System) = Project Jigsaw


8. โครงสร้าง Module

ตั้งแต่ Java 9 มี "module system" (JPMS) ที่ให้แบ่งโค้ดเป็น module พร้อมประกาศว่า export อะไรและพึ่งพา module ไหน หัวใจคือไฟล์ module-info.java ที่วางไว้ที่ราก module โครงสร้างไฟล์เป็นแบบด้านล่าง:

text
my-app/
├── src/
│   └── com.example.app/                    ← module name
│       ├── module-info.java                 ← ⭐ ไฟล์สำคัญ
│       └── com/example/app/
│           ├── Main.java
│           └── service/UserService.java

module-info.java:

java
module com.example.app {
    requires java.net.http;            // ใช้ HttpClient
    requires com.fasterxml.jackson.databind;

    exports com.example.app.api;       // public class ใน package นี้ คนอื่นเรียกได้
    // package อื่น = private อัตโนมัติ — แม้จะเป็น public class

    opens com.example.app.entity to com.fasterxml.jackson.databind;
    // reflection access เฉพาะ Jackson
}

9. Keyword ใน module-info

Keywordใช้ทำอะไร
requires Xmodule นี้ใช้ X
requires transitive Xใคร require ตัวเรา = ได้ X ฟรี
exports a.b.cpublic class ใน package นี้ เปิดให้คนอื่น
exports a.b.c to Xเปิดเฉพาะให้ module X
opens a.b.cเปิดให้ reflection (Spring/Jackson ต้องใช้)
uses Xบอกว่าจะใช้ ServiceLoader
provides X with Yบอกว่ามี implementation Y ของ X

10. ทำไมยังไม่ใช้แพร่หลาย?

  • เก่าก่อน Java 9 = ไม่มี module ใช้ "classpath" (รายการ path ที่ JVM ค้นหา .class — เปรียบได้กับ PATH ใน OS) → Java 9+ ยังรองรับกลุ่มนี้โดยจัดเป็น "unnamed module" คือ library เก่าที่ไม่มี module-info แต่อยู่บน classpath ทั้งหมด ทุก module ที่ import ได้ก็มองเห็น unnamed module นี้หมด
  • Library เก่า ๆ ส่วนใหญ่ ไม่มี module-info.java → ใช้ "automatic module" (library เก่าที่ถูกวางบน module-path — ได้ชื่อ module อัตโนมัติจากชื่อไฟล์ jar หรือจาก Automatic-Module-Name ใน manifest)
  • Spring Boot ส่วนใหญ่ไม่ใช้ JPMS — ใช้ classpath แบบเดิม

สรุป: รู้ว่ามี + อ่านเข้าใจ — เขียนเองตอนทำ library/CLI เท่านั้น


10.5 ServiceLoader — plugin pattern ของ JDK

ServiceLoader = SPI (Service Provider Interface) — ให้ผู้อื่นเสียบ "implementation" ของ interface ของเราโดยไม่ต้อง compile ร่วมกัน

java
// 1. ประกาศ interface
package com.app.payment;
public interface PaymentProcessor {
    boolean process(double amount);
}

// 2. มี implementation
package com.stripe;
public class StripeProcessor implements PaymentProcessor {
    public boolean process(double amount) { /* ... */ return true; }
}

ลงทะเบียน implementation 2 แบบ

แบบ classic — file META-INF/services/com.app.payment.PaymentProcessor (META-INF/services/ คือโฟลเดอร์พิเศษภายใน .jar ที่ JVM ใช้ค้นหา service implementation — สร้างไว้ที่ src/main/resources/META-INF/services/):

text
com.stripe.StripeProcessor
com.paypal.PayPalProcessor

แบบ JPMS — ใน module-info.java:

java
module com.stripe {
    requires com.app.payment;
    provides com.app.payment.PaymentProcessor with com.stripe.StripeProcessor;
}

โหลด

java
ServiceLoader<PaymentProcessor> loader = ServiceLoader.load(PaymentProcessor.class);
for (PaymentProcessor p : loader) {
    p.process(100);
}

// stream + filter — โหลด provider บางตัว
loader.stream()
    .filter(provider -> provider.type().getName().contains("Stripe"))
    .findFirst()
    .map(ServiceLoader.Provider::get)
    .ifPresent(p -> p.process(100));

ตัวอย่างจริง: JDBC driver, SLF4J binding, java.time ZoneRulesProvider, security provider — ทุกตัวใช้ ServiceLoader

💡 Spring/Spring Boot ใช้กลไกของตัวเอง (spring.factories / AutoConfiguration.imports) ที่คล้าย ๆ ServiceLoader


ข้อดีจริงของ module: jlink สร้าง JRE ที่มีแค่ module ที่ใช้

📝 $JAVA_HOME คืออะไร: environment variable (ตัวแปรระบบ) ที่ชี้ไปยังโฟลเดอร์ที่ติดตั้ง JDK ไว้

  • ตรวจสอบด้วย echo $JAVA_HOME (Linux/Mac) หรือ echo $env:JAVA_HOME (PowerShell)
  • ถ้าว่างเปล่า ให้หา path ด้วย java -XshowSettings:all 2>&1 | grep java.home (Linux/Mac) หรือ java -XshowSettings:all 2>&1 | findstr java.home (Windows)
  • แล้วตั้งค่า: export JAVA_HOME=/path/to/jdk (Linux/Mac) หรือ $env:JAVA_HOME = "C:\path\to\jdk" (PowerShell)
bash
# bash (Linux/Mac) — \ ต่อบรรทัด, : คั่น path, $JAVA_HOME
jlink \
    --module-path $JAVA_HOME/jmods:mods \
    --add-modules com.example.app \
    --output custom-jre \
    --strip-debug --no-header-files --no-man-pages --compress=zip-6
powershell
# PowerShell (Windows) — ` ต่อบรรทัด, ; คั่น path, $env:JAVA_HOME
jlink `
    --module-path "$env:JAVA_HOME\jmods;mods" `
    --add-modules com.example.app `
    --output custom-jre `
    --strip-debug --no-header-files --no-man-pages --compress=zip-6

ได้ JRE ขนาด ~30-50 MB (เทียบ full JDK 300 MB) — ดีสำหรับ Docker image

⚠️ Spring Boot ใช้ GraalVM native image แทน (เล็กกว่า + เร็วกว่า)


Part 3: Build Tools — Maven vs Gradle

12. ทำไมต้องมี Build Tool?

โปรเจกต์จริง:

  • ต้อง download library (Jackson, Spring, JUnit, ...) — กี่ตัว version อะไร?
  • ต้อง compile หลายร้อยไฟล์
  • ต้อง package เป็น jar
  • ต้อง run test
  • ต้อง publish ขึ้น artifact repository

ทำเองด้วย javac + jar = ฝันร้าย → ใช้ build tool

MavenGradle
ไฟล์ configpom.xml (XML)build.gradle (Groovy) หรือ build.gradle.kts (Kotlin)
VerboseXML ยาวสั้น
ความเร็วปานกลางเร็ว (incremental, daemon, cache)
Pluginเยอะ + เสถียรเยอะ + flexible
ความยากในการเรียนรู้ตรง ๆสูงขึ้น
Spring Boot default✅ (แนะนำสำหรับมือใหม่ — doc/ตัวอย่างเยอะกว่า)start.spring.io (เว็บสร้างโปรเจกต์ Spring Boot ใหม่) ปัจจุบัน default เป็น Gradle
Android✅ standard

สรุป: Spring Boot project มือใหม่ → ใช้ Maven (เริ่มง่าย, doc/ตัวอย่างเยอะกว่า)
Android / โปรเจกต์ใหญ่ → Gradle

⚠️ ถ้าสร้าง project จาก start.spring.io: เว็บนี้ตั้งค่า default เป็น Gradle — ให้เปลี่ยนก่อนกด Generate โดยดูที่ dropdown "Build:" แล้วเลือก Maven แทน Gradle เพื่อให้ตรงกับตัวอย่างในหนังสือนี้


13. Maven — โครงสร้าง

text
my-app/
├── pom.xml                            ← config
├── src/
│   ├── main/
│   │   ├── java/                      ← source code
│   │   └── resources/                 ← config, properties, static files
│   └── test/
│       ├── java/                      ← test code
│       └── resources/
└── target/                            ← output (compile, jar) — gitignore

Maven convention = ทุกโปรเจกต์ structure เหมือนกัน — ลดความคิด

pom.xml ขั้นต่ำ

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>            <!-- บริษัท / org -->
    <artifactId>my-app</artifactId>            <!-- ชื่อ project -->
    <version>1.0.0</version>                   <!-- version -->
    <packaging>jar</packaging>                 <!-- jar / war / pom -->

    <properties>
        <!-- ใช้ <release> (Java 9+) แทน source/target — กระชับและถูกต้องกว่า; ปรับเลขตาม JDK ที่ติดตั้งจริง (ที่นี่ใช้ 21 LTS) -->
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.19.0</version>
            <!-- ตรวจ version ล่าสุดได้ที่ https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
            <!-- pom.xml นี้เป็น standalone Java project — ถ้าใช้ Spring Boot parent จะได้ version ของ Jackson และ JUnit จาก BOM โดยอัตโนมัติ ไม่ต้องระบุ version เอง -->
            <!-- เมื่อมีหลาย Jackson dep แนะนำให้ใช้ jackson-bom (ดู section BOM ด้านล่าง) แทนการ hardcode version แยกตัว -->
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.11.0</version>   <!-- ถ้าใช้ Spring Boot BOM ให้ลบ <version> ออก — BOM จัดการให้แล้ว -->
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

⚠️ pom.xml ขั้นต่ำนี้ยังไม่มี Maven Surefire Plugin (plugin ที่สั่งรัน JUnit test ตอน mvn test) — ถ้าเพิ่ม dependency JUnit 5 แล้วรัน mvn test แล้วเจอ "No tests found" ให้ดู section Surefire ในบทที่ 15 (Testing + Logging)

Dependency Scope — ตารางครบ

Scopecompile classpathtest classpathruntime classpathอยู่ใน jar สุดท้ายตัวอย่าง
compile (default)jackson, slf4j
provided❌ (มีจาก container)jakarta.servlet-api (Spring Boot 3.x ใช้ Jakarta EE 10+ — ไม่ใช่ javax.*)
runtimeJDBC driver, Logback impl
testJUnit, Mockito
systemjar นอก repo (ลืม! anti-pattern)
importใช้กับ BOM (ดู section ถัดไป)

💡 Lombok ไม่ได้อยู่ใน scope provided — ปกติใช้ scope compile (default) ร่วมกับ <optional>true</optional> ซึ่งเป็นคนละกลไกกับตาราง scope ด้านบน (<optional>true</optional> แค่บอกว่าไม่ต้อง propagate dependency นี้ไปให้ project อื่นที่มาพึ่งเรา)

BOM (Bill of Materials) — แชร์ version ระหว่างหลาย dependency

ปัญหา: Spring Boot มี dependency หลาย dozen ตัว ต้อง compatible version กัน → ใช้ BOM:

xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.4.x</version>   <!-- ⚠️ ใช้ version ล่าสุดของ Spring Boot 3.x — ตรวจสอบที่ https://spring.io/projects/spring-boot (ณ กลาง 2026 อยู่ที่ 3.4.x/3.5.x) -->
            <type>pom</type>
            <scope>import</scope>                            <!-- key! -->
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>     <!-- version มาจาก BOM -->
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>            <!-- version มาจาก BOM -->
    </dependency>
</dependencies>

จุดเด่น:

  • ระบุ version ที่เดียว — auto upgrade ทุก dep พร้อมกัน
  • กัน "transitive version conflict" (transitive แปลว่า พึ่งพาทางอ้อม = dependency ของ dependency — เช่น lib A พึ่ง jackson, lib B พึ่ง jackson แต่คนละ version → conflict)
  • Spring Boot, AWS SDK, Google Cloud — ทุกตัวมี BOM

Multi-module project — โปรเจกต์ใหญ่แบ่งหลาย module

text
parent/
├── pom.xml                              ← parent POM (packaging=pom)
├── common/
│   └── pom.xml                          ← module 1
├── api/
│   └── pom.xml                          ← module 2 (depends on common)
└── service/
    └── pom.xml                          ← module 3 (depends on api)
xml
<!-- parent/pom.xml -->
<project>
    <groupId>com.example</groupId>
    <artifactId>parent</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>                    <!-- ไม่ใช่ jar -->

    <modules>
        <module>common</module>
        <module>api</module>
        <module>service</module>
    </modules>

    <dependencyManagement>
        <!-- version กลาง — module ลูกใช้ได้โดยไม่ระบุ version -->
    </dependencyManagement>
</project>
xml
<!-- service/pom.xml -->
<project>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>parent</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>service</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>api</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
</project>

build ทั้งหมดที่ root: mvn install
build เฉพาะ module service + dependency ที่มันต้องการ: mvn -pl service -am install (-pl project list + -am also-make = build deps ก่อน)


14. คำสั่ง Maven ที่ใช้บ่อย

Maven ทำงานผ่าน "lifecycle" — แต่ละคำสั่งรันขั้นก่อนหน้าให้ด้วย (เช่น package จะ compile + test ให้ก่อน) รวมคำสั่งที่ใช้บ่อยไว้ด้านล่างเป็นที่เดียว:

bash
mvn compile                  # compile src/main
mvn test                     # compile + run test
mvn package                  # → target/my-app-1.0.0.jar
mvn install                  # บันทึกลง local Maven cache (~/.m2) เพื่อให้ module/โปรเจกต์อื่นเรียกใช้ได้ — ไม่ใช่ติดตั้งลงเครื่อง
mvn clean                    # ลบ target/
mvn dependency:tree          # ดู dependency graph
mvn -DskipTests package      # package โดยไม่รอ test — ใช้เมื่อ build Docker image หลัง test ผ่านแล้วในขั้นก่อนหน้า

# ใน Spring Boot
mvn spring-boot:run          # run app

Build Lifecycle

text
validate → compile → test → package → verify → install → deploy

แต่ละ phase ครอบคลุม phase ก่อนหน้าmvn package = compile + test + package


15. Maven Wrapper — มาตรฐาน

ทำให้ทุกเครื่อง build ด้วย Maven version เดียวกัน — แม้ไม่มี Maven installed:

bash
# Setup ครั้งเดียว
mvn wrapper:wrapper

# จากนั้นใช้ ./mvnw (Linux/Mac) หรือ mvnw.cmd (Windows) แทน mvn
./mvnw clean install

16. Gradle — ภาพรวม

build.gradle.kts (Kotlin DSL):

kotlin
plugins {
    id("java")
    id("org.springframework.boot") version "3.4.x"  // ใช้ version ล่าสุด — ตรวจสอบที่ https://spring.io/projects/spring-boot
}

group = "com.example"
version = "1.0.0"

java {
    sourceCompatibility = JavaVersion.VERSION_21
    // สำหรับโปรเจกต์ที่ต้องการ reproducible build แนะนำใช้ Java Toolchain แทน:
    // toolchain { languageVersion = JavaLanguageVersion.of(21) }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.test {
    useJUnitPlatform()
}

คำสั่ง Gradle

bash
./gradlew build              # compile + test + jar
./gradlew test               # run test
./gradlew bootRun            # run Spring Boot
./gradlew clean
./gradlew dependencies       # dependency tree
./gradlew --refresh-dependencies

Gradle Kotlin DSL vs Groovy DSL

Gradle มี 2 DSL:

  • build.gradleGroovy (เก่า — โครงการเก่ายังเจอ)
  • build.gradle.ktsKotlin (ใหม่ — แนะนำสำหรับโครงการใหม่)

Kotlin DSL ได้ IDE auto-complete + type safety เต็ม — ไม่ต้องเดา syntax อีก

Version Catalog (libs.versions.toml) — Gradle 7+

Version Catalog (catalog = รายการ library ทุกตัวพร้อม version — เก็บไว้ที่เดียว) ใช้ไฟล์ .toml (รูปแบบ config file อ่านง่าย คล้าย INI แต่มีโครงสร้างชัดกว่า XML)

แทนการ hardcode version ทุกที่:

toml
# gradle/libs.versions.toml
[versions]
spring-boot = "3.4.x"   # ใช้ version ล่าสุด — ตรวจสอบที่ https://spring.io/projects/spring-boot
jackson = "2.19.0"

[libraries]
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }

[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
kotlin
// build.gradle.kts
plugins {
    java
    alias(libs.plugins.spring.boot)
}

dependencies {
    implementation(libs.spring.boot.starter.web)
    implementation(libs.jackson.databind)
}

→ มี version ที่เดียว ทุกโมดูลแชร์ได้ — เทียบกับ Maven BOM (<dependencyManagement>)


16.5 jpackage — สร้าง native installer (ตัวติดตั้งสำหรับ OS โดยตรง เช่น .exe/.msi บน Windows, .dmg บน Mac)

ต้องการแจกแอป Java ให้คนที่ ไม่มี JVM? — jpackage (มากับ JDK ตั้งแต่ Java 14) สร้าง installer พร้อม JRE bundled

bash
# สร้าง .exe / .msi (Windows), .dmg / .pkg (Mac), .deb / .rpm (Linux)
jpackage \
  --name MyApp \
  --input target/ \
  --main-jar myapp.jar \
  --main-class com.example.Main \
  --type msi \
  --app-version 1.0.0

ผลลัพธ์: installer ที่ผู้ใช้กด install แล้วได้ shortcut + run ได้ทันที (ไม่ต้องลง JDK)

ใช้กับ:

  • Desktop app (JavaFX, Swing)
  • CLI tool ที่อยากให้ run ง่าย ๆ
  • App ภายในองค์กรที่ control environment ไม่ได้

16.6 GraalVM native-image — compile เป็น native binary

แทนการแพ็ก JRE — compile Java เป็น native executable ขนาดเล็ก start เร็ว memory ต่ำ

bash
# ติดตั้ง GraalVM + native-image tool ก่อน
native-image -jar myapp.jar
# ได้ binary ~30-50MB ที่ start ภายใน ~50ms (vs JVM start ~500ms+)

ใน Maven/Gradle ใช้ plugin org.graalvm.buildtools.native

JVMNative Image
Start time500ms+~50ms
Memory100MB+30MB
Peak throughputสูงสุด (JIT optimize)ต่ำกว่านิด
Build timeวินาทีนาที
Reflection / dynamic classใช้ตรง ๆ ได้ต้อง declare config

ใช้กับ:

  • Serverless (รูปแบบ deploy ที่ไม่ต้องดูแล server เอง จ่ายค่าบริการตาม request — เช่น AWS Lambda, Cloud Run) — start เร็ว = ไม่โดน cold start (เวลาที่ใช้เริ่มต้น app ครั้งแรก — ถ้าช้าเกิน user รอนาน)
  • CLI tool ที่ต้อง run บ่อย ๆ
  • Container ที่ memory จำกัด (k8s pod เล็ก)

Spring Boot 3+ รองรับ native image เป็น first-class — mvn -Pnative native:compile

⚠️ ระวัง reflection-heavy library (Hibernate proxy, dynamic JSON binding) — ต้อง config reflect-config.json หรือใช้ tracing agent (JVM agent ที่รัน app จริงแล้วบันทึกการใช้ reflection ออกมาเป็นไฟล์ config ให้อัตโนมัติ)


17. ⚠️ Build Tools Pitfalls

Pitfallแก้
Hardcode version ในหลายที่ใช้ <properties> (Maven) หรือ extra (Gradle)
Build เร็วเปลี่ยน — ลืม commit lockใช้ Maven/Gradle wrapper
Test มี dependency กับ DB จริงใช้ Testcontainers + scope test
Library version conflictmvn dependency:tree หา conflict
Jar ใหญ่เกินSpring Boot fat jar = ปกติ — แต่ check ว่าไม่ include test scope

Part 4: Javadoc

18. Javadoc คืออะไร

Javadoc คือระบบเอกสารในตัวของ Java — เขียนคอมเมนต์พิเศษ /** ... */ เหนือ class/method พร้อม tag (@param, @return, @throws) แล้วเครื่องมือ javadoc แปลงเป็นเว็บ HTML ให้อัตโนมัติ:

Javadoc = comment พิเศษ /** ... */ ที่ tool แปลงเป็น HTML document

java
/**
 * บริการสำหรับจัดการ user — สร้าง, อ่าน, อัพเดต, ลบ.
 * <p>
 * Service นี้ใช้ {@link UserRepository} เพื่อ persistence
 * และ {@link PasswordEncoder} สำหรับ hash password.
 *
 * @author Anna
 * @since 1.0.0
 */
public class UserService {

    /**
     * ค้นหา user จาก id.
     *
     * @param id user id (must be positive)
     * @return user ที่ตรงกับ id
     * @throws UserNotFoundException ถ้าไม่เจอ
     * @see UserRepository#findById
     */
    public User findById(Long id) {
        return repo.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

19. Tag สำคัญ

Tagใช้ทำอะไร
@paramparameter
@returnreturn value
@throwsexception ที่อาจ throw
@seereference ไป class/method อื่น
@sinceversion แรกที่มี
@deprecatedบอกว่า deprecated + แนะนำทางแก้
@authorคนเขียน
{@link X}inline link ไป X
{@code x}inline code

20. Generate Javadoc

เมื่อเขียน Javadoc comment แล้ว สั่ง generate เป็นเว็บเอกสารได้ผ่าน Maven, Gradle หรือคำสั่ง javadoc ดิบ ๆ — ผลลัพธ์คือชุดไฟล์ HTML ที่เปิดดูได้:

bash
# Maven
mvn javadoc:javadoc                  # → target/site/apidocs/index.html

# Gradle
./gradlew javadoc                    # → build/docs/javadoc/index.html

# คำสั่งดิบ (แนะนำใช้แบบนี้ — glob ** ใช้ใน PowerShell/cmd ไม่ได้)
javadoc -d docs -sourcepath src/main/java -subpackages com.example

21. ❓ ควรเขียน Javadoc ทุกอย่างไหม?

ไม่

  • Public API ของ library / SDK ที่คนอื่นใช้ → เขียนละเอียด
  • Internal service class → เขียนเฉพาะที่ WHY ไม่ชัด
  • Getter/setter → ไม่ต้อง (IDE generate ได้)
  • Private method → ไม่ต้อง

กฎ: เขียนเมื่อโค้ดอ่านแล้วยังไม่เข้าใจ — อย่าย้ำสิ่งที่ method signature บอกอยู่แล้ว

❌ Javadoc ที่ไม่มีประโยชน์:

java
/** Get the name. @return the name */
public String getName() { return name; }

✅ Javadoc มีประโยชน์:

java
/**
 * คำนวณภาษีตามกฎ ก.ค. 2025 — รวม VAT แต่ไม่รวม service charge.
 * Throw ถ้า amount &lt; 0 เพราะระบบหลังบ้านไม่รองรับ credit note.
 */
public BigDecimal calculateTax(BigDecimal amount) { ... }

22. Checkpoint

🛠️ Checkpoint 14.1 — Custom Annotation
สร้าง @Timed ที่ใส่บน method แล้วเขียน main ที่:

  1. scan method ใน class ด้วย reflection
  2. ถ้ามี @Timed → wrap call ด้วย stopwatch → พิมพ์เวลาที่ใช้

🛠️ Checkpoint 14.2 — Maven project

⚠️ ก่อนรันคำสั่งนี้ — ต้องติดตั้ง Maven ก่อน
ตรวจสอบด้วย mvn -v ใน terminal — ถ้าเจอ "command not found" หรือ "'mvn' is not recognized" ให้ติดตั้ง Maven ก่อน:

  • Windows: winget install Apache.Maven หรือโหลดที่ https://maven.apache.org/install.html แล้วเพิ่มใน PATH
  • macOS: brew install maven
  • Linux: sudo apt install maven หรือ sudo dnf install maven
    หลังติดตั้งแล้วเปิด terminal ใหม่แล้วรัน mvn -v อีกครั้งเพื่อยืนยัน

สร้างโปรเจกต์ใหม่จาก Maven CLI:

bash
# bash (Linux/Mac)
mvn archetype:generate -DgroupId=com.example -DartifactId=mini-app \
    -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
powershell
# PowerShell (Windows) — เขียนในบรรทัดเดียว หรือใช้ ` ต่อบรรทัด
mvn archetype:generate -DgroupId=com.example -DartifactId=mini-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

⚠️ archetype maven-archetype-quickstart มักสร้าง pom.xml ที่ยังตั้ง Java source/target เป็นเวอร์ชันเก่ามาก — หลัง generate เสร็จให้แก้ pom.xml เพิ่ม <maven.compiler.release>21</maven.compiler.release> (ดูตัวอย่างใน section 13) ก่อน ไม่งั้น syntax ใหม่อย่าง var/record จะ compile ไม่ผ่าน

เพิ่ม Jackson, run test, package เป็น jar, run jar ด้วย java -jar

🛠️ Checkpoint 14.3 — Javadoc your code
เอา class จาก checkpoint ก่อน ๆ มาเพิ่ม Javadoc + generate HTML → เปิดดู


23. สรุปบท

✅ Annotation = metadata บนโค้ด — framework อ่านด้วย reflection
✅ เขียน annotation เอง: @Retention + @Target + @interface
✅ Java Modules (JPMS, Java 9+) = แยก module + control visibility — ส่วนใหญ่ใน app ทั่วไปไม่ใช้
✅ jlink → custom JRE เล็กลง
✅ Build Tools = ตัวจัดการ compile + dependency + test + package
✅ Maven = XML + convention, Gradle = DSL + เร็วกว่า
✅ Spring Boot มือใหม่ → Maven, โปรเจกต์ใหญ่ / Android → Gradle
✅ ใช้ ./mvnw / ./gradlew (wrapper) เพื่อให้ทุกเครื่อง build เหมือนกัน
✅ Javadoc เขียนเมื่อ WHY ไม่ชัด — อย่าย้ำสิ่งที่ signature บอก


← บทที่ 13 | บทที่ 15 → Testing + Logging