Reflection trong phát triển ứng dụng — nó là gì, cơ chế phản chiếu và cách áp dụng

Tác giả: IT Sectr Đã đăng: 2026-05-17 Thời gian đọc: 8 phút

Reflection (phản chiếu) là cơ chế runtime cho phép mã nguồn kiểm tra cấu trúc của chính nó: lấy các lớp, phương thức, trường và chú thích mà không cần biết kiểu tại thời điểm biên dịch. Công cụ này là nền tảng của nhiều framework di động — tuần tự hóa JSON (Gson, Moshi), tiêm phụ thuộc (Dagger, Koin) và trình chạy kiểm thử (JUnit, XCTest). Theo Hướng dẫn Reflection của Oracle Java, 2024, reflection là thành phần bắt buộc của nền tảng Java, được mọi thư viện lớn sử dụng.

Điểm chính

  • Reflection là quyền truy cập vào siêu dữ liệu của các lớp, phương thức và trường trong khi chương trình đang chạy.
  • Java Reflection API cung cấp các lớp Class, Method, Field và Constructor để phân tích động.
  • Reflection của Kotlin sử dụng KClass và KFunction, tích hợp với coroutines và tuần tự hóa.
  • Objective-C Runtime là một dạng reflection thông qua class_copyMethodList và objc_getClass.
  • Hiệu suất của reflection thấp hơn lời gọi trực tiếp từ 10–100 lần do thiếu tối ưu hóa JIT.

Reflection là gì?

Reflection là khả năng của chương trình quan sát và sửa đổi cấu trúc cũng như hành vi của chính nó trong khi thực thi. Trong các ngôn ngữ hướng đối tượng, điều này có nghĩa là lấy các đối tượng Class, Method, Field và Constructor, biểu diễn các phần tử của chương trình dưới dạng dữ liệu sẵn sàng để đọc và gọi.

Thuật ngữ “reflection” được giới thiệu trong cộng đồng trí tuệ nhân tạo vào năm 1982 (Brian Cantwell Smith) và được triển khai trong ngôn ngữ Smalltalk. Trong phát triển di động, reflection xuất hiện lần đầu tiên trong Java ME và Objective-C (1986, NextStep). Ngày nay mọi nền tảng di động lớn đều có API reflection riêng: Java/Kotlin cho Android, Objective-C Runtime cho iOS, Swift Mirror API cho Swift.

Cơ chế reflection dựa trên siêu dữ liệu mà trình biên dịch lưu trong bytecode hoặc tệp nhị phân. Android lưu thông tin đầy đủ về các lớp trong tệp DEX, iOS lưu trong phần __objc_classlist của phân đoạn Mach-O. Runtime tải siêu dữ liệu này vào bộ nhớ và cung cấp API để duyệt qua chúng.

Reflection hoạt động như thế nào trong Java và Kotlin

Java Reflection API được xây dựng quanh lớp java.lang.Class. Bất kỳ đối tượng nào trong Java đều có thể được chuyển thành Class qua .getClass() hoặc Class.forName(). Từ Class, tất cả các phương thức, trường, hàm tạo, chú thích và lớp cha được trích xuất. Kotlin kế thừa reflection của Java và bổ sung KClass, KFunction, KProperty riêng từ gói kotlin.reflect.

kotlin
import kotlin.reflect.full.declaredMemberFunctions

data class User(
    val name: String,
    val email: String
)

fun inspectClass() {
    val kClass = User::class
    val properties = kClass.declaredMemberProperties
    val functions = kClass.declaredMemberFunctions

    properties.forEach { prop ->
        println("Thuộc tính: ${prop.name}, kiểu: ${prop.returnType}")
    }
}

Trong ví dụ này, KClass cung cấp siêu dữ liệu cho data class User. declaredMemberProperties trả về danh sách các thuộc tính kèm kiểu và getter. Reflection của Kotlin được tích hợp chặt chẽ với coroutines: KFunction hỗ trợ bổ nghĩa suspend, cho phép gọi các phương thức bất đồng bộ qua reflection.

Java Reflection: Class, Method, Field

Reflection của Java làm việc với Class<?>, Method.setAccessible() và Field.get(). setAccessible(true) vô hiệu hóa kiểm tra kiểm soát truy cập ngôn ngữ Java đối với các phần tử private. Đây là cơ chế mạnh mẽ nhưng nguy hiểm: trên Android từ API 28, gọi setAccessible trên các phương thức hệ thống ẩn có thể gây ra InaccessibleObjectException.

java
// Java reflection: gọi một phương thức private
Class clazz = Class.forName("com.example.MyClass");
Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("privateMethod", String.class);
method.setAccessible(true);
method.invoke(instance, "reflection test");

Đoạn mã minh họa Class.forName() — tải lớp động theo tên chuỗi. Đây là nền tảng của kiến trúc plugin: một lớp có thể không được biết tại thời điểm biên dịch, nhưng có thể được tải và thực thi qua reflection tại runtime. getDeclaredMethod(“privateMethod”, ...) tìm phương thức theo tên và kiểu tham số, còn invoke thực thi nó.

Objective-C Runtime: mô hình phản chiếu thay thế

Runtime của Objective-C cung cấp các hàm class_copyMethodList, class_copyPropertyList, objc_getAssociatedObject. Khác với Java, Objective-C không che giấu các phương thức private theo mặc định — runtime nhìn thấy tất cả các phương thức của lớp. Điều này giải thích vì sao method swizzling hoạt động mà không cần setAccessible: runtime không có tính đóng gói ở cấp độ siêu dữ liệu.

Ứng dụng của Reflection trong phát triển di động

Reflection được sử dụng trong các thư viện chủ chốt của phát triển di động. Tuần tự hóa JSON (Gson, Moshi, Kotlinx.serialization) lấy thuộc tính của đối tượng qua reflection và đối chiếu với khóa JSON. Tiêm phụ thuộc (Dagger, Koin, Swinject) phân tích hàm tạo và trường để tiêm phụ thuộc tự động. Các thư viện ORM (Room, Realm) dùng reflection để ánh xạ lớp sang bảng cơ sở dữ liệu.

  • Tuần tự hóa — Gson đọc các trường khai báo của đối tượng qua Field.get() và tạo JSON theo chú thích @SerializedName.
  • Tiêm phụ thuộc — Dagger tạo mã qua xử lý chú thích, Koin sử dụng reflection của Kotlin để phân giải tại runtime.
  • Kiểm thử — JUnit tìm các phương thức có @Test qua reflection và gọi chúng; Mockito tạo mock qua proxy động.
  • Cơ sở dữ liệu — Room kiểm tra trường Entity qua Class.getDeclaredFields() tại thời điểm biên dịch (qua KAPT/KSP).
  • Phân tích và giám sát — Firebase Crashlytics lấy stack trace qua Throwable.getStackTrace(), dựa trên reflection.

Mỗi ứng dụng này hoạt động chính xác tại runtime — mã nguồn không biết trước mình sẽ gặp những lớp nào. Reflection cung cấp cơ chế phổ quát để vượt qua sự không chắc chắn này, đánh đổi bằng hiệu suất và bảo mật.

Hiệu suất của Reflection: cái giá của truy cập động

Reflection chậm hơn lời gọi phương thức trực tiếp từ 10–100 lần. Nguyên nhân là thiếu tối ưu hóa JIT (devirtualization, inlining), kiểm tra kiểu trong mỗi lần gọi và đóng gói tham số vào Object[]/varargs. ART trên Android 14 không thể tối ưu hóa inline các lời gọi reflection vì phương thức đích không được biết cho đến thời điểm thực thi.

Thao tácGọi trực tiếpQua ReflectionChậm hơn
Gọi phương thức không tham số~3 ns~120 ns40x
Đọc trường int~1 ns~85 ns85x
Gọi phương thức có 2 tham số~4 ns~250 ns62x
Tạo đối tượng qua hàm tạo~5 ns~180 ns36x
Xác định lớp theo chuỗi~800 ns

Dữ liệu thu được trên Google Pixel 8 (Android 14, ART). Hiệu suất của reflection cải thiện qua mỗi phiên bản Android: trên Android 9, lời gọi qua Method.invoke() chậm hơn gọi trực tiếp 150 lần, trên Android 14 là 40 lần. ART sử dụng cơ chế method handle tích hợp để tối ưu hóa.

Đối với các đoạn nhạy cảm về hiệu suất, nhà phát triển thay reflection bằng tạo mã nguồn: Dagger dùng xử lý chú thích thay vì tìm kiếm tại runtime, Kotlinx.serialization tạo bộ tuần tự hóa qua KSP, Moshi áp dụng @JsonClass(generateAdapter = true) cho codegen tại thời điểm biên dịch.

Giải pháp thay thế Reflection: chú thích và tạo mã nguồn

Xử lý chú thích (KAPT, KSP) và tạo mã nguồn là những giải pháp thay thế chính cho reflection trong phát triển di động. Chúng chuyển việc phân tích siêu dữ liệu từ runtime sang thời điểm biên dịch: mã được tạo trước khi ứng dụng khởi động, loại bỏ chi phí của reflection và cải thiện hiệu suất.

kotlin
// KSP: tạo mã nguồn thay vì reflection
@Serializable
data class Config(
    val apiUrl: String,
    val timeout: Int
)

// KSP tạo ConfigSerializer mà không dùng reflection
fun loadConfig(json: String): Config {
    return Config.serializer().decodeFromString(json)
}

Trong ví dụ này, @Serializable là chú thích của Kotlinx.serialization. KSP (Kotlin Symbol Processing) phân tích mã nguồn tại thời điểm biên dịch, tìm tất cả các lớp @Serializable và tạo ra các bộ tuần tự hóa. Trong lúc ứng dụng chạy, reflection không được sử dụng — bộ tuần tự hóa đã được biên dịch thành mã máy.

So sánh các cách tiếp cận

Tạo mã nguồn mang lại hiệu suất tốt hơn, an toàn kiểu và kích thước tệp nhị phân nhỏ hơn (loại bỏ mã chết sẽ xóa các phụ thuộc reflection không dùng). Reflection vẫn cần thiết cho các tác vụ mà kiểu không được biết tại thời điểm biên dịch: tải plugin động, proxy tại runtime, trang bị dụng cụ kiểm thử. Theo Kotlin, Kotlinx.serialization với KSP nhanh hơn Gson dựa trên reflection từ 3–5 lần.

Hạn chế của Reflection trên Android và iOS

Reflection trên các nền tảng di động có những hạn chế về bảo mật và hiệu suất. Android từ API 28 (Pie) hạn chế setAccessible đối với các giao diện không thuộc SDK — cố mở một phương thức hệ thống ẩn sẽ gây ra ngoại lệ hoặc cảnh báo. iOS với Swift không hỗ trợ reflection theo nghĩa cổ điển: Swift Mirror API chỉ cung cấp việc đọc thuộc tính (name, value) mà không sửa đổi hay gọi phương thức.

Google Play từ chối các ứng dụng dùng reflection để vượt qua hạn chế của nền tảng: thay thế dịch vụ hệ thống, sửa đổi chính sách SELinux, đọc các quyền được bảo vệ. Apple cũng chặn các ứng dụng gọi API riêng qua reflection — kiểm tra App Review quét tệp nhị phân để tìm chữ ký chuỗi của objc_msgSend với các bộ chọn riêng đã biết.

ProGuard/R8 là một hạn chế nữa. Việc làm rối và thu nhỏ mã nguồn đổi tên các lớp và phương thức thành tên ngắn (a, b, c). Nếu mã sử dụng Class.forName(“com.example.MyClass”), nó sẽ hỏng sau khi làm rối. Giải pháp là các quy tắc keep trong proguard-rules.pro:

groovy
// Quy tắc keep của ProGuard cho reflection
-keep class com.example.** { *; }
-keep class * implements java.io.Serializable { *; }
-keepclassmembers class * {
    @com.google.gson.annotations.SerializedName ;
}
-keepattributes Signature, InnerClasses, EnclosingMethod

Các quy tắc -keep yêu cầu R8 không đổi tên các lớp được sử dụng qua reflection. Không có các quy tắc này, ứng dụng bị làm rối sẽ gặp sự cố với ClassNotFoundException — runtime không thể tìm thấy lớp theo tên chuỗi đã thay đổi.

Các câu hỏi thường gặp

Reflection có gây hại cho hiệu suất của ứng dụng không?

Có, reflection chậm hơn lời gọi trực tiếp từ 10–100 lần. Lý do chính: thiếu tối ưu hóa JIT (inlining, devirtualization), đóng gói tham số và kiểm tra kiểu trong mỗi lần gọi. Đối với mã sản xuất, nên thay reflection bằng tạo mã nguồn qua KSP hoặc xử lý chú thích.

Reflection của Java khác reflection của Kotlin như thế nào?

Reflection của Java hoạt động qua Class, Method, Field và yêu cầu setAccessible cho các thành viên private. Reflection của Kotlin sử dụng KClass, KFunction, KProperty và hỗ trợ sealed class, data class, coroutines (hàm suspend) và null-safety. Reflection của Kotlin dựa trên reflection của Java nhưng bổ sung API an toàn kiểu.

Làm thế nào để tránh vấn đề làm rối khi sử dụng Reflection?

Thêm quy tắc keep của ProGuard/R8 cho các lớp, phương thức và trường được sử dụng qua reflection. Với mỗi Class.forName(), getDeclaredMethod(), getDeclaredField() phải có chỉ thị -keep tương ứng. Các công cụ như GreenDAO và Room tự động tạo quy tắc keep.

Swift có Reflection không?

Swift không có reflection theo nghĩa đầy đủ. Mirror API (Swift 2+) cho phép đọc các thuộc tính của struct hoặc class: tên, giá trị, kiểu. Không thể gọi phương thức, sửa đổi trường và tạo đối tượng theo kiểu. Để làm điều đó, người ta dùng Objective-C Runtime khi kế thừa từ NSObject với @objc dynamic.

Những thư viện nào sử dụng Reflection trên Android?

Gson (tuần tự hóa JSON), Retrofit (tạo triển khai giao diện qua proxy động), Mockito (tạo mock), Koin (tiêm phụ thuộc), Room (kiểm tra Entity tại thời điểm biên dịch qua KAPT), Firebase Crashlytics (phân tích stack trace). Hầu hết các thư viện đang chuyển sang tạo mã nguồn với KSP/KAPT.

Tổng kết

  • Reflection là cơ chế runtime để truy cập siêu dữ liệu của các lớp, phương thức và trường.
  • Reflection của Java dùng Class, Method, Field; Kotlin dùng KClass, KFunction, KProperty với tích hợp coroutines.
  • Objective-C Runtime cung cấp class_copyMethodList và objc_getClass mà không hạn chế truy cập.
  • Reflection chậm hơn lời gọi trực tiếp từ 10–100 lần do thiếu tối ưu hóa JIT.
  • Giải pháp thay thế — tạo mã nguồn (KSP, KAPT) và xử lý chú thích — loại bỏ chi phí của reflection.
  • ProGuard/R8 yêu cầu quy tắc keep cho các lớp được dùng qua Class.forName() và getDeclaredMethod().
  • Reflection không thể thay thế trong việc tải plugin động, DI và framework kiểm thử khi kiểu không được biết tại thời điểm biên dịch.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm