Truyền tham số trong các lời gọi phương thức từ xa

Trong một chương trình Java, các biến kiểu đối tượng thì được truyền theo tham chiếu trong các lời gọi hàm. Nghĩa là khi đối tượng truyền cho phương thức bị thay đổi bên trong thân phương thức thì khi lời gọi phương thức kết thúc, giá trị các thành phần của đối tượng cũng bị thay đổi theo. Chẳng hạn ta xét sự hoạt động của chương trinh sau đây:

class Number {

public int value = 0; public Number{int v) { this.value = v;

}

}

publie class App {

public static void main(String[] args) {

Number num = new Number(12);

System, out. println (“Giá trị trước khi gọi hầm:   ”   + num.value); incNum (num) ;

System.out.println(“Giá trị sau khí gọi hàm: ” + num.value);

}

public static void incNum(Number n) { n.value++;

}

}

Trong ví dụ này, ta tạo ra lóp Number lưu giữ giá trị nguyên. Phương thức incNum () tiếp nhận tham số có kiểu đối tượng là Number với giá trị value khởi đầu là 12. Bên trong phương thức, thành phần của biến đối tượng (tăng value lên 1). Kết quả là sau khi lời gọi phương thức kết thúc, đối tượng num có giá trị value thay đồi (tăng lên 1).

Cũng cần nên biết rằng Java có hai kiểu dữ liệu chính: kiếu dữ liệu nguyên thuỷ và kiểu dừ liệu phức hợp Các kiểu dữ liệu nguyên thuỷ, như int, float, double, char, byte, long, … khi truyền cho các hàm thì chúng được xem như là các (ham trị, ngược lại, các kiểu dữ liệu phức hợp, được xem là các đối tượng, thì luôn được truyền cho các hàm theo tham chiểu. Nghĩa là, khi sửa đổi chương trình nói trên vé dạng các tham số truyền vào các lời gọi hàm là dữ liộu kiểu nguyôn thủy thì giá trị của đối số truyền cho hàm sẽ không thay đổi sau khi thực hiện lời gọi hàm vì chúng được gọi theo tham trị.

public class App2 {

public static void main(String[] args) { int num = 12;

System.out.prìntln(“Giá trị trước khi gọi hàm: ” + num); ìncNum(num);

System.out.prìntln(“Giá tri sau khi gọi hàm: ” + num);

}

public static void incNum(int n) n++;

}

}

Giá trị sau khi gọi hàm vẫn là 12.

Tóm lại, trong Java, đối tượng được truyền theo tham chiếu còn các kiểu dữ liệu nguyên thuỷ thì được truyền theo tham trị. vấn đề của chúng ta ờ đây là truyền tham số qua các lời gọi phương thức từ xa thì được xem là truyền theo kiểu tham chiếu hay tham trị. Thật sự, truyền tham số cho các lòi gọi phương thức từ xa trong RMI có hơi khác so với nguyên tắc truyền tham số theo kiểu thông thường:

  1. Tất cả các kiểu dừ liệu nguyên thuỳ đều được truyền theo tham trị.
  2. Tất cả các kiểu dữ liệu kiểu lóp muốn truyền qua mạng đều buộc phải cài đặt một trong hai giao diện Remote hoặc Các đối tượng cài đặt giao diện Remote sẽ được truyền theo tham chiếu, còn các đối tượng cài đặt theo giao diện Serializable sẽ được truyền theo tham trị.

Lưu ý: Nếu trong các lời gọi phương thức RMI, kiểu dữ liệu đối tượng nểu không cài đặt một trong hai giao diện Remote hoặc Serializable thì sẽ không thể dùng làm tham số chuyền qua mạng được.

1. Truyền đối tượng đến trình chủ theo tham trị

Các lớp đối tượng thông dụng cùa Java như string, Date, Time, … đều được cài đặt giao diện Serializable cho nên chúng được chuyển cho các lời gọi hàm hay phương thức ở xa theo kiểu tham trị.

Với cơ chể truyền tham số đối tượng theo tham trị, khi gọi một phương thức của đối tượng từ xa, nếu trong lời gọi của phương thức này có yêu cầu tham số là kiểu đối tượng, đối tượng sẽ được đóng gói và chuyển toàn bộ đến máy chủ (nơi tiếp nhận tham số và thực thi phương thức). Tại máy chủ, đối tượng sẽ được khôi phục lại trạng thái ban đầu và đưa vào sừ dụng. Quá trình đóng gói tham số được thực hiện bởi lớp trung gian _stub, ngược lại, quá trình mờ gói dữ liệu để khôi phục lại tham số sẽ được thực hiện bời lớp _Skel.

2. Chuyển đối tượng đến chương trình chủ theo tham chiếu

Đối với lập trình trên một máy cục bộ, việc truyền một đối tượng có kích thước lớn cho một hàm theo kiếu tham trị không phải là một giải pháp hay do vừa tốn bộ nhớ, vừa tổn thời gian tạo bản sao của đối số. Trong kỹ thuật lập trình triệu gọi từ xa cũng vậy, với một đối tượng có kích thước lớn, việc đóng gói toàn bộ đối tượng rồi chuyển đi .chuyển lại trên mạng sê ảnh hường đến tốc độ thực thi của chương trình. Có cách nào mà trình chủ có thể tham chiếu và xử lý được trực tiếp đối tượng đang nằm trên máy khách hay không? Có nghĩa là, nếu trình chủ được trinh khách truy xuất từ xa thì chính chương trinh khách cũng có thể được gọi từ xa bời trình chú. Hay nói cách khác, trinh khách không cần chuyển đối tượng cho trinh chù theo trị mà có thể chuyển theo tham chiếu. Bằng cách này ta có thể cho đối tượng trên trình khách và trình chủ triệu gọi lẫn nhau.

Cách mà chương trinh khách có thể tham chiếu được đến đổi tượng trên mảy chù là máỳ chủ yêu cầu phải thực thi giao diện Remote (chẳng hạn Productlmpl extends Remote), tiếp đến là sinh ra các lớp trung gian _stub và _Skel, sau cùng là dùng RMI registry để trình khách tham chiếu đến. Chúng ta sẽ áp dụng điều tương tự như vậy với các đối tượng trên trình khách. Với Java, các đối tượng nếu cài đặt giao diện Remote thi sẽ được xem như là có khả năng chuyển qua mạng thông qua tham chiếu chứ không cần phải đóng gói chuyển đi (tạo ra bản copy) theo tham trị như trường hợp cài đặt giao diện Serializable. Đon giản chì có vậy!

Ví dụ 2.2. Chương trình sau chi ra cách sao chép các tham biến và các giá trị trả lại khi thực hiện càt đặt với giao diện Serializable. Chương trình giới thiệu các sản phẩm từ xa (marketing trên mạng) theo sở thích, độ tuổi và để khách hàng lựa chọn tuỳ theo họ là nam hay nữ. Khách hàng có thể cho biết độ tuổi, chọn mục nam (Male) hay nữ (Female), có thể cả hai và sờ thích (Hobbies) rồi nhấn nút “Submit” để có được những sàn phẩm mà kho hàng giới thiệu. Trường hợp khách tầm tuổi 50, là nữ, sở thích là máy tính (chọn Computers) thì sẽ được giới thiệu hai cuốn sách như hình 2.5.

Để thực hiện được những công việc trên, ta phải xây dựng hai giao diện từ xa:

Product, Warehouse, các lớp từ xa Productlmpl, Warehouselmpl, WarehouseSever, WarehouseClient và một lớp cục bộ Customer.

Một đối tượng thuộc lớp Customer được gửi đến cho Server. Bởi vi Customer không phải là đối tượng từ xa, vi thế một bản sao cùa nó sẽ được tạo ra ờ Server. Chương trinh Server sẽ gữi trả lại một Vector (một dãy) các sản phẩm phù họp với sở thích đề khách hàng lựa chọn.

Trước tiên ta tạo ra giao diện từ xa cho sản phẩm.

II Product.java khai báo giao diện từ xa Product import java.rmi.*;      ‘

import java.awt.*;

public interface Product extends Remote{ public String getDescription() throws RemoteException; static final int MALE = 1; static final int FEMALE = 2; static final int BOTH = MALE + FEMALE;

}

Thông tin về sản phẩm được lưu trữ bao gồm: mô tả sàn phẩm, cho lớp người (nam/nữ) ờ phạm vi độ tuổi nào đó và phù hợp cho những sở thích nhất định. Lớp Productlmpl bên cạnh việc cài đặt phương thức get Description () như đã khai báo trong giao diện từ xa, được gọi là phương thức lừ xa, còn cài đặt phương ùiưc matchộ, getlmageFile () không có trong giao diện, được gọi là phương thức cục bộ. Phương thức này chỉ cho phép gọi cục bộ trong một chương trình, không cho phép triệu gọi từ xa.

// Productlmpl.java cài đặt giao diện từ xa và bổ sung các phương thức cục bộ import java.rmi.*; import java.rmi.server.* ; import i ava.awt.*; public class ProductImpl extends UnicastRemoteObj ect implements Product{

public Productlmpl(String n, int s, int agel, int age2, String h, string img) throws RemoteException{ name = n; sex = s; ageLow = agel; ageHig = age2; hobby = h; imageFile = img;

1

public String get Description () throws Remo teEx cep t.i on { return name;

}

public String getImageFile() throws RemoteException{ return imageFile;

}

public boolean match(Customer c) (

if(c.getAge () < ageLow I I c.getAge() > ageHig) return false;

if(!c.hasHobby(hobby)) return false; if ((sex & c.getSexO) == 0) return false; return true; private string name; private int ageLow; private int ageHig; private int sex;

private string hobby;

private string imageFile;

I

Tiếp theo, ta xây dựng lóp Customer cài dặt giao diện Serializable, không phải là lớp từ xa. Nghĩa là không một phương thức nào của nó có thể được triệu gọi từ xa. Tuy nhiên, các đối tượng của lóp này có thể chuyến từ một máy JVM này sang máy JVM khác.

// Customer java tạo ra một lớp Customer cục bộ

import java.i o.■*;

public class Customer implements Serializable!

public Customer(int theAge, int theSex, string!] theHobbies){ age = theAge; sex = theSex; hobbies = theHobbies;

}

public int getAqe(){
return age;

}

public int getSex(){
return sex;

}

public boolean hasHobby(string aHobby){ if(aHobby == “”) return true; for { int i = 0; Ì < hobbies.length; i + +)

if(hobbies[i].equals(aHobby)) return true; return false;

1

public void reset!){ age = sex = 0; hobbies = null;

1

public String toString(){

String res = “Age:              ” + age + “, Sex:           “;

if(sex == Product.MALE) res += “MALE”;

else if(sex == Product.FEMALE) res += “FEMALE”;

else res += “FEMALE OR MALE”; res += “, Hobbies: “;

for(int i = 0; i < hobbies.length; iff) res += ” ” + hobbies[i]; return res;

}

private int age; private int sex; private StringH hobbies;

}

Tương tự như đổi với Product, ta tạo ra tiếp giao diện tù xa Warehouse.

// Warehouse.java khai báo giao diện từ xa cho kho hàng để tìm kiếm hàng theo yêu cầu. import j ava.rmi.*; import java.util.*;

public interface Warehouse extends Remote{

public Vector find(Customer c) throws RemoteException;

}

Giống như lớp Product Impl, Warehouse Imp 1 cũng sẽ có phương thức cục bộ và phương thức từ xa. Phương thức cục bộ add () được sừ dụng để bổ sung (mua thêm) các sản phẩm vào kho hàng. Phương thức f ind {) là từ xa, được triệu gọi đề tìm các sản phẩm tương ứng trong kho hàng.

// Warehouselmpl.java cài đặt một lớp từ xa có cả các hàm cục bộ.

import j ava.rmi.*; import j ava.rmì.server.*; import java.util.*;

public class Warehouselmpl extends UnicastRemoteObject implements Warehouse{ public Warehouselmpl() throws RemoteException{ products = new Vector 0;

}

public synchronized void add (Productlmpl p) { // Local method products.add(p);

}

public synchronized Vector find (Customer c) throws RemoteExoeption { Vector res = new Vector(); • for(int i — 0; i < products.size(); i++){

ProductImp1 p = (ProductImpl)products.get(i); if(p.match (c)) res.add(p);

}

res.add (new Productlrnpl (“Core Java Book”, Product.BOTH, 20, 100, “corejava.jpg”));

c.reset (); return res;

}

private Vector products;

}

Chương trình WarehouseServer. j ava nhằm tạo ra các đối tượng từ xa và đăng ký chúng để cho phép triệu gọi từ xa.

// WarehouseServer.java tạo ra lóp dịch vụ từ xa.

import j ava.rmi.*; import j ava.rmi.server.* ; public class WarehouseServer{

public static void main(String agrs[]){ try {

Systsn.out.println(”Ccristructing server irrplaTentations …”); Warehouselmpl w = new WarehouseImpl (); fillWarehouse(w);

Systgn.out.println(“Rinding server urplerentaticns to registry…”) ; Naming.rebind(“central_warehouse”, w);

Systan.out.println(“Whiting for invocations frcm clients …”);

}catch(Exception ef{

System.out.println(“Error: ” + e);

}

}

public static void fillWarehouse(Warehouselmpl w) throws RemoteException{

w.add(new Productlmpl(“Blackwell Teaster”, Product.BOTH,

18, 100, “Household”,””));

w.add(new Productlmpl(“Cosmetic Set”, Product.FEMALE,

15, 50, “Beauty”,””));

w.add (new ProductImpl (“Learn Java in 21 Days Book”, Product.BOTH, 20, 100, “Computers”,””)); w.add(new Productlmpl(“Handy Hand Grenade”, Product.MALE, 16, 60, “Gardening”,””));

w.add(new Productlmpl(“Network Computer”, Product.BOTH,

18, 80, “Computers”,””)) ;

//Tương tự có thể bổ sung nhiều mặt hàng khác

}

}

Cuối cùng là chương trình khách. Sau khi đã cho biết tuổi (Age), và lựa chọn mục nam (Male), hay nữ (Female), có thể cả hai và sờ thích (Hobbies), người sử dụng nhấn nút “Submit” thì một đối tượng món được tạo ra và được truyền vào cho phương thức từ xa f ind (). Những thông tin về sản phẩm tim thấy sẽ được hiển thị cho khách hàng lựa chọn.

//WarehouseClient. java triệu gpi từ xa để có được các sản phẩm phù hợp vói khách hàng

import java.awt.*;

import java.awt.event.*;

import java.io.*;

import java.util.*;

import j ava.rmi.*;

import j ava.rmi.server.*;

import javax.swing.*;

public class WarehouseClient{

public static void main(String agrs[]){

JFrame fr = new WarehouseClientFrame(); fr.show();

}

}

class WarehouseClientFrame extends JFrame implements ActionListener{ public WarehouseClientFrame(){ initui();

System.setSecurityManager(new RMISecurityManager()); try {

String url = “rmi://localhost/central_warehouse”;

centralWarehouse = (Warehouse)Naming.lookup(url);

}catch(Exception e){

System..out.println(“Error: Can’t connect to warehouse! ” + e);

}

}

private void callWarehouse(Customer c){ try {

Vector rec = centralWarehouse.find(c);

result.setText(c + “\n”);

for(int i = 0; i < rec.sizeO ; i + + ) {

Product p = (Product)rec.get(i);

String t = p.getDescription()                  + “\n”;

result.append(t);

}

}catch(Exception e){

result.setText(“Error: ” + e);

}

}

public void actionPerformed(ActionEvent evt){

Object[] hobbyObjects = hobbies.getSelectedValues(); String!] hobbyStrings = new String[hobbyObjects.length]; System.arraycopy(hobbyObjects, 0, hobbyStrings, 0, hobbyObjects.length );

Customer c = new Customer(Integer.parselnt(age.getText0), (male.isSelected()? Product.MALE : 0)

+ (female.isSelected()? Product.FEMALE : 0) , hobbyStrings); callWarehouse(c);

}

private void initui ( ) {//Khởi tạo màn hình giao diện cho khách hàng setTitle(“Warehouse Client”); setSize(300, 300);

addWindowListener(new WindowAdapter(){

public void Windowclosing(WindowEvent e){

System.exit(0);

}

}) ;

getContentPane () .setLayout(new GridBagLayout());

GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 100; gbc.weighty = 0;

add(new JLabel(“Age:”), gbc, 0, 0, 1, 1); age = new JTextField(4); age.setText ( “20”); add(age, gbc, 1, 0, 1, 1);

male = new JCheckBox(“Hale”, true); add(male, gbc, 0, 1, 1, 1); female = new JCheckBox(“Female”, true); add(female, gbc, 1, 1, 1, 1);

gbc.weighty = 100;

add(new JLabel(“Hobbies”), gbc, 0, 2, 1, 1);

String[] choices = {“Gardaiing”, “Beauty”, “Garputers”, ’Tfousebold”};

gbc.fill = GridBagConstraints.BOTH; hobbies = new JList(choices);

add(new JScrollPane(hobbies), gbc, 1,2, 1,1); gbc.weighty = 0;

gbc.fill = GridBagConstraints.NONE;

JButton submitButton = new JButton(“Submit”); add(submitButton, gbc, 0,3, 2,1); submitButton,addActionListener(this); gbc.weighty = 100;

gbc.fill = GridBagConstraints.BOTH; result = new JTextArea(4,40); result.setEditable(false); add(result, gbc, 0, 4, 2,1);

)

private void add(Component c, GridBagConstraints gbc, ìnt X, int y, int w, int h){ gbc.gridx = x; gbc.gridy = y; gbc.gridwidth = w; gbc.gridheight = h; getContentPane().add(c, gbc);

}

private Warehouse centralWarehouse; private JTextField age; private JCheckBox male, female; private JList hobbies; private JTextArea result;

}

Việc tạo ra tệp client .policy và năm bước: dịch, tạo ra lớp trung gian, đăng ký RMI registry rồi thực hiện chưcmg ừình phục vụ, chương trình khách được thực hiện tương tự như ờ ví dụ 2.1 nêu trên

3. Truyền gọi đối tượng từ xa

Truyền các đối tượng từ xa từ chương trình phục vụ (Server) tới chương trình khách (Client) là khá đơn giản. Chương ữình khách nhận được đối tượng trung gian stub và lưu vào biến đối tượng có cùng kiểu giao diện từ xa với đối tượng được truyền đi. Client có thể truy cập vào đối tượng hiện thòi ờ hên Server thông qua biến đó. Nên nhớ ìà chỉ có các phương thức từ xa (khai báo trong giao diện từ xa, giao diện kế thừa từ Remote) mới được phép truy cập trong các đối tượng stub. Tất cả các phương thức cục bộ (không khai báo trong giao diện từ xa) không truy cập được bởi stub.

Trường hợp một lớp con không cài đặt giao diện từ xa, nhưng lóp cha của nó lại cài đặt giao diện từ xa, và đối tượng của lóp con được truyền gọi với phương thức từ xa thì chì những phương thức cùa lóp cha là được chấp nhận. Để hiểu rõ hơn vấn đề này, ta hãy xét ví dụ sau.

class BookImp1 extends ProductImpl{

public Booklmp.1 (String title, String the ISBN, int sex, int agel,int age2, string hobby, string img){ super(title + ” Book”, sex, agel, age2, hobby, img) ; ISBN = thelSBN;

}

public String getStockCode(){ return ISBN;

}

private string ISBN;

}

Giá sử ta truyền một đối tượng book của lóp Booklmpl với lời gọi phương thức getStockCode () từ xa. Dối tượng nhận được sẽ là một dối tượng stub. Nhưng đối tượng stub này không đại diện cho book mà nó chi’ đại diện được cho đối tượng cùa lóp cha là Product Tmpl. Mối quan hệ giữa các giao diện và các lóp được mô tà như hình 2.5.

Hình 2.6. Các phương pháp của Productlmpl truy cập dược từ xa

Trong trường hợp này, phương thức qetStockCodeí) không truy’cập được từ xa. Muốn nó truy cập được từ xa thì phái tạo ra giao diện từ xa, ví dụ stock kế thừa từ Remote và Booklmpl dược viết lại như sau.

interface stock extends Remote{

public s t.ring getStockCode () throws RemoteException;

}

class Booklmpl extends ProductImp1{

public Booklmpl(String title, string theISON, int sex, int agel,int age2, string hobby, string img){

super(title + ” Book”, sex, agel, age2, hobby, img); ISBN – thelSBN;

}

public String getstockcode() throws RemoteException{ return ISBN;

}

private string ISBN;

}

Khi một đối tượng của lớp Booklmpl được truyền vào lời gọi phương thức từ xa, noi nhận sẽ được quyền truy cập vào tất cả các phương thức từ xa đà được khai báo cả trong Remote và stock. Như vậy một đoi tượng từ xa có thể là một cuốn sách. Các đối tượng trung gian stub được sinh ra bời chương trình rmic theo cơ chế cùa RMI và người lập ưình không cần quan tâm đến chúng. Ta có thể sù dụng toán tử instanceof để kiểm tra xem đối tượng p được truyền đi thuộc loại nào.

if(p instanceof stock){

Stock s = (Stock)p;

String c = s.getstock();

H …

}