info-proxy-pattern-with-java

Proxy Pattern คือการใช้ออบเจ็กต์ตัวแทนเพื่อควบคุมการเข้าถึงออบเจ็กต์จริง โดยตัวแทนและออบเจ็กต์จริงมีสัญญาการใช้งานร่วมกัน

บทความนี้อธิบายแนวคิด โครงสร้าง และการนำ Proxy Pattern มาใช้ใน Java ผ่านสามตัวอย่าง ได้แก่ การโหลดรูปภาพเมื่อจำเป็น การตรวจสิทธิ์อ่านรายงาน และการสร้าง Dynamic Proxy สำหรับจับเวลาการเรียกเมธอด

พื้นฐานที่ควรมี: Class, Interface, Composition และ Exception ใน Java

สภาพแวดล้อมสำหรับทำตาม: JDK 17 ขึ้นไป ไม่ใช้ Maven, Gradle หรือไลบรารีเพิ่มเติม

#1. ปัญหาที่ Proxy Pattern ช่วยแก้

สมมติว่าโปรแกรมมีรายการรูปภาพจำนวนมาก หากสร้างออบเจ็กต์แต่ละรูปแล้วโหลดข้อมูลทันที โปรแกรมอาจใช้เวลาและหน่วยความจำกับรูปที่ผู้ใช้ยังไม่ได้เปิดดู

อีกกรณีหนึ่งคือระบบรายงาน ผู้ใช้ทุกคนอาจเรียกบริการผ่านเมธอดเดียวกัน แต่ผู้ใช้บางคนเท่านั้นที่ควรอ่านรายงานได้ หากใส่การตรวจสิทธิ์กระจายอยู่ทุกหน้าจอ การดูแลกฎการเข้าถึงจะยุ่งยากขึ้น

Proxy ช่วยเพิ่มจุดควบคุมระหว่างผู้เรียกกับออบเจ็กต์จริง เช่น สร้างออบเจ็กต์จริงเมื่อจำเป็น ตรวจสิทธิ์ก่อนส่งคำขอ หรือคืนค่าจากแคชเมื่อมีข้อมูลที่ใช้ได้อยู่แล้ว

#2. Proxy Pattern คืออะไร

Proxy อยู่ในกลุ่ม Structural Design Patterns ซึ่งเน้นการประกอบความสัมพันธ์ระหว่างคลาสและออบเจ็กต์

หัวใจของรูปแบบนี้มีสามข้อ:

  1. ผู้เรียกใช้งานผ่านสัญญาร่วม เช่น Java interface
  2. Proxy รับคำขอและตัดสินใจว่าจะจัดการอย่างไร
  3. Proxy ส่งคำขอไปยังออบเจ็กต์จริงเมื่อเหมาะสม หรือจัดการเองตามหน้าที่ของตัวแทน

เปรียบได้กับเจ้าหน้าที่หน้าห้องเอกสาร ผู้ขอเอกสารติดต่อเจ้าหน้าที่ ซึ่งตรวจเงื่อนไขก่อนอนุญาตให้เข้าถึงเอกสารภายใน

การมีอินเทอร์เฟซร่วมช่วยให้โค้ดผู้เรียกใช้ Proxy แทนออบเจ็กต์จริงได้ แต่ไม่ได้แปลว่าพฤติกรรมและต้นทุนจะเหมือนเดิมทุกด้าน เช่น Proxy อาจปฏิเสธการเข้าถึง หรืออาจมีเวลาในการโหลดครั้งแรก

#3. องค์ประกอบของรูปแบบ

องค์ประกอบ หน้าที่ ตัวอย่างในบทความ
Subject กำหนดสัญญาร่วมสำหรับผู้เรียก Image
Real Subject ทำงานจริง RealImage
Proxy ควบคุมการเข้าถึง Real Subject ImageProxy
Client เรียกใช้งานผ่าน Subject เมธอด main()
classDiagram
    class Image {
        <<interface>>
        +display() void
    }
    class RealImage {
        -String fileName
        +display() void
    }
    class ImageProxy {
        -String fileName
        -RealImage realImage
        +display() void
    }
    class Client
    Image <|.. RealImage
    Image <|.. ImageProxy
    ImageProxy --> RealImage : creates on demand
    Client --> Image : uses

ImageProxy เก็บรายละเอียดที่จำเป็นสำหรับสร้าง RealImage โดยอ้างอิงออบเจ็กต์จริงหลังจากสร้างแล้ว ส่วน Client ใช้เพียง Image

#4. ประเภทของ Proxy ที่พบบ่อย

ประเภท จุดประสงค์ ตัวอย่าง
Virtual Proxy เลื่อนการสร้างหรือโหลดออบเจ็กต์ที่มีต้นทุนสูง โหลดรูปเมื่อเปิดดู
Protection Proxy ควบคุมสิทธิ์การเข้าถึง ตรวจสิทธิ์ก่อนอ่านรายงาน
Remote Proxy เป็นตัวแทนของบริการที่อยู่ต่างกระบวนการหรือเครื่อง ตัวแทนเรียกบริการผ่านเครือข่าย
Caching Proxy ใช้ผลลัพธ์เดิมเมื่อเงื่อนไขของแคชยังถูกต้อง คืนข้อมูลที่เคยร้องขอ

ประเภทเหล่านี้อธิบาย เจตนาของการใช้งาน ส่วน Static Proxy และ Dynamic Proxy อธิบาย วิธีสร้างตัวแทน ดังนั้น Protection Proxy อาจเขียนเป็นคลาสเองหรือสร้างแบบ Dynamic Proxy ก็ได้

#5. Workshop 1: Virtual Proxy สำหรับรูปภาพ

#5.1 เป้าหมาย

สร้าง ImageProxy ที่ยังไม่สร้าง RealImage ใน constructor แต่สร้างเมื่อเรียก display() ครั้งแรก และใช้ออบเจ็กต์เดิมในการเรียกครั้งถัดไป

ตัวอย่างจำลองการโหลดและแสดงภาพด้วยข้อความใน console เพื่อให้เห็นจังหวะการทำงาน โดยไม่ต้องเตรียมไฟล์รูปภาพหรือส่วนติดต่อแบบกราฟิก

#5.2 โค้ดฉบับสมบูรณ์

บันทึกเป็นไฟล์ ProxyDemo.java

import java.util.Objects;

public class ProxyDemo {

    interface Image {
        void display();
    }

    static final class RealImage implements Image {
        private final String fileName;

        RealImage(String fileName) {
            this.fileName = Objects.requireNonNull(fileName);
            loadFromDisk();
        }

        private void loadFromDisk() {
            System.out.println("Loading image: " + fileName);
        }

        @Override
        public void display() {
            System.out.println("Displaying image: " + fileName);
        }
    }

    static final class ImageProxy implements Image {
        private final String fileName;
        private RealImage realImage;

        ImageProxy(String fileName) {
            this.fileName = Objects.requireNonNull(fileName);
        }

        @Override
        public synchronized void display() {
            if (realImage == null) {
                realImage = new RealImage(fileName);
            }
            realImage.display();
        }
    }

    public static void main(String[] args) {
        Image image = new ImageProxy("campus.png");
        System.out.println("Proxy created; image not loaded yet.");

        System.out.println("--- First display ---");
        image.display();

        System.out.println("--- Second display ---");
        image.display();
    }
}

#5.3 วิธีคอมไพล์และรัน

เปิด Terminal หรือ Command Prompt ในโฟลเดอร์ที่บันทึกไฟล์ แล้วใช้คำสั่ง:

javac -encoding UTF-8 ProxyDemo.java
java ProxyDemo

ผลลัพธ์:

Proxy created; image not loaded yet.
--- First display ---
Loading image: campus.png
Displaying image: campus.png
--- Second display ---
Displaying image: campus.png

#5.4 อธิบายจุดสำคัญ

  • new ImageProxy(...) เก็บชื่อไฟล์อย่างเดียว จึงยังไม่เกิดข้อความ Loading image
  • Client ประกาศตัวแปรเป็น Image และเรียก display() ตามสัญญาร่วม
  • การเรียกครั้งแรกพบว่า realImage == null จึงสร้างออบเจ็กต์จริงและแสดงภาพ
  • การเรียกครั้งที่สองใช้ RealImage เดิม จึงมีเพียงข้อความแสดงภาพ
  • การนำออบเจ็กต์เดิมกลับมาใช้เกิดภายใน Proxy หนึ่งตัว หากสร้าง Proxy สองตัวด้วยชื่อไฟล์เดียวกัน ตัวอย่างนี้จะยังสร้าง Real Subject แยกกัน

#5.5 ลำดับการทำงาน

sequenceDiagram
    participant C as Client
    participant P as ImageProxy
    participant R as RealImage
    C->>P: display() ครั้งแรก
    Note over P: ยังไม่มี RealImage
    P->>R: สร้างและโหลดภาพ
    P->>R: display()
    R-->>P: ทำงานเสร็จ
    P-->>C: ทำงานเสร็จ
    C->>P: display() ครั้งที่สอง
    Note over P: ใช้ RealImage เดิม
    P->>R: display()
    R-->>P: ทำงานเสร็จ
    P-->>C: ทำงานเสร็จ

แต่ละการเรียก display() แสดงข้อความผ่าน console แล้วจบเมธอด โดยไม่มีค่าคืนกลับ เพราะสัญญากำหนดชนิดผลลัพธ์เป็น void

#5.6 เหตุผลที่ใช้ synchronized

หากหลาย thread เรียก Proxy ตัวเดียวกันพร้อมกัน ทั้งสอง thread อาจเห็น realImage == null และสร้างออบเจ็กต์จริงซ้ำได้ synchronized ในตัวอย่างจึงป้องกันการแข่งขันระหว่างการสร้างและเรียกใช้งานผ่าน Proxy ตัวนี้

วิธีนี้ล็อกครอบคลุมทั้ง display() จึงทำให้การเรียก Proxy เดียวกันเป็นลำดับ เหมาะกับตัวอย่างที่ต้องการความชัดเจน หากงานจริงใช้เวลาแสดงผลนาน ควรออกแบบการล็อกและความปลอดภัยของ Real Subject ให้เหมาะกับลักษณะการทำงาน

Lazy Loading ช่วยเลื่อนต้นทุนการโหลดไปจนถึงเวลาที่ต้องใช้ ไม่ได้ลดต้นทุนการโหลดภาพนั้นโดยอัตโนมัติ และผู้ใช้อาจรับรู้ความหน่วงในการเปิดภาพครั้งแรก

#6. Workshop 2: Protection Proxy สำหรับรายงาน

ตัวอย่างนี้ให้ Proxy ตรวจสิทธิ์ทุกครั้งก่อนส่งคำขอไปยังบริการรายงาน โดยแยกกฎการอนุญาตออกมาเป็น Predicate<String>

บันทึกเป็นไฟล์ ProtectionProxyDemo.java

import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;

public class ProtectionProxyDemo {

    interface ReportService {
        String readReport();
    }

    static final class RealReportService implements ReportService {
        @Override
        public String readReport() {
            return "Report: SE-2026";
        }
    }

    static final class ReportProxy implements ReportService {
        private final ReportService target;
        private final String userId;
        private final Predicate<String> canRead;

        ReportProxy(ReportService target, String userId,
                    Predicate<String> canRead) {
            this.target = Objects.requireNonNull(target);
            this.userId = Objects.requireNonNull(userId);
            this.canRead = Objects.requireNonNull(canRead);
        }

        @Override
        public String readReport() {
            if (!canRead.test(userId)) {
                throw new SecurityException("Access denied: " + userId);
            }
            return target.readReport();
        }
    }

    public static void main(String[] args) {
        Set<String> allowedReaders = Set.of("teacher");
        ReportService realService = new RealReportService();

        ReportService teacherService = new ReportProxy(
                realService, "teacher", allowedReaders::contains);
        System.out.println(teacherService.readReport());

        ReportService guestService = new ReportProxy(
                realService, "guest", allowedReaders::contains);
        try {
            System.out.println(guestService.readReport());
        } catch (SecurityException e) {
            System.out.println(e.getMessage());
        }
    }
}

รันด้วย:

javac -encoding UTF-8 ProtectionProxyDemo.java
java ProtectionProxyDemo

ผลลัพธ์:

Report: SE-2026
Access denied: guest

เมื่อ canRead คืนค่า false Proxy จะโยน exception ก่อนเรียก target.readReport() จึงไม่ส่งคำขอที่ถูกปฏิเสธไปยังบริการจริง

ในการนำไปใช้กับระบบจริง userId ต้องมาจากบริบทผู้ใช้ที่ผ่านการยืนยันตัวตนแล้ว และกฎการอนุญาตต้องมาจากแหล่งที่ระบบเชื่อถือได้ ตัวอย่างกำหนดค่าตรงใน main() เพื่อสาธิตรูปแบบเท่านั้น นอกจากนี้ โค้ดประกอบระบบควรส่งต่อเฉพาะ Proxy ให้ส่วนที่ต้องถูกควบคุม หากส่วนนั้นได้รับ realService โดยตรง ก็สามารถเรียกข้าม Proxy ได้

#7. Static Proxy และ JDK Dynamic Proxy

ตัวอย่างสองชุดแรกเป็น Static Proxy เพราะเราเขียนคลาส Proxy และเมธอดที่รับคำขอด้วยตนเอง ทำให้ติดตามการทำงานง่าย และกำหนดพฤติกรรมเฉพาะแต่ละเมธอดได้ชัดเจน

Java ยังมี JDK Dynamic Proxy ผ่าน java.lang.reflect.Proxy ซึ่งสร้างคลาสที่ implement อินเทอร์เฟซที่กำหนดขณะรัน และส่งการเรียกเมธอดไปยัง InvocationHandler [1][2]

ประเด็น Static Proxy JDK Dynamic Proxy
วิธีสร้าง เขียนคลาสเอง สร้างคลาสขณะรันผ่าน API
การควบคุมเมธอด เขียนโค้ดแต่ละเมธอดโดยตรง ประมวลผลผ่าน handler
กรณีเหมาะสม เงื่อนไขเฉพาะบริการหรือมีเมธอดไม่มาก พฤติกรรมร่วมหลายเมธอด เช่น จับเวลาหรือบันทึกการเรียก
ขอบเขตของตัวอย่าง ใช้ interface ร่วมกับ Real Subject API รับชนิด interface เพื่อสร้างตัวแทน
สิ่งที่ต้องดูแล โค้ด forwarding เมื่อมีเมธอดเพิ่ม Reflection, exception และเมธอดจาก Object

JDK Dynamic Proxy ไม่ได้สร้าง subclass ของคลาสธุรกิจเป้าหมาย และไม่สามารถส่งคลาสธรรมดาแทน interface ในรายการอินเทอร์เฟซได้ อย่างไรก็ตาม ตัวออบเจ็กต์เป้าหมายอาจเป็นคลาส final ได้ หากเราเรียกผ่านอินเทอร์เฟซที่มัน implement

#8. Workshop 3: จับเวลาการเรียกด้วย Dynamic Proxy

บันทึกเป็นไฟล์ DynamicProxyDemo.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.Locale;

public class DynamicProxyDemo {

    public interface GreetingService {
        String greet(String name);
    }

    public static final class RealGreetingService
            implements GreetingService {
        @Override
        public String greet(String name) {
            if (name == null || name.isBlank()) {
                throw new IllegalArgumentException("Name is required");
            }
            return "Hello, " + name;
        }
    }

    public static void main(String[] args) {
        GreetingService target = new RealGreetingService();

        InvocationHandler handler = (proxy, method, methodArgs) -> {
            if (method.getDeclaringClass() == Object.class) {
                return switch (method.getName()) {
                    case "equals" -> proxy == methodArgs[0];
                    case "hashCode" -> System.identityHashCode(proxy);
                    case "toString" -> "TimingProxy[GreetingService]";
                    default -> throw new UnsupportedOperationException(
                            method.getName());
                };
            }

            long started = System.nanoTime();
            try {
                return method.invoke(target, methodArgs);
            } catch (InvocationTargetException e) {
                throw e.getCause();
            } finally {
                double elapsedMs =
                        (System.nanoTime() - started) / 1_000_000.0;
                System.out.printf(Locale.ROOT, "[Timing] %s: %.3f ms%n",
                        method.getName(), elapsedMs);
            }
        };

        GreetingService service = (GreetingService) Proxy.newProxyInstance(
                GreetingService.class.getClassLoader(),
                new Class<?>[] { GreetingService.class },
                handler);

        System.out.println(service.greet("Java"));
        System.out.println(service);
        System.out.println("Self equality: " + service.equals(service));
    }
}

รันด้วย:

javac -encoding UTF-8 DynamicProxyDemo.java
java DynamicProxyDemo

รูปแบบผลลัพธ์ด้านล่างเป็นเพียงตัวอย่าง ค่าเวลาจะแตกต่างกันในแต่ละรอบ:

[Timing] greet: 0.123 ms
Hello, Java
TimingProxy[GreetingService]
Self equality: true

#สิ่งที่เกิดขึ้นใน handler

  1. เมื่อเรียก service.greet("Java") JDK ส่งรายละเอียดเมธอดและอาร์กิวเมนต์เข้ามายัง handler [1][2]
  2. handler บันทึกเวลาเริ่มต้น แล้วใช้ method.invoke(target, methodArgs) เรียกออบเจ็กต์จริง
  3. ถ้าเมธอดเป้าหมายโยน exception การเรียกผ่าน reflection อาจห่อ exception ไว้ใน InvocationTargetException จึงดึง getCause() เพื่อส่งสาเหตุเดิมออกไป [3]
  4. finally บันทึกเวลาที่ผ่านไปทั้งกรณีสำเร็จและกรณีเกิด exception
  5. JDK ส่งการเรียก equals(), hashCode() และ toString() มายัง handler ด้วย ตัวอย่างจึงกำหนดพฤติกรรมเหล่านี้แยกไว้ โดยใช้ identity สำหรับ equality ของตัว Proxy [1]

หาก handler ส่ง checked exception ที่เมธอดในอินเทอร์เฟซไม่ประกาศรองรับ ผู้เรียกอาจได้รับ UndeclaredThrowableException จึงต้องออกแบบสัญญา exception ให้สอดคล้องกัน [2]

การจับเวลาด้วยวิธีนี้รวมค่าใช้จ่ายของ reflection บางส่วนและได้รับผลจากการ warm-up ของ JVM เหมาะสำหรับสังเกตการเรียกในตัวอย่าง ไม่ควรใช้ผลครั้งเดียวเป็นข้อสรุปเพื่อเปรียบเทียบประสิทธิภาพของอัลกอริทึม

#9. Proxy ต่างจาก Decorator และ Adapter อย่างไร

รูปแบบ เจตนาหลัก ความสัมพันธ์ของสัญญา ตัวอย่าง
Proxy ควบคุมการเข้าถึงหรือตัวแทนของออบเจ็กต์ มักมีสัญญาร่วมกับออบเจ็กต์จริง โหลดภาพเมื่อจำเป็น ตรวจสิทธิ์
Decorator เพิ่มหน้าที่หรือความสามารถให้สิ่งที่ห่ออยู่ คงสัญญาร่วมเพื่อประกอบความสามารถ เพิ่มการเข้ารหัสหรือบีบอัดข้อมูล
Adapter แปลงสัญญาของสิ่งเดิมให้เข้ากับสิ่งที่ผู้เรียกต้องการ เชื่อมสัญญาที่ต่างกัน แปลงบริการเก่าให้ใช้กับ interface ใหม่

Proxy และ Decorator อาจมีโครงสร้างคล้ายกันมาก เพราะต่างก็สามารถ implement อินเทอร์เฟซเดียวกับสิ่งที่ห่ออยู่และส่งต่อคำขอ การพิจารณาจึงต้องดูเจตนาของการออกแบบด้วย เช่น การจับเวลาอาจเขียนด้วยโครงสร้างคล้าย Decorator หรือด้วย Dynamic Proxy ก็ได้

#10. ข้อดีและข้อแลกเปลี่ยน

#ข้อดี

  • รวมกฎการเข้าถึงไว้ในออบเจ็กต์เฉพาะหน้าที่
  • เลื่อนการสร้างทรัพยากรไปจนกว่าจะจำเป็น
  • ช่วยให้ Client พึ่งพา interface แทนรายละเอียดของ Real Subject
  • เพิ่มการควบคุมโดยไม่ต้องแก้การทำงานหลักของ Real Subject ในหลายกรณี

#ข้อแลกเปลี่ยน

  • เพิ่มชั้นการเรียกและจำนวนองค์ประกอบที่ต้องดูแล
  • Lazy Loading ทำให้การเรียกครั้งแรกอาจช้ากว่าครั้งถัดไป
  • แคชต้องมีนโยบายอายุข้อมูล การล้างข้อมูล และ key ที่ถูกต้อง
  • การใช้หลาย thread ต้องออกแบบทั้งการสร้างและการเข้าถึง Real Subject
  • Remote Proxy ต้องจัดการความล่าช้า timeout และความล้มเหลวของเครือข่ายเพิ่มเติม เพราะมี interface เหมือนการเรียกในเครื่องไม่ได้ทำให้เครือข่ายมีพฤติกรรมเหมือนการเรียกในเครื่อง

#11. ข้อผิดพลาดที่ควรสังเกต

#สร้าง Real Subject เร็วเกินไป

หากเป้าหมายคือ Virtual Proxy แต่ constructor ของ Proxy เรียก new RealImage(...) ทันที การเลื่อนการโหลดก็จะไม่เกิดขึ้น ควรเก็บข้อมูลสำหรับสร้างและสร้างเมื่อถึงจุดที่ต้องใช้งาน

#เรียกข้าม Proxy

Protection Proxy ตรวจสิทธิ์ได้เฉพาะคำขอที่วิ่งผ่านมัน การแจก reference ของ Real Subject ให้ Client โดยตรงอาจทำให้การควบคุมถูกข้ามได้

#คิดว่ามี Proxy แล้วจะมีแคชหรือทำงานข้ามเครือข่ายให้เอง

รูปแบบ Proxy ไม่ได้ติดตั้งความสามารถเหล่านี้โดยอัตโนมัติ ผู้พัฒนาต้องเขียนหรือเลือกกลไกที่รองรับพฤติกรรมที่ต้องการ

#ใช้ this ภายใน Real Subject แล้วคาดว่าจะผ่าน Proxy

หาก Real Subject เรียกอีกเมธอดหนึ่งของตัวเองผ่าน this การเรียกนั้นจะเกิดบนตัว Real Subject โดยตรง ไม่ได้ย้อนผ่าน Proxy ภายนอก จึงไม่ถูก handler ของ Proxy ตัวอย่างนี้ดักซ้ำ

#เชื่อว่า Caching Proxy ใช้ key จากอาร์กิวเมนต์อย่างเดียวได้เสมอ

ข้อมูลที่ขึ้นอยู่กับผู้ใช้ สิทธิ์ tenant หรือภาษา อาจต้องรวมบริบทดังกล่าวไว้ใน key และตรวจสิทธิ์ก่อนคืนผลลัพธ์ด้วย ควรกำหนดนโยบายตามสัญญาของบริการ

#12. แบบฝึกหัดต่อยอด

  1. เพิ่มการเรียก image.display() เป็นสามครั้ง แล้วอธิบายว่าทำไม Loading image ยังเกิดครั้งเดียว
  2. สร้าง ImageProxy สองตัวด้วยชื่อไฟล์เดียวกัน แล้วเปรียบเทียบจำนวนครั้งที่โหลด
  3. เปลี่ยนกฎ canRead ให้ตรวจจากชุดสิทธิ์ที่แก้ไขได้ แล้วทดลองถอนสิทธิ์หลังอ่านรายงานสำเร็จครั้งแรก
  4. เรียก service.greet("") และจับ IllegalArgumentException เพื่อสังเกตว่า handler ส่งสาเหตุเดิมออกมาและยังพิมพ์เวลา
  5. ออกแบบ Caching Proxy สำหรับบริการค้นหาสินค้า โดยระบุ key อายุแคช และเงื่อนไขล้างข้อมูลก่อนเขียนโค้ด

#13. เกณฑ์ตรวจความเข้าใจ

เมื่อทำตัวอย่างเสร็จ ควรอธิบายได้ว่า Client อ้างอิงสัญญาใด Proxy ควบคุมอะไร และออบเจ็กต์จริงถูกเรียกเมื่อใด โดยแยกหน้าที่ของตัวแทนออกจากงานธุรกิจหลักอย่างชัดเจน

สำหรับ Virtual Proxy ผลที่ต้องสังเกตได้คือยังไม่โหลดรูปขณะสร้าง Proxy โหลดครั้งเดียวเมื่อเรียกครั้งแรก และยังแสดงรูปได้ทุกครั้งหลังจากนั้น ส่วน Protection Proxy ต้องไม่เรียกบริการจริงเมื่อผู้ใช้ไม่ได้รับอนุญาต

#เอกสารอ้างอิง

ตัวอย่าง Java ทั้งสามชุดเขียนขึ้นเพื่ออธิบายรูปแบบนี้โดยเฉพาะ ไม่ได้คัดลอกจากเอกสาร API

  1. Oracle, Proxy — Java SE 21 API: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/Proxy.html
  2. Oracle, InvocationHandler — Java SE 21 API: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/InvocationHandler.html
  3. Oracle, InvocationTargetException — Java SE 21 API: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/InvocationTargetException.html

อ้างอิง API รุ่น 21 เพื่ออธิบายกลไกของ JDK Dynamic Proxy ส่วนโค้ดในบทความใช้คุณสมบัติที่รองรับใน Java 17