PDF:

Động lực học lập trình Java, Phần 2: Giới thiệu sự phản
chiếu
Sử dụng thông tin lớp trong thời gian chạy để khởi động việc lập trình
của bạn
Dennis Sosnoski
Nhà tư vấn
Sosnoski Software Solutions, Inc.
04 12 2009
Sự phản chiếu cho phép truy cập mã của bạn tới thông tin bên trong đối với các lớp được nạp
vào JVM và cho phép bạn viết mã để làm việc với các lớp được lựa chọn trong quá trình thực
hiện, không phải trong mã nguồn. Điều này tạo cho sự phản chiếu một công cụ quan trọng để
xây dựng các ứng dụng linh hoạt. Nhưng xem ra -- nếu được sử dụng không thích hợp, sự phản
chiếu có thể tốn kém. Trong Phần 2 của loạt bài của mình về bản chất của nền tảng Java, nhà tư
vấn phần mềm Dennis Sosnoski đưa ra một sự giới thiệu về cách sử dụng sự phản chiếu, cũng
như xem xét một số các chi phí liên quan. Bạn cũng sẽ tìm hiểu cách Java Reflection API (API
phản chiếu Java) cho phép bạn kết nối vào các đối tượng trong thời gian chạy.
Xem thêm bài trong loạt bài này
Trong "Động lực học lập trình Java, Phần 1," tôi đã cung cấp cho bạn một sự giới thiệu về các lớp
lập trình Java và nạp lớp. Bài viết đó mô tả một số tư liệu thông tin rộng lớn theo định dạng lớp
nhị phân Java. Trong bài viết tháng này, tôi sẽ trình bày những điều cơ bản về việc sử dụng Java
Reflection API để truy cập và sử dụng một số thông tin như vậy trong thời gian chạy. Để giúp duy trì
những điều này thú vị với các nhà phát triển, những người đã biết những điều cơ bản của sự phản
chiếu, tôi sẽ trình bày một cái nhìn về cách so sánh hiệu năng phản chiếu với truy cập trực tiếp.
Đừng bỏ lỡ phần còn lại của loạt bài này
Phần 1, "Các lớp Java và nạp lớp" (04.2003)
Phần 3, "Ứng dụng sự phản chiếu" (07.2003)
Phần 4, "Chuyển đổi lớp bằng Javassist" (09.2003)
Phần 5, "Việc chuyển các lớp đang hoạt động" (02.2004)
Phần 6, "Các thay đổi hướng-khía cạnh với Javassist" (03.2004)
Phần 7, "Kỹ thuật bytecode với BCEL" (04.2004)
Phần 8, "Thay thế sự phản chiếu bằng việc tạo mã" (06.2004)
Sử dụng sự phản chiếu khác với lập trình Java tiêu chuẩn ở chỗ nó làm việc với siêu dữ liệu -- dữ
liệu mô tả dữ liệu khác. Kiểu siêu dữ liệu cụ thể được truy cập bởi sự phản chiếu của ngôn ngữ Java
© Copyright IBM Corporation 2009
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Nhẫn hiệu đăng ký
Trang 1 của 14
developerWorks®
ibm.com/developerWorks/vn/
là sự mô tả về các lớp và các đối tượng bên trong JVM. Sự phản chiếu cho phép bạn truy cập trong
thời gian chạy đến một loạt các thông tin lớp. Thậm chí nó còn cho phép bạn đọc và viết các trường
và các phương thức gọi của một lớp được chọn trong thời gian chạy.
Sự phản chiếu là một công cụ mạnh. Nó cho phép bạn xây dựng mã linh hoạt, mã này có thể được
lắp ráp trong thời gian chạy mà không đòi hỏi các liên kết mã nguồn giữa các thành phần. Nhưng
một số khía cạnh của sự phản chiếu có thể khó hiểu. Trong bài này, tôi sẽ đi vào những lý do tại sao
bạn có thể không muốn sử dụng sự phản chiếu trong các chương trình của bạn, cũng như những lý
do tại sao bạn muốn. Sau khi bạn biết các sự thỏa hiệp, bạn có thể quyết định cho chính mình khi
những lợi ích có giá trị hơn những hạn chế.
Lớp của những người mới bắt đầu
Điểm khởi đầu để sử dụng sự phản chiếu luôn luôn là một cá thể java.lang.Class. Nếu bạn muốn
làm việc với một lớp định sẵn, thì ngôn ngữ Java cung cấp một phím tắt dễ dàng để có được cá thể
Class trực tiếp:
Class clas = MyClass.class;
Khi bạn sử dụng kỹ thuật này, tất cả các công việc liên quan đến việc nạp các lớp diễn ra ở hậu
trường. Tuy nhiên, nếu bạn cần phải đọc tên lớp trong thời gian chạy từ một số nguồn bên ngoài, thì
cách tiếp cận này không phải là sắp thực hiện. Thay vào đó, bạn cần phải sử dụng một trình nạp lớp
để tìm thông tin lớp. Dưới đây là một cách để thực hiện điều đó:
// "name" is the class name to load
Class clas = null;
try {
clas = Class.forName(name);
} catch (ClassNotFoundException ex) {
// handle exception case
}
// use the loaded class
Nếu lớp đã được nạp, bạn sẽ tìm lại các thông tin Class hiện có. Nếu lớp chưa được nạp, trình nạp
lớp sẽ nạp nó bây giờ và trả về cá thể lớp vừa mới được xây dựng.
Sự phản chiếu trên một lớp
Đối tượng Class mang đến cho bạn tất cả các kết nối cơ bản để truy cập phản chiếu đến siêu dữ
liệu lớp. Siêu dữ liệu này bao gồm các thông tin về chính lớp đó, chẳng hạn như gói và siêu lớp
của lớp đó, cũng như các giao diện được lớp đó triển khai thực hiện. Nó cũng bao gồm các chi tiết
về các hàm tạo, các trường và các phương thức được lớp đó xác định. Các mục sau cùng này là
những thứ hầu như thường được sử dụng trong lập trình, vì vậy tôi sẽ đưa ra một số ví dụ về làm việc
với chúng sau trong phần này.
Hỏi chuyên gia: Dennis Sosnoski về các vấn đề JVM và
bytecode
Đối với các ý kiến hay các câu hỏi về tài liệu được trình bày trong loạt bài này, cũng như bất
cứ điều gì khác có liên quan đến Java bytecode, định dạng lớp nhị phân Java hoặc các vấn
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 2 của 14
ibm.com/developerWorks/vn/
developerWorks®
đề JVM chung, hãy truy cập vào diễn đàn thảo luận JVM và Bytecode, do Dennis Sosnoski
kiểm soát.
Đối với mỗi một trong ba kiểu này của các thành phần lớp -- các hàm tạo (constructor), các trường
và các phương thức -- java.lang.Class cung cấp bốn cuộc gọi thể hiện sự phản chiếu riêng biệt để
truy cập thông tin theo nhiều cách khác nhau. Tất cả các cuộc gọi đi theo sau một dạng chuẩn. Đây
là một tập được sử dụng để tìm các hàm tạo:
• Constructor getConstructor(Class[] params) -- Tìm ra hàm tạo công khai bằng cách sử
dụng các kiểu tham số cụ thể.
• Constructor[] getConstructors() -- Tìm ra tất cả các hàm tạo công khai cho lớp đó.
• Constructor getDeclaredConstructor(Class[] params) -- Tìm ra hàm tạo (bất kể mức truy
cập) bằng cách sử dụng các kiểu tham số cụ thể.
• Constructor[] getDeclaredConstructors() -- Tìm ra tất cả các hàm tạo (bất kể mức truy cập)
cho lớp đó.
Mỗi một trong các cuộc gọi này trả về một hoặc nhiều cá thể java.lang.reflect.Constructor. Lớp
Constructor này định nghĩa một phương thức newInstance lấy một mảng các đối tượng làm đối số
duy nhất của nó, sau đó trả về một cá thể vừa được xây dựng của lớp gốc. Mảng các đối tượng là
các giá trị tham số sử dụng cho cuộc gọi hàm tạo. Như là một ví dụ về cách làm việc này, giả sử bạn
có một lớp TwoString với một hàm tạo lấy một cặp String, như thể hiện trong Liệt kê 1:
Liệt kê 1. Lớp được xây dựng từ cặp strings
public class TwoString {
private String m_s1, m_s2;
public TwoString(String s1, String s2) {
m_s1 = s1;
m_s2 = s2;
}
}
Mã được hiển thị trong Liệt kê 2 tìm ra hàm tạo và sử dụng nó để tạo một cá thể của lớp TwoString
khi sử dụng Strings "a" và "b":
Liệt kê 2. Cuộc gọi sự phản chiếu cho hàm tạo
Class[] types = new Class[] { String.class, String.class };
Constructor cons = TwoString.class.getConstructor(types);
Object[] args = new Object[] { "a", "b" };
TwoString ts = (TwoString)cons.newInstance(args);
Mã trong Liệt kê 2 bỏ qua một số kiểu có thể của các ngoại lệ đã kiểm tra được các phương thức
phản chiếu khác nhau đưa ra. Các ngoại lệ này được trình bày chi tiết trong các mô tả Javadoc
API, vậy để cho ngắn gọn, tôi để chúng ở ngoài các ví dụ này.
Trong khi tôi đang nói chủ đề về các hàm tạo, ngôn ngữ lập trình Java cũng định nghĩa một phương
thức phím tắt đặc biệt mà bạn có thể sử dụng để tạo một cá thể của một lớp bằng một hàm tạo noargument (hoặc mặc định). Phím tắt này được nhúng vào trong định nghĩa Class riêng của nó như
sau:
Object newInstance() -- Xây dựng cá thể mới khi sử dụng hàm tạo mặc định
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 3 của 14
developerWorks®
ibm.com/developerWorks/vn/
Mặc dù cách tiếp cận này chỉ cho phép bạn sử dụng một hàm tạo cụ thể, nó tạo một phím tắt rất
tiện lợi nếu đó là một thứ bạn muốn. Kỹ thuật này đặc biệt có ích khi làm việc với JavaBeans,
JavaBeans được dùng để xác định một hàm tạo công khai, không có đối số (no-argument).
Các trường của sự phản chiếu
Các cuộc gọi phản chiếu Class (lớp) nhằm truy cập thông tin về trường là tương tự như các cuộc gọi
được dùng để truy cập các hàm tạo, với tên trường được sử dụng thay cho một mảng của các kiểu
tham số:
•
•
•
•
Field getField(String name) -- Tìm ra trường công khai có tên.
Field[] getFields() -- Tìm ra tất cả các trường công khai của lớp đó.
Field getDeclaredField(String name) -- Tìm ra trường có tên được lớp đó khai
Field[] getDeclaredFields() -- Tìm ra tất cả các trường được lớp đó khai báo.
báo.
Mặc dù có sự tương đồng với các cuộc gọi hàm tạo, cũng có một sự khác biệt quan trọng khi nói
đến các trường: hai trường đầu tiên trả về các thông tin cho các trường công khai để có thể truy cập
chúng thông qua lớp đó -- ngay cả những lớp được thừa kế từ một lớp ông bà. Hai trường cuối trả
về các thông tin cho các trường được lớp đó khai báo trực tiếp -- không phân biệt các kiểu truy cập
của trường.
Các cá thể java.lang.reflect.Field được các cuộc gọi trả về định nghĩa các phương thức getXXX
và setXXX cho tất cả các kiểu nguyên thủy, cũng như các phương thức get và set chung làm việc
với các tham chiếu đối tượng. Nó cho bạn quyết định sử dụng một phương thức thích hợp dựa trên
kiểu trường thực tế, mặc dù các phương thức getXXX sẽ xử lý tự động các biến đổi mở rộng (như khi
sử dụng phương thức getInt để lấy ra một giá trị byte).
Liệt kê 3 cho thấy một ví dụ về việc sử dụng các phương thức phản chiếu trường, dưới dạng một
phương thức để tăng một trường int của một đối tượng theo tên:
Liệt kê 3. Tăng một trường bằng sự phản chiếu
public int incrementField(String name, Object obj) throws... {
Field field = obj.getClass().getDeclaredField(name);
int value = field.getInt(obj) + 1;
field.setInt(obj, value);
return value;
}
Phương thức này bắt đầu hiển thị một số tính linh hoạt có thể với sự phản chiếu. Thay vì làm việc
với một lớp cụ thể, incrementField sử dụng phương thức getClass của đối tượng được chuyển qua
để tìm thông tin lớp, sau đó trực tiếp tìm trường có tên trong lớp đó.
Các phương thức của sự phản chiếu
Sự phản chiếu Class gọi truy cập thông tin của phương thức rất giống với những sự phản chiếu được
sử dụng cho các hàm tạo và các trường:
• Method getMethod(String name, Class[] params) -- Tìm ra phương thức công khai có tên
bằng cách sử dụng các kiểu tham số cụ thể.
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 4 của 14
ibm.com/developerWorks/vn/
developerWorks®
• Method[] getMethods() -- Tìm ra tất cả các phương thức công khai của lớp.
• Method getDeclaredMethod(String name, Class[] params) -- Tìm ra phương thức công khai
có tên được lớp đó khai báo bằng cách sử dụng các kiểu tham số cụ thể.
• Method[] getDeclaredMethods() -- Tìm ra tất cả các phương thức được lớp đó khai báo.
Như với các cuộc gọi trường, hai phương thức đầu tiên trả về thông tin cho các phương thức công
khai có thể được truy cập thông qua lớp đó, ngay cả các lớp đó được thừa kế từ một lớp ông bà.
Hai phương thức cuối trả về thông tin cho các phương thức được lớp đó khai báo trực tiếp, mà
không liên quan đến kiểu truy cập của phương thức này.
Các cá thể java.lang.reflect.Method được các cuộc gọi trả về định nghĩa một phương thức
invoke (gọi) mà bạn có thể sử dụng để gọi phương thức đó trên một cá thể của lớp định nghĩa.
Phương thức invoke này lấy hai đối số cung cấp cá thể lớp và một mảng các giá trị tham số cho
cuộc gọi này.
Liệt kê 4 đưa ví dụ về trường tiến thêm một bước, khi hiển thị một ví dụ về sự phản chiếu của
phương thức đang hành động. Phương thức này làm tăng một thuộc tính int JavaBean được xác
định bằng phương thức get và set. Ví dụ, nếu đối tượng đã xác định phương thức getCount và
setCount cho một giá trị count (đếm) số nguyên, thì bạn có thể vượt qua "count" như tham số name
trong một cuộc gọi đến phương thức này để tăng giá trị đó.
Liệt kê 4. Làm tăng một thuộc tính JavaBean bằng sự phản chiếu
public int incrementProperty(String name, Object obj) {
String prop = Character.toUpperCase(name.charAt(0)) +
name.substring(1);
String mname = "get" + prop;
Class[] types = new Class[] {};
Method method = obj.getClass().getMethod(mname, types);
Object result = method.invoke(obj, new Object[0]);
int value = ((Integer)result).intValue() + 1;
mname = "set" + prop;
types = new Class[] { int.class };
method = obj.getClass().getMethod(mname, types);
method.invoke(obj, new Object[] { new Integer(value) });
return value;
}
Để thực hiện theo các quy ước JavaBeans, tôi biến đổi chữ cái đầu của thuộc tính tên thành chữ
hoa, sau đó dựa vào get để xây dựng tên phương thức đọc và set để xây dựng tên phương thức
viết. Các phương thức đọc JavaBeans chỉ trả về giá trị và viết các phương thức lấy giá trị làm tham
số duy nhất, vì vậy tôi chỉ định các kiểu tham số cho các phương thức cho phù hợp. Cuối cùng, quy
ước đòi hỏi các phương thức là công khai, vậy tôi sử dụng dạng tra cứu thông tin để tìm các phương
thức công khai có khả năng gọi được trên lớp đó.
Ví dụ này là một ví dụ đầu tiên mà tôi đã chuyển qua các giá trị nguyên thủy khi sử dụng sự phản
chiếu, vậy chúng ta hãy xem xét cách làm này. Nguyên tắc cơ bản rất đơn giản: bất cứ khi nào
bạn cần phải chuyển qua một giá trị nguyên thủy, chỉ cần thay thế một cá thể của lớp trình bao
(wrapper) tương ứng (được định nghĩa trong gói java.lang cho kiểu nguyên thủy đó. Điều này áp
dụng cho cả các cuộc gọi và cả các trả về. Vì vậy, khi tôi gọi phương thức get trong ví dụ của tôi, tôi
chờ đợi kết quả là một trình bao java.lang.Integer cho giá trị thuộc tính int thực sự.
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 5 của 14
developerWorks®
ibm.com/developerWorks/vn/
Phản chiếu các mảng
Các mảng là các đối tượng trong ngôn ngữ lập trình Java. Giống như tất cả các đối tượng, chúng
có các lớp. Nếu bạn có một mảng, bạn có thể nhận được lớp của mảng đó khi sử dụng phương thức
getClass chuẩn, cũng giống như với bất kỳ đối tượng khác. Tuy nhiên, việc nhận được lớp đó mà
không có một cá thể hiện có làm việc khác với các kiểu đối tượng khác. Ngay cả sau khi bạn có
một lớp mảng không có nhiều lớp bạn có thể làm việc trực tiếp với nó, việc truy cập hàm tạo được
sự phản chiếu cho các lớp thông thường đưa ra không làm việc với các mảng và các mảng không có
bất kỳ các trường nào dễ truy cập. Các phương thức java.lang.Object cơ bản chỉ được định nghĩa
cho đối tượng mảng.
Việc xử lý đặc biệt của các mảng sử dụng một tập hợp các phương thức tĩnh được lớp
java.lang.reflect.Array cung cấp. Các phương thức trong lớp này cho phép bạn tạo các mảng
mới, nhận được chiều dài của một đối tượng mảng và đọc và viết các giá trị có chỉ mục của một đối
tượng mảng.
Liệt kê 5 cho thấy một phương thức hữu ích để thay đổi kích thước một mảng hiện có một cách
hiệu quả. Nó sử dụng sự phản chiếu để tạo một mảng mới cùng kiểu, sau đó sao chép tất cả các dữ
liệu suốt từ mảng cũ trước khi trả về mảng mới.
Liệt kê 5. Phát triển một mảng bằng sự phản chiếu
public Object growArray(Object array, int size) {
Class type = array.getClass().getComponentType();
Object grown = Array.newInstance(type, size);
System.arraycopy(array, 0, grown, 0,
Math.min(Array.getLength(array), size));
return grown;
}
An ninh và sự phản chiếu
An ninh có thể là một vấn đề phức tạp khi đối phó với sự phản chiếu. Mã kiểu-khung công tác
thường sử dụng sự phản chiếu và với điều này bạn có thể muốn khung công tác có truy cập đầy đủ
tới mã của bạn mà không cần quan tâm về các hạn chế truy cập thông thường. Tuy vậy việc truy cập
không kiểm soát được có thể tạo những nguy cơ an ninh chính trong các trường hợp khác, chẳng
hạn như khi mã được thi hành trong một môi trường được chia sẻ bởi mã không đáng tin cậy.
Do các nhu cầu xung đột nhau, nên ngôn ngữ lập trình Java định nghĩa một cách tiếp cận đa cấp
để xử lý an ninh phản chiếu. Các chế độ cơ bản là bắt tuân theo các hạn chế như nhau trên sự phản
chiếu như đã áp dụng cho việc truy cập mã nguồn:
• Truy cập từ bất cứ ở đâu tới các thành phần công khai của lớp.
• Không truy cập bên ngoài lớp riêng của nó tới các thành phần riêng.
• Truy cập có giới hạn tới các thành phần được bảo vệ và các thành phần gói (truy cập mặc
định).
Tuy nhiên có một cách đơn giản xung quanh các hạn chế này -- thỉnh thoảng có. Tất cả các lớp
Constructor, Field, và Method mà tôi đã sử dụng trong các ví dụ trước đó mở rộng một lớp cơ
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 6 của 14
ibm.com/developerWorks/vn/
developerWorks®
sở chung -- lớp java.lang.reflect.AccessibleObject. Lớp này định nghĩa một phương thức
setAccessible cho phép bạn bật hoặc tắt kiểm tra truy cập cho một cá thể của một trong những lớp
này. Việc bắt giữ duy nhất (catch) là nếu có một trình quản lý an ninh, nó sẽ kiểm tra xem mã tắt
kiểm tra truy cập có cho phép làm như vậy không. Nếu không cho phép, trình quản lý an ninh đưa
ra một lỗi ngoại lệ.
Liệt kê 6 giải thích một chương trình có sử dụng sự phản chiếu trên một cá thể của lớp TwoString
của Liệt kê 1 để hiển thị điều này đang hoạt động:
Liệt kê 6. An ninh phản chiếu đang hoạt động
public class ReflectSecurity {
public static void main(String[] args) {
try {
TwoString ts = new TwoString("a", "b");
Field field = clas.getDeclaredField("m_s1");
//
field.setAccessible(true);
System.out.println("Retrieved value is " +
field.get(inst));
} catch (Exception ex) {
ex.printStackTrace(System.out);
}
}
}
Nếu bạn biên dịch mã này và chạy nó trực tiếp từ dòng lệnh mà không có bất kỳ tham số đặc biệt
nào, nó sẽ đưa ra một IllegalAccessException trên cuộc gọi field.get(inst). Nếu bạn không
ghi chú dòng field.setAccessible(true) ), sau đó biên dịch lại và chạy mã đó, nó sẽ thành công.
Cuối cùng, nếu bạn thêm tham số JVM -Djava.security.manager trên dòng lệnh để kích hoạt một
trình quản lý an ninh, một lần nữa nó sẽ không thành công, trừ khi bạn xác định các quyền truy cập
cho lớp ReflectSecurity.
Hiệu năng phản chiếu
Sự phản chiếu là một công cụ mạnh, nhưng bị một vài hạn chế. Một trong những nhược điểm chính
là ảnh hưởng về hiệu năng. Khi sử dụng sự phản chiếu về cơ bản là một hoạt động được giải thích,
ở đó bạn ra lệnh cho JVM những gì bạn muốn làm và nó thực hiện nó cho bạn. Kiểu hoạt động này
sẽ luôn chậm hơn so với việc thực hiện trực tiếp cùng một hoạt động. Để giải thích các chi phí hiệu
năng về việc sử dụng phản chiếu, tôi đã chuẩn bị một tập các chương trình chuẩn cho bài này (xem
Tài nguyên để có một đường liên kết đến mã đầy đủ).
Liệt kê 7 cho thấy một đoạn trích từ việc thử nghiệm hiệu năng truy cập trường, bao gồm các
phương pháp thử nghiệm cơ bản. Mỗi phương thức thử nghiệm một dạng truy cập vào các trường -accessSame làm việc với các trường thành viên của cùng một đối tượng, accessOther sử dụng các
trường của đối tượng khác được truy cập trực tiếp và accessReflection sử dụng các trường của đối
tượng khác được sự phản chiếu truy cập. Trong mỗi trường hợp, các phương pháp thực hiện các
tính toán như nhau -- một chuỗi cộng/nhân đơn giản trong một vòng lặp.
Liệt kê 7. Mã thử nghiệm hiệu năng truy cập trường
public int accessSame(int loops) {
m_value = 0;
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 7 của 14
developerWorks®
ibm.com/developerWorks/vn/
for (int index = 0; index < loops; index++) {
m_value = (m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return m_value;
}
public int accessReference(int loops) {
TimingClass timing = new TimingClass();
for (int index = 0; index < loops; index++) {
timing.m_value = (timing.m_value + ADDITIVE_VALUE) *
MULTIPLIER_VALUE;
}
return timing.m_value;
}
public int accessReflection(int loops) throws Exception {
TimingClass timing = new TimingClass();
try {
Field field = TimingClass.class.
getDeclaredField("m_value");
for (int index = 0; index < loops; index++) {
int value = (field.getInt(timing) +
ADDITIVE_VALUE) * MULTIPLIER_VALUE;
field.setInt(timing, value);
}
return timing.m_value;
} catch (Exception ex) {
System.out.println("Error using reflection");
throw ex;
}
}
Chương trình thử nghiệm gọi mỗi phương thức lặp lại nhiều lần với số đếm vòng lặp lớn, lấy trung
bình các phép đo thời gian trên một số các cuộc gọi. Thời gian cho cuộc gọi đầu tiên tới mỗi
phương thức không được bao gồm trong giá trị trung bình, để cho thời gian khởi chạy không phải
là một tham số trong các kết quả. Trong các hoạt động thử nghiệm cho bài viết này, tôi đã sử dụng
một số đếm vòng lặp là 10 triệu cho mỗi cuộc gọi, chạy trên một hệ thống PIIIm 1GHz. Các kết quả
tạo thời gian của tôi với ba JVM Linux khác nhau được thể hiện trong Hình 1. Tất cả các thử nghiệm
đã sử dụng các giá trị cài đặt mặc định cho từng JVM.
Hình 1. Các thời gian truy cập trường
Thang đo logarit của biểu đồ trên hiển thị toàn bộ dải thời gian, nhưng làm giảm tác động trực
quan của các sự khác biệt. Trong trường hợp của hai tập hợp số liệu đầu tiên (các Sun JVM), thời
gian thực hiện khi sử dụng sự phản chiếu là lớn hơn 1000 lần so với khi sử dụng truy cập trực tiếp.
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 8 của 14
ibm.com/developerWorks/vn/
developerWorks®
IBM JVM theo so sánh tốt hơn một chút, nhưng phương thức phản chiếu vẫn còn mất dài hơn 700
lần so với các phương thức khác. Không có các sự khác biệt đáng kể nào theo thời gian giữa hai
phương thức khác trên bất kỳ JVM nào, mặc dù IBM JVM đã chạy nhanh gần như gấp đôi so với
các Sun JVM. Có khả năng, sự khác biệt này phản chiếu sự tối ưu hóa chuyên dụng được các Sun
Hot Spot JVM sử dụng, chúng có xu hướng thực hiện không tốt theo tiêu chuẩn đơn giản.
Bên cạnh các thử nghiệm thời gian truy cập trường, tôi đã thực hiện cùng một loại thử nghiệm tính
giờ cho các cuộc gọi phương thức. Với các cuộc gọi phương thức, tôi đã thử ba sự thay đổi truy cập
giống như với việc truy cập trường, với biến thêm vào cho việc sử dụng các phương thức không đối
số so với cho qua và trả về một giá trị theo các cuộc gọi phương thức. Liệt kê 8 cho thấy mã với ba
phương thức được sử dụng để thử nghiệm dạng giá trị được cho qua và trả về của các cuộc gọi.
Liệt kê 8. Mã thử nghiệm hiệu năng truy cập phương thức
public int callDirectArgs(int loops) {
int value = 0;
for (int index = 0; index < loops; index++) {
value = step(value);
}
return value;
}
public int callReferenceArgs(int loops) {
TimingClass timing = new TimingClass();
int value = 0;
for (int index = 0; index < loops; index++) {
value = timing.step(value);
}
return value;
}
public int callReflectArgs(int loops) throws Exception {
TimingClass timing = new TimingClass();
try {
Method method = TimingClass.class.getMethod
("step", new Class [] { int.class });
Object[] args = new Object[1];
Object value = new Integer(0);
for (int index = 0; index < loops; index++) {
args[0] = value;
value = method.invoke(timing, args);
}
return ((Integer)value).intValue();
} catch (Exception ex) {
System.out.println("Error using reflection");
throw ex;
}
}
Hình 2 cho thấy kết quả tính thời gian của tôi cho các cuộc gọi phương thức. Ở đây một lần nữa,
sự phản chiếu chậm hơn nhiều so với khả năng trực tiếp. Các khác biệt này không khá lớn như với
trường hợp truy cập trường, mặc dù có phạm vi từ chậm hơn vài trăm lần trên Sun 1.3.1 JVM đến
ít hơn 30 lần chậm hơn trên IBM JVM cho trường hợp không đối số. Hiệu năng thử nghiệm cho các
cuộc gọi phương thức phản chiếu với các đối số là chậm hơn đáng kể so với các cuộc gọi không
đối số trên tất cả các JVM. Điều này một phần có lẽ là do trình bao java.lang.Integer cần thiết
cho giá trị int được chuyển qua và được trả về. Do các Integer là không thay đổi, một cái mới cần
được tạo cho mỗi lần trả về phương thức, bổ sung thêm chi phí hoạt động đáng kể.
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 9 của 14
developerWorks®
ibm.com/developerWorks/vn/
Hình 2. Các thời gian gọi phương thức
Hiệu năng phản chiếu đã là một vùng trọng tâm cho Sun khi phát triển các JVM 1.4, nó cho thấy
trong các kết quả gọi phương thức phản chiếu. Sun JVM 1.4.1 cho thấy hiệu năng được cải thiện
đáng kể so với phiên bản 1.3.1 cho kiểu hoạt động này, chạy nhanh hơn khoảng bảy lần trong các
lần thử nghiệm của tôi. IBM 1.4.0 JVM lại đưa ra hiệu năng tốt hơn cho lần thử nghiệm đơn giản
này, tuy vậy, chạy nhanh hơn 2 đến 3 lần so với Sun 1.4.1 JVM.
Tôi cũng đã viết một chương trình thử nghiệm tính giờ tương tự để tạo các đối tượng sử dụng phản
chiếu. Tuy nhiên, các sự khác nhau với trường hợp này gần như không đáng kể như với trường
hợp gọi trường và trường hợp gọi phương thức. Việc xây dựng một cá thể java.lang.Object đơn
giản với một cuộc gọi newInstance() tốn thời gian nhiều hơn khoảng 12 lần so với việc sử dụng
new Object() trên Sun 1.3.1 JVM, nhiều hơn khoảng bốn lần trên IBM 1.4.0 JVM và chỉ nhiều hơn
khoảng hai lần trên Sun 1.4.1 JVM. Việc xây dựng một mảng khi sử dụng Array.newInstance(type,
size) phải mất tối đa là khoảng hai lần dài hơn khi sử dụng new type[size] với bất kỳ JVM thử
nghiệm nào, với sự khác biệt giảm xuống khi kích thước mảng tăng lên.
Tóm tắt phản chiếu
Sự phản chiếu của ngôn ngữ Java đưa ra một cách rất linh hoạt về các thành phần của chương trình
liên kết động. Nó cho phép chương trình của bạn tạo và vận hành các đối tượng của bất kỳ các lớp
(dễ bị các hạn chế về an ninh) mà không cần phải mã cố định (hardcode) các lớp đích trước thời
hạn. Các tính năng này làm cho sự phản chiếu đặc biệt có ích để tạo các thư viện làm việc với các
đối tượng theo những cách rất chung chung. Ví dụ sự phản chiếu thường được sử dụng trong khung
công tác vẫn tồn tại đối tượng cho các cơ sở dữ liệu, XML, hoặc các định dạng bên ngoài khác.
Sự phản chiếu cũng có hai hạn chế. Một là vấn đề hiệu năng. Sự phản chiếu chậm hơn nhiều so với
mã trực tiếp khi sử dụng để truy cập trường và phương thức. Các vấn đề này ở mức độ nào đó phụ
thuộc vào cách sự phản chiếu được sử dụng trong một chương trình. Nếu nó được sử dụng như một
phần tương đối ít xảy ra về hoạt động của chương trình, thì hiệu năng chậm sẽ không phải là một
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 10 của 14
ibm.com/developerWorks/vn/
developerWorks®
mối quan tâm. Ngay cả các số liệu thời gian trong trường hợp xấu nhất trong thời gian thử nghiệm
của tôi đã cho thấy các hoạt động phản chiếu chỉ mất có một vài micro giây. Các vấn đề về hiệu
năng chỉ trở thành một mối quan tâm thật sự nếu sự phản chiếu được sử dụng trong logic cốt lõi của
các ứng dụng hiệu năng-tới hạn.
Một hạn chế đáng sợ hơn cho nhiều ứng dụng là việc sử dụng sự phản chiếu có thể che khuất những
gì thực sự sẽ xảy ra bên trong mã của bạn. Các lập trình viên mong đợi nhìn thấy logic của một
chương trình trong mã nguồn và các kỹ thuật như là sự phản chiếu mà nó bỏ qua các mã nguồn có
thể tạo những vấn đề về bảo trì. Mã phản chiếu cũng phức tạp hơn so với các mã trực tiếp tương
ứng, như có thể được thấy trong các ví dụ mã từ các so sánh hiệu năng. Các cách tốt nhất để đối
phó với những vấn đề này là sử dụng phản chiếu ít đi -- chỉ ở những nơi mà nó thực sự bổ sung
thêm tính linh hoạt có ích -- và dẫn chứng tài liệu sử dụng của nó bên trong các lớp đích.
Trong lần cài đặt tiếp theo, tôi sẽ cho ví dụ chi tiết hơn về việc sử dụng sự phản chiếu như thế nào.
Ví dụ này cung cấp một API để xử lý các tham số trên dòng lệnh của một ứng dụng Java, một
công cụ bạn có thể thấy có ích cho ứng dụng của riêng bạn. Nó cũng được xây dựng trên những thế
mạnh của sự phản chiếu trong khi tránh được các điểm yếu. Sự phản chiếu có làm đơn giản hóa việc
xử lý dòng lệnh của bạn? Tìm thấy trong phần 3 của loạt bài Động lực học lập trình Java .
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 11 của 14
developerWorks®
ibm.com/developerWorks/vn/
Các tải về
Mô tả
Tên
Kích thước
j-dyn0603.zip
12KB
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 12 của 14
ibm.com/developerWorks/vn/
developerWorks®
Tài nguyên
• Việc sử dụng rộng rãi sự phản chiếu có thể gây tác hại cho hiệu năng của khung công tác. Với
một ví dụ đồ họa và một số cuộc thảo luận liên quan, xem các bài viết liên kết dữ liệu (data
binding) XML của tác giả "Liên kết dữ liệu, Phần 2: Hiệu năng" (developerWorks, 01.2003) và
"Liên kết dữ liệu, Phần 3: kiến trúc JiBX" (developerWorks, 04.2003).
• Để có hướng dẫn sâu về cách sử dụng sự phản chiếu, hãy thử làm theo API phản chiếu trong
Hướng dẫn Java của Sun.
• Tìm thêm hàng trăm tài nguyên công nghệ Java trên vùng công nghệ Java của
developerWorks.
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 13 của 14
developerWorks®
ibm.com/developerWorks/vn/
Đôi nét về tác giả
Dennis Sosnoski
Dennis Sosnoski là một nhà tư vấn và nhà trợ giúp đào tạo chuyên về các dịch vụ
Web và SOA dựa trên-Java. Kinh nghiệm phát triển phần mềm chuyên nghiệp của ông
trải suốt hơn 30 năm qua, với một thập kỉ cuối tập trung vào các công nghệ XML và
Java phía máy chủ. Dennis là nhà phát triển hàng đầu về dụng cụ liên kết dữ liệu XML
JiBX mã nguồn mở, cũng là một người có duyên nợ với khung công tác của các dịch
vụ Web Apache Axis2. Ông cũng là một trong những thành viên của nhóm chuyên gia
đặc tả kỹ thuật của Jax-WS 2.0 và JAXB 2.0. Xem trang web của ông để có thông tin
về các dịch vụ đào tạo và tư vấn của ông.
© Copyright IBM Corporation 2009
(www.ibm.com/legal/copytrade.shtml)
Nhẫn hiệu đăng ký
(www.ibm.com/developerworks/vn/ibm/trademarks/)
Động lực học lập trình Java, Phần 2: Giới thiệu sự phản chiếu
Trang 14 của 14