Động lực học lập trình Java, Phần 1: Các lớp Java và việc nạp các lớp Quan sát các lớp và những gì xảy ra khi chúng được một JVM nạp - Pdf 20

Động lực học lập trình Java, Phần 1: Các lớp Java và việc nạp các lớp
Quan sát các lớp và những gì xảy ra khi chúng được một JVM nạp
Dennis Sosnoski, Nhà tư vấn, Sosnoski Software Solutions, Inc.
Tóm tắt: Hãy xem xét những gì xảy ra ở hậu trường về việc thực hiện ứng dụng
Java của bạn trong loạt bài viết mới về các khía cạnh động của lập trình Java.
Chuyên gia Java doanh nghiệp Dennis Sosnoski đưa ra tin số dẻo về định dạng lớp
nhị phân Java và những gì xảy ra với các lớp bên trong JVM. Trong bài này, ông
còn trình bày các vấn đề nạp lớp nằm trong phạm vi từ số lượng các lớp cần thiết
để chạy một ứng dụng Java đơn giản đến các xung đột trình nạp lớp, mà chúng có
thể gây ra các vấn đề trong J2EE và các kiến trúc phức tạp tương tự.
Bài viết này mở đầu một loạt bài viết mới trình bày một họ các chủ đề mà tôi gọi
là động lực học lập trình Java. Các chủ đề gồm từ cấu trúc cơ sở của định dạng
tệp lớp nhị phân Java, thông qua truy cập siêu dữ liệu trong thời gian chạy bằng
cách sử dụng sự phản chiếu, tất cả các cách để sửa đổi và xây dựng các lớp mới
trong thời gian chạy. Các chủ đề chung chạy xuyên suốt tất cả tài liệu này là ý
tưởng trong đó việc lập trình nền tảng Java là năng động nhiều hơn làm việc với
các ngôn ngữ biên dịch thẳng với mã gốc. Nếu bạn hiểu những khía cạnh năng
động này, bạn có thể làm nhiều thứ với lập trình Java mà không thể khớp với bất
kì ngôn ngữ lập trình chủ đạo nào khác.
Trong bài viết này, tôi trình bày một số các khái niệm cơ bản làm nền tảng cho các
tính năng động này của nền tảng Java. Các khái niệm này xoay quanh định dạng
nhị phân được sử dụng để biểu diễn các lớp Java, gồm cả những gì sẽ xảy ra khi
những lớp này được nạp vào trong JVM. Tài liệu này không chỉ cung cấp một nền
móng cho phần còn lại của các bài viết trong loạt này, mà nó còn giải thích một số
các mối quan tâm rất thiết thực cho các nhà phát triển đang làm việc trên nền tảng
Java.
Một lớp dưới dạng mã nhị phân
Các nhà phát triển đang làm việc trong ngôn ngữ Java thường không phải bận tâm
đến các chi tiết về những gì xảy ra với mã nguồn của họ khi nó được chạy qua
trình biên dịch. Trong loạt bài này, tôi sắp trình bày rất nhiều các chi tiết ở hậu
trường liên quan đến việc sẽ xảy ra từ mã nguồn để thực hiện chương trình, tuy

0090: 672f 4f62 6a65 6374 0100 106a 6176 612f g/Object java/
00a0: 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 lang/System ou Bên trong mã nhị phân
Điều đầu tiên trong việc biểu diễn lớp nhị phân được thể hiện trong Liệt kê 1 là
chữ kí "quán cafe babe" để xác nhận định dạng lớp nhị phân Java (và ngẫu nhiên
dùng như là một nhưng phần lớn không được thừa nhận chứng cứ bền vững
cho những người pha cà phê (baristas) làm việc chăm chỉ, những người giữ tinh
thần cho các nhà phát triển đang xây dựng nền tảng Java). Chữ kí này là một cách
dễ dàng để xác minh rằng một khối dữ liệu thực sự đưa ra yêu cầu là một cá thể
của định dạng lớp Java. Mỗi lớp nhị phân Java, thậm chí một lớp không có trên hệ
thống tệp tin đó, cần bắt đầu bằng bốn byte này.
Đừng bỏ lỡ phần còn lại của loạt bài này
Phần 2, "Giới thiệu sự phản chiếu" (June 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)
Phần còn lại của dữ liệu ít thú vị. Sau chữ kí là một cặp các số phiên bản định
dạng lớp (trong trường hợp này, phiên bản phụ 0 và phiên bản chính 46 hệ đếm
mười sáu 0x2e như được tạo ra bởi các javac 1.4.1), sau đó một số đếm các lối

mở rộng sử dụng các thuộc tính để phục vụ cho các mục đích khác trong tương lai,
như là việc cung cấp siêu-thông tin được các khung công tác cần để làm việc với
các lớp của người sử dụng một cách tiếp cận mà các ngôn ngữ C# có nguồn gốc
Java đã sử dụng rộng rãi. Thật không may, vẫn chưa có các kết nối nào được cung
cấp để tiến hành sử dụng tính linh hoạt này ở mức người dùng. Bytecode và các ngăn xếp
Bytecode chiếm phần thực thi của tệp lớp trên thực tế là mã máy cho một loại máy
tính đặc biệt JVM. Máy này được gọi là một máy ảo vì nó được thiết kế để thực
hiện trong phần mềm hơn là phần cứng. Mỗi JVM được sử dụng để chạy các ứng
dụng nền tảng Java được xây dựng xung quanh một việc triển khai thực hiện của
máy này.
Máy ảo này thực sự khá đơn giản. Nó sử dụng một kiến trúc ngăn xếp, có nghĩa là
các toán hạng lệnh được nạp vào một ngăn xếp bên trong trước khi chúng được sử
dụng. Tập lệnh gồm tất cả các phép toán số học và logic bình thường, cùng với các
nhánh có điều kiện và không điều kiện, nạp/lưu trữ, gọi/trả về, thao tác ngăn xếp
và một số các kiểu lệnh đặc biệt. Một số lệnh gồm các giá trị toán hạng cụ thể
được mã hóa trực tiếp vào trong các lệnh. Những cái khác trực tiếp tham chiếu các
giá trị từ nhóm hằng số.
Mặc dù máy ảo đơn giản, những việc thực hiện không nhất thiết phải như vậy. Các
JVM ban đầu (thế hệ đầu tiên) về cơ bản đã là các trình dịch cho bytecode của
máy ảo. Trên thực tế chúng tương đối đơn giản, nhưng bị mắc phải vấn đề về hiệu
năng nghiêm trọng ; việc thông dịch mã luôn luôn sẽ mất nhiều thời gian hơn so
với thực hiện mã gốc. Để giảm các vấn đề hiệu năng này, các JVM thế hệ thứ hai
được bổ sung việc dịch ngay (JIT). Kỹ thuật JIT biên dịch bytecode Java thành mã
gốc trước khi thực hiện nó cho lần đầu tiên, cho phép hiệu năng tốt hơn với nhiều
lần thực hiện lặp lại. Các JVM thế hệ hiện nay thậm chí còn đi xa hơn nữa, bằng
cách sử dụng các kỹ thuật thích nghi để theo dõi việc thực hiện chương trình và
chọn lựa tối ưu hóa các mã sử dụng nhiều.

{
public static void main(String[] args) {
System.out.println("**beginning execution**");
Greeter greeter = new Greeter();
System.out.println("**created Greeter**");
greeter.greet();
}
}

public class Greeter
{
private static Message s_message = new Message("Hello, World!");

public void greet() {
s_message.print(System.out);
}
}

public class Message
{
private String m_text;

public Message(String text) {
m_text = text;
}

public void print(java.io.PrintStream ps) {
ps.println(m_text);
}
}


Đây chỉ là một phần danh sách của các phần quan trọng nhất dấu vết đầy đủ
gồm 294 dòng, mà với danh sách này tôi đã xóa hầu hết. Tập đầu tiên của việc nạp
lớp (trong trường hợp này là 279) tất cả được kích hoạt bằng nỗ lực để nạp lớp
Demo. Đây là các lớp cốt lõi được tất cả các chương trình Java sử dụng, không kể
nhỏ bao nhiêu. Ngay cả việc loại bỏ tất cả các mã khỏi phương thức Demo main
không ảnh hưởng đến trình tự nạp ban đầu này. Tuy vậy, số lượng và tên của các
lớp được bao hàm sẽ khác giữa một phiên bản của thư viện lớp này với một phiên
bản của thư viện lớp khác.
Phần liệt kê sau khi lớp Demo được nạp sẽ thú vị hơn. Trình tự ở đây cho thấy lớp
Greeter được nạp sẽ thú vị hơn. Trình tự ở đây cho thấy lớp Greeter sử dụng một
cá thể tĩnh của lớp Message, nên trước khi một cá thể của lớp trước có thể được
tạo ra, các lớp sau này cũng cần phải được nạp.
Có nhiều việc xảy ra bên trong JVM khi một lớp được nạp và được khởi tạo, bao
gồm việc giải mã định dạng lớp nhị phân, kiểm tra tính tương thích với các lớp
khác, xác minh chuỗi các phép toán bytecode và cuối cùng là xây dựng một cá thể
java.lang.Class để biểu diễn lớp mới. Đối tượng Class này trở thành cơ sở cho tất
cả các cá thể của lớp mới được JVM tạo ra. Đó cũng là trình định danh cho lớp đã
tự nạp bạn có thể có nhiều bản sao của cùng một lớp nhị phân đã nạp trong một
JVM, mỗi bản sao có cá thể Class riêng của nó. Mặc dù tất cả các bản sao này chia
sẻ cùng một tên lớp, nhưng chúng sẽ là các lớp riêng biệt với JVM.
Tắt đường dẫn (lớp) theo lối mòn (beaten)
Các trình nạp lớp (class loaders) điều khiển lớp đang nạp vào một JVM. Có một
trình nạp lớp tự mồi (bootstrap) được xây dựng bên trong JVM, nó có trách nhiệm
nạp các lớp thư viện của lớp Java cơ bản. Trình nạp lớp đặc biệt này có một số
tính năng đặc biệt. Với một điều là, nó chỉ nạp các lớp được tìm thấy trên đường
dẫn lớp khởi động. Bởi vì đó là những lớp hệ thống tin cậy, trình nạp bootstrap bỏ
qua phần lớn việc xác nhận hợp lệ được thực hiện cho các lớp bình thường (không
tin cậy).
Bootstrap không phải là trình nạp lớp duy nhất. Đối với người mới bắt đầu, một

Tomcat xác định. Ở đây trình nạp lớp Chung (Common) nạp từ các tệp JAR trong
một thư mục cụ thể của quá trình cài đặt Tomcat dành cho những mã dùng chung
giữa máy chủ và tất cả các ứng dụng Web. Trình nạp Catalina dành cho các lớp
riêng của Tomcat và trình nạp dùng chung (Shared) cho các lớp được chia sẻ giữa
các ứng dụng Web. Cuối cùng, mỗi ứng dụng Web nhận trình nạp riêng cho các
lớp riêng của nó.

Hình 1. Các trình nạp lớp Tomcat

Trong kiểu môi trường này, việc theo dõi trình nạp thích hợp để sử dụng cho yêu
cầu một lớp mới có thể lộn xộn. Do điều này, các phương thức
setContextClassLoader và getContextClassLoader đã được thêm vào lớp
java.lang.Thread trong nền tảng Java 2. Các phương thức này cho phép khung
công tác thiết lập trình nạp lớp sẽ được sử dụng cho mỗi ứng dụng trong lúc chạy
mã từ ứng dụng đó.
Tính linh hoạt về khả năng nạp các tập độc lập của các lớp là một tính năng quan
trọng của nền tảng Java. Tuy nhiên, khi tính năng này có ích, nó thể tạo ra sự lẫn
lộn trong một số trường hợp. Một khía cạnh lẫn lộn là vấn đề tiếp tục đối phó với
các đường dẫn lớp (classpaths) của JVM. Trong phân cấp Tomcat của các trình
nạp lớp đã chỉ ra trong Hình 1, ví dụ, các lớp được nạp bởi trình nạp lớp Common
sẽ không bao giờ có khả năng trực tiếp truy cập (theo tên) các lớp được ứng dụng
Web nạp. Cách duy nhất để liên kết các lớp này cùng nhau là thông qua việc sử
dụng các giao diện trực quan cho cả hai tập các lớp. Trong trường hợp này, điều
đó bao gồm javax.servlet.Servlet được servlet Java triển khai thực hiện.
Các vấn đề có thể nảy sinh khi mã được di chuyển giữa các trình nạp lớp vì bất cứ
lý do nào. Ví dụ, khi J2SE 1.4 đã di chuyển JAXP API để xử lý XML trong sự
phân phối chuẩn, nó đã tạo ra các vấn đề cho nhiều môi trường, mà ở đó các ứng
dụng trước đó đã dựa vào việc nạp các việc thực hiện đã chọn theo các XML API
riêng của chúng. Với J2SE 1.3, điều này có thể được thực hiện chỉ bằng cách đặt
tệp JAR thích hợp trong đường dẫn lớp của người sử dụng. Trong J2SE 1.4, các

từ sự linh hoạt lắp ráp mã trong thời gian chạy, vì vậy không có gì ngạc nhiên rằng
nền tảng Java đã trở nên ngày càng thuận lợi cho kiểu phát triển này.
Trong phần 2 của loạt bài này, tôi sẽ trình bày một giới thiệu về cách sử dụng một
khía cạnh khác của các nền móng năng động của nền tảng Java: API phản chiếu
(Reflection API). Sự phản chiếu giúp cho việc thực thi mã của bạn truy cập tới các
lớp thông tin bên trong. Đây có thể là một công cụ quan trọng để xây dựng mã linh
hoạt, có thể được nối với nhau trong thời gian chạy mà không cần bất kỳ các liên
kết mã nguồn nào giữa các lớp. Tuy nhiên, như với hầu hết các công cụ, bạn cần
phải biết sử dụng nó khi nào và như thế nào để có lợi ích tốt nhất. Kiểm tra lại để
tìm hiểu các thủ thuật và các sự thỏa hiệp về sự phản chiếu hiệu quả trong Phần 2
của Các động lực học lập trình Java.

Mục lục

 Một lớp dưới dạng mã nhị phân
 Bytecode và các ngăn xếp
 Nạp các lớp
 Kết luận


Nhờ tải bản gốc

Tài liệu, ebook tham khảo khác

Music ♫

Copyright: Tài liệu đại học © DMCA.com Protection Status