Ajax cho các nhà phát triển Java: Ajax với Direct Web Remoting
Sự tuần tự hóa dữ liệu không dễ dàng hơn điều này!
Philip McCarthy, Nhà Phát triển, SmartStream Technologies Ltd
Tóm tắt: Thật thú vị như nó có, việc thêm chức năng Ajax cho các ứng dụng của
bạn có thể mang lại nhiều việc khó khăn. Trong bài viết thứ ba của loạt bài Ajax
cho các nhà phát triển Java™ này, Philip McCarthy cho bạn thấy cách sử dụng
Direct Web Remoting (DWR-Truy cập Web trực tiếp từ xa) để trực tiếp đặt các
phương thức JavaBeans vào mã JavaScript của bạn và tự động hóa công việc đòi
hỏi sự nỗ lực của Ajax.
Hiểu biết những điều căn bản về lập trình Ajax (fundamentals of Ajax
programming) là điều cần thiết, nhưng nếu bạn đang xây dựng các giao diện người
dùng (UI) Ajax phức tạp, điều quan trọng là có thể làm việc ở một mức độ trừu
tượng cao hơn. Trong bài viết thứ ba của loạt bài Ajax cho các nhà phát triển Java
này, tôi thêm phần giới thiệu của số trước vào các kỹ thuật tuần tự hóa dữ liệu cho
Ajax (data serialization techniques for Ajax), giới thiệu một kỹ thuật cho phép bạn
tránh những chi tiết thực dụng của việc tuần tự hóa các đối tượng Java.
Trong bài trước, tôi đã chỉ cho bạn cách sử dụng JavaScript Object Notation
(JSON- Ký hiệu đối tượng JavaScript) để tuần tự hóa dữ liệu theo một định dạng
dễ dàng được chuyển đổi thành các đối tượng JavaScript trên máy khách. Với thiết
lập này, bạn có thể gọi các cuộc gọi dịch vụ từ xa khi sử dụng mã JavaScript và
nhận các đồ thị đối tượng JavaScript trả lời, không giống như việc thực hiện một
cuộc gọi thủ tục từ xa. Bây giờ, bạn sẽ tìm hiểu cách tiến thêm một bước nữa,
bằng cách sử dụng một khung công tác chính thức hóa khả năng của bạn để thực
hiện cuộc gọi thủ tục từ xa trên các đối tượng Java phía máy chủ từ mã máy khách
JavaScript.
DWR là một giải pháp được cấp phép Apache, mã nguồn mở gồm các thư viện
Java phía máy chủ, một servlet DWR và các thư viện JavaScript. Trong khi DWR
không chỉ là bộ công cụ Ajax-RPC có sẵn cho nền tảng Java, mà nó còn là một
trong những giải pháp hoàn thiện nhất và cung cấp rất nhiều chức năng có ích.
Xem Tài nguyên để tải về DWR trước khi tiếp tục các ví dụ.
DWR là gì?
Hình 1. Biểu đồ lớp mô tả các giỏ hàng, CatalogDAO và các lớp Item
Tôi sẽ trình diễn hai trường hợp sử dụng rất đơn giản trong kịch bản này. Trước
tiên, người dùng có thể thực hiện tìm kiếm văn bản trên danh mục và xem các mục
phù hợp. Thứ hai, người sử dụng có thể thêm các mục vào giỏ mua hàng và xem
tổng chi phí của các mục trong giỏ hàng. Thực hiện danh mục
Điểm khởi đầu của một ứng dụng DWR là viết mô hình đối tượng phía máy chủ
của bạn. Trong trường hợp này, tôi bắt đầu bằng cách viết một DAO để cung cấp
các khả năng tìm kiếm trên kho dữ liệu (datastore) danh mục sản phẩm.
CatalogDAO.java là một lớp không trạng thái, đơn giản với một số hàm tạo
không-đối số. Liệt kê 1 cho thấy các điểm đặc trưng của các phương thức Java mà
tôi muốn để trưng ra cho các máy khách Ajax:
Liệt kê 1. Các phương thức CatalogDAO để trưng ra qua DWR
/**
* Returns a list of items in the catalog that have
* names or descriptions matching the search expression
* @param expression Text to search for in item names
* and descriptions
* @return list of all matching items
*/
public List<Item> findItems(String expression);
/**
* Returns the Item corresponding to a given Item ID
Phần tử gốc của tài liệu dwr.xml là dwr. Bên trong nó là phần tử allow (cho phép),
cho phép xác định các lớp mà DWR sẽ truy cập từ xa. Hai phần tử con của allow
là create (tạo) và convert (chuyển đổi).
Phần tử create
Phần tử create báo cho DWR biết một lớp phía máy chủ nên được trưng ra cho các
yêu cầu Ajax và định nghĩa cách DWR nhận được một cá thể của lớp đó từ xa.
Thuộc tính creator ở đây được đặt tới giá trị new, có nghĩa là DWR sẽ gọi hàm tạo
mặc định của lớp để nhận được một cá thể. Các khả năng khác là tạo một cá thể
thông qua một đoạn kịch bản lệnh bằng cách sử dụng Bean Scripting Framework
(BSF-Khung công tác tạo kịch bản lệnh Bean) hoặc để nhận được một cá thể
thông qua việc tích hợp với thùng chứa IOC, Spring. Theo mặc định, khi yêu cầu
Ajax với DWR gọi creator, đối tượng cụ thể được đặt trong phạm vi trang và do
đó không còn tồn tại sau khi yêu cầu hoàn tất. Trong trường hợp của CatalogDAO
không trạng thái, điều này là tốt.
Thuộc tính javascript của phần tử create xác định tên mà đối tượng sẽ có thể truy
cập từ mã JavaScript. Được lồng bên trong phần tử create , phần tử param xác
định lớp Java mà phần tử creator sẽ tạo ra. Cuối cùng, các phần tử, include xác
định các tên của các phương thức sẽ được trưng ra. Việc bắt đầu các phương thức
để trưng ra là thực tế tốt để tránh vô tình cho phép truy cập chức năng có thể gây
hại nếu phần tử này được bỏ qua, tất cả các phương thức của lớp sẽ được trưng
ra các cuộc gọi từ xa. Lần lượt, bạn có thể sử dụng các phần tử exclude (loại trừ)
để xác định chỉ những phương thức nào bạn muốn ngăn chặn truy cập vào.
Phần tử convert
Trong khi các creator có liên quan tới việc trưng ra các lớp và các phương thức
của chúng cho việc truy cập Web từ xa, các convertor (trình chuyển đổi) có liên
quan đến các kiểu tham số và kiểu trả về của các phương thức đó. Vai trò của phần
tử convert là để nói cho DWR biết cách chuyển đổi các kiểu dữ liệu giữa việc thể
hiện đối tượng Java phía máy chủ của chúng và việc thể hiện JavaScript được tuần
tự hóa và ngược lại.
DWR tự động dàn xếp các kiểu dữ liệu đơn giản giữa các thể hiện Java và
Hình 2. Trang thử nghiệm DWR cho CatalogDAO
Có thể nhập các giá trị tham số vào các hộp văn bản bên cạnh các phương thức
được phép truy cập và nhấn nút Execute (Thực hiện) để gọi chúng. Trả lời của
máy chủ sẽ được hiển thị bằng cách sử dụng ký hiệu của JSON trong một hộp
cảnh báo, trừ khi nó là một giá trị đơn giản; trong trường hợp này nó được hiển thị
nội tuyến dọc theo các phương thức. Các trang thử nghiệm này rất có ích. Không
chỉ chúng cho phép bạn dễ dàng kiểm các lớp và phương thức nào được trưng ra
để truy cập từ xa, mà bạn cũng có thể thử nghiệm xem mỗi phương thức có đang
hoạt động như mong đợi không.
Một khi bạn đã hài lòng rằng các phương thức được truy cập từ xa của bạn đang
hoạt động đúng, bạn có thể sử dụng các stub (nhánh rẽ) JavaScript được tạo của
DWR để gọi các đối tượng phía máy chủ của bạn từ mã phía máy khách. Gọi một đối tượng được truy cập từ xa
Việc ánh xạ giữa các phương thức đối tượng Java được truy cập từ xa và các hàm
stub JavaScript tương ứng của chúng là đơn giản. Dạng chung là
JavaScriptName.methodName(methodParams , callBack), ở đây
JavaScriptName là bất kỳ tên đã được xác định như là thuộc tính javascript của
creator, methodParams thể hiện các tham số n của phương thức Java và callback
(gọi lại) là một hàm JavaScript, nó sẽ được gọi với giá trị trả về của phương thức
Java. Nếu bạn quen với Ajax, bạn sẽ nhận ra cơ chế gọi lại như là cách tiếp cận
thông thường cho sự không đồng bộ của XMLHttpRequest.
Trong kịch bản ví dụ này, tôi sử dụng các hàm JavaScript trong Liệt kê 3 để thực
hiện tìm kiếm và cập nhật giao diện người dùng theo các kết quả tìm kiếm. Danh
sách này cũng sử dụng các hàm tiện ích từ util.js của DWR. Có chú ý đặc biệt là
hàm JavaScript có tên là $(), nó có thể được dùng như một phiên bản được cải tiến
của document.getElementById(). Tất nhiên là dễ dàng định kiểu hơn. Nếu bạn đã
sử dụng thư viện JavaScript nguyên mẫu, bạn sẽ quen thuộc với hàm này.
DWRUtil.addRows("items",items,cellFunctions);
$("catalog").style.visibility = "visible";
}
}
Trong hàm searchFormSubmitHandler() ở trên, tất nhiên mã quan tâm là
catalog.findItems(searchexp, displayItems);. Một dòng mã này là tất cả những thứ
cần thiết để gửi một XMLHttpRequest qua mạng tới DWR servlet và gọi hàm
displayItems() với trả lời của đối tượng được truy cập từ xa.
Hàm gọi lại displayItems() tự nó được gọi với một mảng của các thể hiện Item.
Mảng này được được chuyển qua tới hàm tiện ích DWRUtil.addRows() cùng với
mã nhận dạng (ID) của bảng cần điền vào và một mảng các hàm. Càng có nhiều
hàm trong mảng này thì càng có nhiều ô trong mỗi hàng của bảng. Mỗi hàm được
gọi lần lượt với một Item từ mảng đó và sẽ trả về nội dung để điền vào ô tương
ứng.
Trong trường hợp này, tôi muốn mỗi hàng trong bảng các mục hiển thị tên của
mục, mô tả và giá cả, cũng như một nút Add to Cart (Thêm vào giỏ hàng) cho
mục đó trong cột cuối. Liệt kê 4 cho thấy mảng các hàm của ô thực hiện việc này:
Liệt kê 4. Mảng các hàm của ô để điền vào bảng các mục
/*
* Array of functions to populate a row of the items table
* using DWRUtil's addRows function
*/
var cellFunctions = [
function(item) { return item.name; },
function(item) { return item.description; },
function(item) { return item.formattedPrice; },
như là khoá. Giá trị tương ứng trong Map là một Integer (số nguyên) thể hiện số
lượng của Item cụ thể trong giỏ hàng này. Vì vậy Cart.java có một trường,
contents (các nội dung), được khai báo như là Map<Item,Integer>.
Việc sử dụng các kiểu phức tạp như là các khóa băm (hash key ) đặt ra vấn đề cho
DWR trong JavaScript, các khóa mảng phải là các chữ. Kết quả là contents Map
có thể không được DWR chuyển đổi như nó có. Tuy nhiên, với các mục đích của
giao diện người dùng của giỏ mua hàng, tất cả những thứ mà người sử dụng cần
phải thấy là tên và số lượng của mỗi mục trong giỏ hàng này. Vì vậy, tôi có thể
thêm một phương thức có tên là getSimpleContents() vào Cart, có contents Map
và xây dựng một Map<String,Integer> đơn giản từ nó, thể hiện chỉ có tên và số
lượng của mỗi Item. Việc thể hiện bản đồ chuỗi có khóa này có thể đơn giản được
các trình biến đổi gắn sẵn của DWR chuyển đổi thành JavaScript.
Một trường khác của Cart mà máy khách có quan tâm đến là totalPrice, thể hiện
tổng số của tất cả mọi thứ trong giỏ hàng. Như với Item, tôi đã cung cấp một bộ
phận tổng hợp có tên là formattedTotalPrice là một thể hiện String (Chuỗi) được
định dạng sẵn của tổng số.
Chuyển đổi giỏ hàng
Thay vì có mã khách hàng thực hiện hai cuộc gọi vào Cart, một để nhận được các
nội dung và một cho giá tổng, tôi muốn gửi tất cả các dữ liệu này đến máy khách
hàng cùng một lúc. Để thực hiện điều này tôi đã thêm một phương thức mới lạ,
hiển thị trong Liệt kê 5::
Liệt kê 5. Phương thức Cart.getCart ()
/**
* Returns the cart itself - for DWR
* @return the cart
*/
public Cart getCart() {
return this;
</convert>
<create creator="new" scope="session" javascript="Cart">
<param name="class" value="developerworks.ajax.store.Cart"/>
<include method="addItemToCart"/>
<include method="getCart"/>
</create>
<convert converter="bean" match="developerworks.ajax.store.Cart">
<param na
me="include" value="simpleContents,formattedTotalPrice"/>
</convert>
</allow>
</dwr>
Trong phiên bản này của dwr.xml, tôi đã thêm cả một creator lẫn một convertor
cho Cart. Phần tử create xác định rằng các phương thức addItemToCart() và
getCart() cần được truy cập từ xa và quan trọng là, cá thể Cart được tạo sẽ được
đặt trong phiên giao dịch của người dùng. Kết quả là, nội dung của giỏ hàng sẽ
vẫn còn giữa các yêu cầu của người sử dụng.
Phần tử convert cho Cart là cần thiết vì các phương thức Cart được truy cập từ xa
tự trả về Cart. Ở đây tôi đã xác định rằng các bộ phận trong Cart nên được trình
bày dưới dạng JavaScript được tuần tự hóa của nó, là bản đồ simpleContents và
String formattedTotalPrice.
Nếu bạn thấy hơi khó hiểu, hãy nhớ rằng create xác định các phương thức phía
máy chủ trên Cart có thể được một máy khách DWR gọi và phần tử convert xác
định các bộ phận có trong việc sắp xếp theo thứ tự JavaScript của Cart.
Bây giờ tôi có thể triển khai thực hiện mã phía máy khách để gọi các phương thức
Cart được truy cập từ xa của tôi.
Với DWR đang giữ tất cả các giao tiếp, hành vi thêm vào giỏ hàng trên máy khách
đúng là một hàm một dòng. Liệt kê 8 cho thấy đoạn mã cuối cùng của trò chơi
ghép hình này thực hiện cuộc gọi lại displayCart(), cập nhật giao diện người
dùng với trạng thái của Cart:
Liệt kê 8. Thực hiện displayCart ()
/*
* Displays the contents of the user's shopping cart
*/
function displayCart(cart) {
// Clear existing content of cart UI
var contentsUL = $("contents");
contentsUL.innerHTML="";
// Loop over cart items
for (var item in cart.simpleContents) {
// Add a list element with the name and quantity of item
var li = document.createElement("li");
li.appendChild(document.createTextNode(
cart.simpleContents[item] + " x " + item
));
contentsUL.appendChild(li);
}
// Update cart total
var totalSpan = $("totalprice");
Bạn đã thấy cách dễ dàng để thực hiện một ứng dụng Ajax có Java ở phía sau khi
sử dụng DWR. Mặc dù kịch bản ví dụ là một ứng dụng đơn giản và tôi đã có một
cách tiếp cận gần như tối thiểu để triển khai thực hiện các trường hợp sử dụng, bạn
không nên đánh giá thấp số lượng công việc mà công cụ DWR có thể tiết kiệm
cho bạn hơn một cách tiếp cận Ajax được tạo tại chỗ. Trong khi ở bài viết trước tôi
đi qua tất cả các bước thiết lập bằng tay một yêu cầu và trả lời Ajax và chuyển đổi
một đồ thị đối tượng Java thành một thể hiện JSON, ở đây DWR đã làm tất cả các
công việc đó cho tôi. Tôi đã viết ít hơn 50 dòng mã JavaScript để triển khai thực
hiện máy khách và ở phía máy chủ, tất cả những thứ mà tôi đã làm đã tăng thêm
JavaBeans thông thường của tôi bằng một cặp các phương thức bổ sung.
Tất nhiên, mỗi công nghệ có nhược điểm của nó. Cũng như bất kỳ cơ chế RPC
nào, trong DWR có thể dễ quên rằng mỗi cuộc gọi mà bạn thực hiện trên các đối
tượng được truy cập từ xa tốn kém hơn nhiều so với một cuộc gọi hàm cục bộ.
DWR thực hiện nhiều công việc về ẩn bộ máy Ajax, nhưng điều quan trọng cần
nhớ rằng mạng không trong suốt, có độ trễ liên quan đến việc thực hiện cuộc gọi
DWR và ứng dụng của bạn phải được kiến trúc để cho các phương thức được truy
cập từ xa có độ chi tiết thô. Đối với mục đích này addItemToCart() tự trả về Cart.
Mặc dù nó đã có sẵn để tạo cho addItemToCart() một phương thức trống, mỗi
cuộc gọi DWR tới nó sau đó sẽ phải được tiếp theo bằng một cuộc gọi đến
getCart() để lấy ra trạng thái Cart đã thay đổi.
DWR có giải pháp riêng của mình cho vấn đề độ trễ trong việc xử lý theo khối
cuộc gọi (xem phần bên cạnh Xử lý cuộc gọi theo khối). Nếu bạn không thể cung
cấp giao diện Ajax có độ chi tiết thô thích hợp cho ứng dụng của bạn, thì hãy sử
dụng xử lý theo khối cuộc gọi bất cứ ở đâu có thể để kết hợp nhiều cuộc gọi từ xa
vào trong một yêu cầu HTTP.
Tách mối quan tâm
Do tính chất của mình, DWR tạo một sự kết hợp chặt chẽ giữa mã phía máy khách
và mã phía máy chủ, với một số các hàm ý. Trước hết, các thay đổi với API của
các phương thức được truy cập từ xa cần phải được phản ánh trong JavaScript để
gọi vào các nhánh DWR. Thứ hai (và đáng kể hơn), sự kết hợp này làm cho các
không cần phải viết bất kỳ mã servlet nào, mã tuần tự hóa đối tượng hoặc mã
XMLHttpRequest phía máy khách. Vô cùng đơn giản để triển khai ứng dụng Web
của bạn khi sử dụng DWR và các tính năng an ninh của DWR có thể được tích
hợp với một hệ thống xác thực dựa trên vai trò J2EE. Tuy nhiên DWR không thể
áp dụng cho mọi kiến trúc ứng dụng và nó yêu cầu bạn phải cung cấp một số ý
tưởng cho việc thiết kế các API của đối tượng miền của bạn.
Nếu bạn muốn tìm hiểu thêm về những ưu và nhược điểm của Ajax với DWR,
điều tốt nhất là tải nó cho chính mình và bắt đầu thử nghiệm. Trong khi DWR có
nhiều tính năng mà tôi đã không trình bày, mã nguồn bài viết này là một điểm
khởi đầu tốt đẹp để có DWR cho một công việc. Xem Tài nguyên để tìm hiểu
thêm về Ajax, DWR và các công nghệ liên quan.
Một trong những điểm quan trọng nhất lấy ra từ loạt bài này là cho các ứng dụng
Ajax, không có giải pháp một kích cỡ phù hợp cho tất cả. Ajax là một lĩnh vực
phát triển nhanh với các công nghệ và các kỹ thuật mới đang nổi lên trong suốt
thời gian qua. Trong ba bài viết của loạt bài này, tôi đã tập trung vào việc bạn bắt
đầu sử dụng các công nghệ Java trong tầng Web của ứng dụng Ajax cho dù bạn
chọn một cách tiếp cận dựa trên XMLHttpRequest với một khung công tác tuần tự
hóa đối tượng hay trừu tượng mức độ cao hơn của DWR. Hãy theo dõi các bài viết
tiếp theo khám phá Ajax cho các nhà phát triển Java trong những số tháng tới.
Mục lục
DWR là gì?
Về ví dụ này
Thực hiện danh mục
Thử nghiệm triển khai
Gọi một đối tượng được truy cập từ xa
Triển khai thực hiện giỏ mua hàng