พร็อกซีแบบไดนามิกด้วย InvocationHandler
สร้างพร็อกซีแบบไดนามิกของ JDK เพื่อดักจับการเรียกเมธอดของอินเทอร์เฟซสำหรับการบันทึกล็อก การแคช และการลองใหม่
พร็อกซีแบบไดนามิกด้วย InvocationHandler เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน
พร็อกซีแบบไดนามิกคืออะไร
พร็อกซีแบบไดนามิกของ JDK คือออบเจ็กต์ที่สร้างขึ้นขณะรันไทม์และนำอินเทอร์เฟซหนึ่งรายการหรือมากกว่าไปใช้ การเรียกเมธอดทั้งหมดจะถูกส่งต่อไปยัง InvocationHandler ที่คุณจัดเตรียมไว้
การสร้างพร็อกซีด้วย Proxy.newProxyInstance
เรียก Proxy.newProxyInstance(classLoader, interfaces[], handler) เพื่อสร้างพร็อกซี พร็อกซีจะนำอินเทอร์เฟซทั้งหมดที่ระบุไว้ไปใช้
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new LoggingHandler(realService)
);
proxy.findById(42L); // intercepted by LoggingHandlerอินเทอร์เฟซ InvocationHandler
นำ InvocationHandler ไปใช้เพื่อจัดการการเรียกเมธอดทุกครั้ง แฮนเดิลเลอร์จะได้รับออบเจ็กต์พร็อกซี เมธอดที่ถูกเรียก และอาร์กิวเมนต์
public class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">> " + method.getName());
Object result = method.invoke(target, args);
System.out.println("<< " + method.getName());
return result;
}
}พร็อกซีสำหรับการแคช
InvocationHandler สามารถแคชผลลัพธ์ได้ หากแคชมีผลลัพธ์สำหรับอาร์กิวเมนต์นั้นอยู่ ให้ส่งคืนผลลัพธ์ดังกล่าว มิฉะนั้นให้เรียกเมธอดจริงและจัดเก็บผลลัพธ์ไว้
public class CachingHandler implements InvocationHandler {
private final Object target;
private final Map<String, Object> cache = new HashMap<>();
public CachingHandler(Object target) { this.target = target; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String key = method.getName() + Arrays.toString(args);
return cache.computeIfAbsent(key, k -> {
try { return method.invoke(target, args); }
catch (Exception e) { throw new RuntimeException(e); }
});
}
}พร็อกซีสำหรับตรรกะการลองใหม่
ครอบการเรียกเมธอดไว้ในลูปการลองใหม่ หากเกิดข้อยกเว้น ให้ลองใหม่ได้ไม่เกิน N ครั้งก่อนโยนข้อยกเว้นนั้นอีกครั้ง
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
for (int attempt = 1; attempt <= 3; attempt++) {
try { return method.invoke(target, args); }
catch (InvocationTargetException e) {
if (attempt == 3) throw e.getCause();
Thread.sleep(1000L * attempt);
}
}
throw new RuntimeException("unreachable");
}พร็อกซีสำหรับการจัดการธุรกรรม
@Transactional ของสปริงใช้พร็อกซีแบบไดนามิกเพื่อเริ่มธุรกรรมก่อนการเรียกเมธอด และคอมมิตหรือย้อนกลับหลังจากนั้น โดยผู้เรียกไม่จำเป็นต้องทราบรายละเอียด
การตรวจสอบว่าออบเจ็กต์เป็นพร็อกซีหรือไม่
ใช้ Proxy.isProxyClass(obj.getClass()) เพื่อตรวจหาพร็อกซี และใช้ Proxy.getInvocationHandler(proxy) เพื่อรับแฮนเดิลเลอร์
if (Proxy.isProxyClass(service.getClass())) {
InvocationHandler h = Proxy.getInvocationHandler(service);
System.out.println("Handler: " + h.getClass().getSimpleName());
}ข้อจำกัดของพร็อกซีแบบไดนามิกของ JDK
พร็อกซีแบบไดนามิกของ JDK ทำงานได้เฉพาะกับอินเทอร์เฟซเท่านั้น ไม่สามารถสร้างพร็อกซีให้คลาสรูปธรรมได้ สำหรับการสร้างพร็อกซีจากคลาส ให้ใช้ CGLIB ซึ่งสปริงใช้ภายในกับคลาส @Configuration เช่นกัน
CGLIB เป็นทางเลือก
CGLIB สร้างคลาสย่อยของคลาสเป้าหมายขณะรันไทม์ โดยแทนที่เมธอดที่ไม่ใช่ final ทั้งหมด สปริงจะใช้วิธีนี้เมื่อไม่มีอินเทอร์เฟซ ไม่ต้องใช้แอนโนเทชัน และทำงานอย่างโปร่งใส
พร็อกซีใน Hibernate
Hibernate โหลดการเชื่อมโยงเอนทิตีแบบขี้เกียจโดยใช้พร็อกซี CGLIB/Byte Buddy เมื่อคุณเข้าถึงคอลเลกชันแบบขี้เกียจ พร็อกซีจะเรียกใช้คำสั่ง SQL
// Hibernate creates a proxy for:
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
// Accessing dept triggers a DB query via the proxyประสิทธิภาพของพร็อกซี
การกระจายการเรียกเมธอดผ่านพร็อกซีแบบไดนามิกช้ากว่าการเรียกโดยตรงประมาณ 5–10 เท่า สำหรับเส้นทางที่ทำงานบ่อยมาก ให้พิจารณาการสร้างโค้ดขณะคอมไพล์ด้วย APT หรือใช้ MethodHandles แทนพร็อกซีแบบไดนามิก
ตรวจสอบความเข้าใจ
ข้อจำกัดหลักของพร็อกซีแบบไดนามิกของ JDK คืออะไร
สรุป
พร็อกซีแบบไดนามิกของ JDK ดักจับการเรียกเมธอดของอินเทอร์เฟซผ่าน InvocationHandler ใช้สำหรับการบันทึกเหตุการณ์ การแคช การลองใหม่ และการจัดการธุรกรรม สำหรับการสร้างพร็อกซีจากคลาส ให้ใช้ CGLIB และควรแคชการสร้างพร็อกซี เนื่องจากกระบวนการนี้ใช้รีเฟลกชันและมีต้นทุน
คำถามที่พบบ่อย
บทเรียน “พร็อกซีแบบไดนามิกด้วย InvocationHandler” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “พร็อกซีแบบไดนามิกด้วย InvocationHandler” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “พร็อกซีแบบไดนามิกด้วย InvocationHandler”
สร้างพร็อกซีแบบไดนามิกของ JDK เพื่อดักจับการเรียกเมธอดของอินเทอร์เฟซสำหรับการบันทึกล็อก การแคช และการลองใหม่ คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “พร็อกซีแบบไดนามิกด้วย InvocationHandler” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม
ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การตรวจสอบคลาสด้วยรีเฟลกชัน
- การเรียกใช้เมธอดและการเข้าถึงฟิลด์
- พร็อกซีแบบไดนามิกด้วย InvocationHandler
- ประสิทธิภาพของรีเฟลกชันและทางเลือกอื่น