📄 rmi.html
字号:
serialized bytestream, re-creates the arguments to the original
<CODE>RegistrationHome.create</CODE> call, and returns the result of
calling the real <CODE>RegistraionServer.create</CODE> method back along
the same route, but this time wrapping the data on the server side.
<P>
Marshaling and unmarshalling data is not without its complications.
The first issue is serialized objects might be incompatible across
Java Development Kit (JDK<FONT SIZE="-2"><SUP>TM</SUP></FONT>) releases. A Serialized object has an
identifier stored with the object that ties the serialized object to
its release. If the RMI client and server complain about incompatible
serial IDs, you might need to generate backward
compatible stubs and skeletons using the <CODE>-vcompat</CODE> option
to the <CODE>rmic</CODE> compiler.
<P>
Another issue is not all objects are serialized by default.
The initial Bean-managed <CODE>RegistrationBean</CODE> object this
example is based on returns an <CODE>Enumeration</CODE> object
that contains <CODE>Registration</CODE> elements in a <CODE>Vector</CODE>.
Returning this list from a remote method works fine, but when you
try to send a vector as a parameter to a remote method, you
get a runtime <CODE>Marshaling</CODE> exception in the Java 2 platform.
<P>
Fortunately, in the Java 2 platform the Collections API offers
alternatives to previously unmarshable objects. In this example,
an <CODE>ArrayList</CODE> from the Collections API replaces the
<CODE>Vector</CODE>.
If the Collections API is not an option, you can create a wrapper class
that extends <CODE>Serializable</CODE> and provides <CODE>readObject</CODE>
and <CODE>writeObject</CODE> method implementations to convert the object into
a bytestream.
<A NAME="regserver"></A>
<H3>RegistrationServer Class</H3>
The
<A HREF="./Code/rmi/RegistrationServer.java">RegistrationServer</A> class
extends <CODE>java.rmi.server.UnicastRemoteObject</CODE> and implements
the <CODE>create</CODE>, <CODE>findByPrimaryKey</CODE> and
<CODE>findLowCreditAccounts</CODE> methods declared in the
<CODE>RegistrationHome</CODE> interface. The
<A HREF="./Code/rmi/RegistrationServer.java">RegistrationServer.java</A>
source file also includes the implementation for the
<CODE>Registration</CODE> remote interface as class
<CODE>RegistrationImpl</CODE>. <CODE>RegistrationImpl</CODE> also
extends <CODE>UnicastRemoteObject</CODE>.
<A NAME="export"></A>
<H4>Exporting a Remote Object</H4>
Any object that you want to be remotely accessible needs to either extend
<CODE>java.rmi.server.UnicastRemoteObject</CODE> or use the
<CODE>exportObject</CODE> method from the <CODE>UnicastRemoteObject</CODE>
class. If you extend <CODE>UnicastRemoteObject</CODE>, you also get
the <CODE>equals</CODE>, <CODE>toString</CODE> and <CODE>hashCode</CODE> methods
for the exported object.
<A NAME="pass"></A>
<H4>Passing by Value and Passing by Reference</H4>
Although the <CODE>RegistrationImpl</CODE> class is not bound to the
registry, it is still referenced remotely because it is associated with
the <CODE>RegistrationHome</CODE> return results. Because
<CODE>RegistrationImpl</CODE> extends <CODE>UnicastRemoteObject</CODE>, its
results are passed by reference, and so only one copy of that user's
registration Bean exists in the Java VM at any one time.
<P>
In the case of reporting results such as in the
<CODE>RegistrationServer.findLowCreditAccounts</CODE>
method, the <CODE>RegistrationImpl</CODE> class copy of the remote object
could be used instead. By simply not extending <CODE>UnicastRemoteObject</CODE>
in the <CODE>RegistrationImpl</CODE> class definition, a new <CODE>Registration</CODE>
object would be returned for each request. In effect
the values were passed but not the reference to the object on the server.
<A NAME="garbage"></A>
<H4>Distributed Garbage Collection</H4>
Using remote references to objects on the server from a client outside
the server's garbage collector introduces some potential problems with
memory leaks. How does the server know it is holding onto a
reference to a <CODE>Registration</CODE> object that is no longer being used by any
clients because they aborted or a network connection was dropped?
<P>
To avoid potential memory leaks on the server from clients, RMI uses a leasing
mechanism when giving out references to exported objects. When exporting
an object, the Java VM increases the count for the number of references to this
object and sets an expiration time, or lease time, for the new reference to this
object.
<P>
When the lease expires, the reference count of this object is decreased
and if it reaches 0, the object is set for garbage collection by the Java VM.
It is up to the client that maintains this weak reference to the remote
object to renew the lease if it needs the object beyond the lease time.
A weak reference is a way to refer to an object in memory without
keeping it from being garbage collected.
<P>
This lease time value is a configurable property measured in milliseconds. If
you have a fast network, you could shorten the default value and create a large
number of transient object references.
<P>
The following code sets the lease timeout to 2 minutes.
<PRE>
Property prop = System.getProperties();
prop.put("java.rmi.dgc.leaseValue", 120000);
</PRE>
<P>
The <CODE>create</CODE> and <CODE>findByPrimaryKey</CODE> methods
are practically identical to the other versions of the Registration Server.
The main difference is that on the server side, the registration record
is referenced as <CODE>RegistrationImpl</CODE>, which is the implementation
of <CODE>Registration</CODE>. On the client side, <CODE>Registration</CODE>
is used instead.
<P>
The <CODE>findLowCreditAccounts</CODE> method builds an
<CODE>ArrayList</CODE> of serializable <CODE>RegistrationImpl</CODE> objects
and calls a remote method in the <CODE>SellerBean</CODE> class to pass the
results bacl. The results are generated by an inner <CODE>Thread</CODE> class
so the method returns before the results are complete. The <CODE>SellerBean</CODE>
object waits for the <CODE>updateAccounts</CODE> method to be called before displaying
the HTML page. In a client written with the Java programming langauge,
it would not need to wait, but could display the update in real time.
<PRE>
public class RegistrationServer
extends UnicastRemoteObject
implements RegistrationHome {
public registration.RegistrationPK
create(String theuser,
String password,
String emailaddress,
String creditcard)
throws registration.CreateException{
// code to insert database record
}
public registration.Registration
findByPrimaryKey(registration.RegistrationPK pk)
throws registration.FinderException {
if ((pk == null) || (pk.getUser() == null)) {
throw new FinderException ();
}
return(refresh(pk));
}
private Registration refresh(RegistrationPK pk)
throws FinderException {
if(pk == null) {
throw new FinderException ();
}
Connection con = null;
PreparedStatement ps = null;
try{
con=getConnection();
ps=con.prepareStatement("select password,
emailaddress,
creditcard,
balance from registration where theuser = ?");
ps.setString(1, pk.getUser());
ps.executeQuery();
ResultSet rs = ps.getResultSet();
if(rs.next()) {
RegistrationImpl reg=null;
try{
reg= new RegistrationImpl();
}catch (RemoteException e) {}
reg.theuser = pk.getUser();
reg.password = rs.getString(1);
reg.emailaddress = rs.getString(2);
reg.creditcard = rs.getString(3);
reg.balance = rs.getDouble(4);
return reg;
}else{
throw new FinderException ();
}
}catch (SQLException sqe) {
throw new FinderException();
}finally {
try{
ps.close();
con.close();
}catch (Exception ignore) {}
}
}
public void findLowCreditAccounts(
final ReturnResults client)
throws FinderException {
Runnable bgthread = new Runnable() {
public void run() {
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
ArrayList ar = new ArrayList();
try{
con=getConnection();
ps=con.prepareStatement("select theuser,
balance from registration
where balance < ?");
ps.setDouble(1, 3.00);
ps.executeQuery();
rs = ps.getResultSet();
RegistrationImpl reg=null;
while (rs.next()) {
try{
reg= new RegistrationImpl();
}catch (RemoteException e) {}
reg.theuser = rs.getString(1);
reg.balance = rs.getDouble(2);
ar.add(reg);
}
rs.close();
client.updateResults(ar);
}catch (Exception e) {
System.out.println("findLowCreditAccounts: "+e);
return;
}
finally {
try{
if(rs != null) {
rs.close();
}
if(ps != null) {
ps.close();
}
if(con != null) {
con.close();
}
}catch (Exception ignore) {}
}
} //run
};
Thread t = new Thread(bgthread);
t.start();
}
}
</PRE>
The <CODE>main</CODE> method loads the JDBC<FONT SIZE="-2"><SUP>TM</SUP></FONT> pool driver. This
version uses the Postgres database, installs the
<CODE>RMISecurityManager</CODE>, and contacts the RMI registry to
bind the the <CODE>RegistrationHome</CODE> remote object to
the name <CODE>registration2</CODE>. It does not need to bind the
remote interface, <CODE>Registration</CODE> because that
class is loaded when it is referenced by <CODE>RegistrationHome</CODE>.
<P>
By default, the server name uses port 1099. If you
want to use a different port number, you can add it
with a colon as follows: <CODE>kq6py:4321</CODE>.
If you change the port here, you must start the
<A HREF="#registry">RMI Registry</A> with the same port number.
<P>
The <CODE>main</CODE> method also installs a <CODE>RMIFailureHandler</CODE>.
If the server fails to create a server socket then the failure handler
returns <CODE>true</CODE> which instructs the RMI server to retry the
operation.
<P>
<PRE>
public static void main(String[] args){
try {
new pool.JDCConnectionDriver(
"postgresql.Driver",
"jdbc:postgresql:ejbdemo",
"postgres", "pass");
} catch (Exception e){
System.out.println(
"error in loading JDBC driver");
System.exit(1);
}
try {
Properties env=System.getProperties();
env.put("java.rmi.server.codebase",
"http://phoenix.eng.sun.com/registration");
RegistrationServer rs=
new RegistrationServer();
if (System.getSecurityManager() == null ) {
System.setSecurityManager(
new RMISecurityManager());
}
RMISocketFactory.setFailureHandler(
new RMIFailureHandlerImpl());
Naming.rebind("
//phoenix.eng.sun.com/registration2",rs);
}catch (Exception e) {
System.out.println("Exception thrown "+e);
}
}
}
class RMIFailureHandlerImpl
implements RMIFailureHandler {
public boolean failure(Exception ex ){
System.out.println("exception "+ex+" caught");
return true;
}
}
</PRE>
<A NAME="int"></A>
<H3>Registration Interface</H3>
The <A HREF="./Code/rmi/Registration.java">Registration</A> interface declares the
methods implemented by <CODE>RegistrationImpl</CODE> in the
<CODE>RegistrationServer.java</CODE> source file.
<PRE>
package registration;
import java.rmi.*;
import java.util.*;
public interface Registration extends Remote {
boolean verifyPassword(String password)
throws RemoteException;
String getEmailAddress() throws RemoteException;
String getUser() throws RemoteException;
int adjustAccount(double amount)
throws RemoteException;
double getBalance() throws RemoteException;
}
</PRE>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -