/**图11.15 生成每一个方法的代码 让我们分析一下在这个方法中都要做些什么:*Generates the code for the given method * * @param m Method to generate * @param numIndent Number of indentations (tabs) * @param out Print writer */ public void codeMethod(java.lang.reflect.Method m,int numIndent, java.io.PrintWriter out) throws java.io.IOException { String line; String tab = indent(numIndent); boolean throwsClassNotFoundException = false; // Get the method return type Class ret = m.getReturnType(); String retName = decodeType(ret.getName()); // Validate the return type to ensure we can marshal it if (!validateType(ret)) { throw new java.io.IOException("Invalid return data type " + retName); } // Get the method parameters Class params[] = m.getParameterTypes(); // Get the exceptions thrown by the method Class exceptions[] = m.getExceptionTypes(); // Generate the method signature line = "public " + retName + " " + m.getName() + "("; // Loop for each parameter for (int i = 0; i < params.length; i++) { // Validate the parameter type to ensure we can marshal it if (!validateType(params[i])) { throw new java.io.IOException("Invalid parameter " + "data type " + retName); } // Insert a comma if necessary if (i > 0) { line += ", "; } // Call the parameters p0, p1, etc. line += decodeType(params[i].getName()) + " p" + i; } // Add the ending paren line += ")"; // Write out the method signature out.println(tab + line); // Take care of any exceptions thrown by the method if (exceptions.length > 0) { line = "throws "; for (int i = 0; i < exceptions.length; i++) { // Insert a comma if necessary if (i > 0) { line += ", "; } line += exceptions[i].getName(); } out.println(tab + indent(1) + line); } // Start the method body numIndent++; tab = indent(numIndent); out.println(tab + "{"); numIndent++; tab = indent(numIndent); // Generate the default return value if (!retName.equals("void")) { line = retName + " retValue = "; // Determine the default value if (retName.equals("boolean")) { line += "false;"; } else if (retName.equals("char") || retName.equals("byte") || retName.equals("short") || retName.equals("int") || retName.equals("long") || retName.equals("float") || retName.equals("double")) { line += "0;"; } else { line += "null;"; } out.println(tab + line); } out.println(tab + "try {"); numIndent++; tab = indent(numIndent); out.println(tab + "// Create an internal buffer"); out.println(tab + "ByteArrayOutputStream baos = " + "new ByteArrayOutputStream();"); out.println(""); out.println(tab + "// Create an object stream to write " + "the request"); out.println(tab + "ObjectOutputStream out ="); out.println(tab + indent(1) + "(ObjectOutputStream) "+ "_createHeader(baos, " + m_methodNum + ");"); // Write the parameters for (int i = 0; i < params.length; i++) { String param = "p" + i; String paramType = decodeType(params[i].getName()); // Convert scalars to the proper object if (paramType.equals("boolean")) { param = "new Boolean(" + param + ")"; } else if (paramType.equals("byte")) { param = "new Byte(" + param + ")"; } else if (paramType.equals("char")) { param = "new Character(" + param + ")"; } else if (paramType.equals("short")) { param = "new Short(" + param + ")"; } else if (paramType.equals("int")) { param = "new Integer(" + param + ")"; } else if (paramType.equals("long")) { param = "new Long(" + param + ")"; } else if (paramType.equals("float")) { param = "new Float(" + param + ")"; } else if (paramType.equals("double")) { param = "new Double(" + param + ")"; } out.println(tab + "out.writeObject(" + param + ");"); } // Invoke the method out.println(""); out.println(tab + "// Invoke the method"); out.println(tab + "ObjectInputStream in = "); out.println(tab + indent(1) + "(ObjectInputStream) " + "_invokeMethod(baos.toByteArray());"); // Get the return value if necessary if (!retName.equals("void")) { out.println(""); out.println(tab + "// Get the return value"); out.println(tab + "Object retObject = in.readObject();"); if (retName.equals("boolean")) { out.println(tab + "retValue = " + "((Boolean) retObject).booleanValue();"); } else if (retName.equals("byte")) { out.println(tab + "retValue = " + "((Byte) retObject).byteValue();"); } else if (retName.equals("char")) { out.println(tab + "retValue = " + "((Character) retObject).charValue();"); } else if (retName.equals("short")) { out.println(tab + "retValue = " + "((Short) retObject).shortValue();"); } else if (retName.equals("int")) { out.println(tab + "retValue = " + "((Integer) retObject).intValue();"); } else if (retName.equals("long")) { out.println(tab + "retValue = " + "((Long) retObject).longValue();"); } else if (retName.equals("float")) { out.println(tab + "retValue = " + "((Float) retObject).floatValue();"); } else if (retName.equals("double")) { out.println(tab + "retValue = " + "((Double) retObject).doubleValue();"); } else { out.println(tab + "retValue = (" + retName + ") retObject;"); } throwsClassNotFoundException = true; } // Wrap up out.println(tab + "out.close();"); out.println(tab + "in.close();"); // End the try block numIndent--; out.println(indent(numIndent) + "}"); out.println(indent(numIndent) + "catch (java.io.IOException ex) {"); out.println(indent(numIndent + 1) + "ex.printStackTrace();"); out.println(indent(numIndent) + "}"); if (throwsClassNotFoundException) { out.println(indent(numIndent) + "catch (ClassNotFoundException ex) {"); out.println(indent(numIndent + 1) + "ex.printStackTrace();"); out.println(indent(numIndent) + "}"); } out.println(indent(numIndent) + "catch (TunnelException ex) {"); out.println(indent(numIndent + 1) + "ex.printStackTrace();"); out.println(indent(numIndent) + "}"); // Write the return value if (!retName.equals("void")) { out.println(indent(numIndent) + "return retValue;"); } // End the method body numIndent--; out.println(indent(numIndent) + "}"); out.println(""); // Increment the method number m_methodNum++; }
1.验证返回类型的有效性,确保它可以完全地被编发。
2.创建方法签名。这个签名包括所有的语法修饰符、返回类型、方法名、参数类型和异常。每个参数类型都要验证有效性以保证它可以被完全地编发。
3.创建方法正文。参考图11.2,其中举例说明了方法是如何被生成的。
确认数据类型以保证它们可以被完全地编发。这项工作主要依据是它们属于可以被用于“Lite”遂道的HTTP遂道类型也可以被JDK1.0.2使用的类型,是可以使用DataInputStream和DataOutputStream方法来编发的类型。因此,只有标量和字符串对象可以使用。正规的遂道使用ObjectInputStream和ObjectOutputStream编发数据并且需要所使用的对象实现java.io.Serializable。
实现了所有的代码生成器后,最后要做的就是要建立一个应用程序来测试它们。这个名为ServletGen的应用程序接收命令行参数并调用适当的代码生成器。在图11.16中列出了它的完整代码。package javaservlets.CodeGen; /** *This application will invoke the proper code generator * depending upon the command line options given: * * -i Interface name * -c Class name * -l (option) Lite version * * All generated source will be created in the current directory. */ public class ServletGen { public static void main(String args[]) { // Get the interface name String interfaceName = getArg(args, "-i"); // Get the class name String className = getArg(args, "-c"); // Get the optional 'lite' arg boolean lite = argExists(args, "-l"); // Make sure the required parameters were given if ((interfaceName == null) || (className == null)) { System.out.println("\nServletGen usage:\n"); System.out.println("ServletGen -i
-c " + "[-l]"); return; } try { // Generate the appropriate code if (lite) { // Generate the lite client ServletGenLiteClient client = new ServletGenLiteClient(); client.setInterfaceName(interfaceName); client.setObjectName(className); System.out.println("Generating servlet client proxy"); client.generate(); // Generate the server ServletGenLiteServer server = new ServletGenLiteServer(); server.setInterfaceName(interfaceName); server.setObjectName(className); System.out.println("Generating servlet server stub"); server.generate(); } else { // Generate the client ServletGenClient client = new ServletGenClient(); client.setInterfaceName(interfaceName); client.setObjectName(className); System.out.println("Generating servlet client proxy"); client.generate(); // Generate the server ServletGenServer server = new ServletGenServer(); server.setInterfaceName(interfaceName); server.setObjectName(className); System.out.println("Generating servlet server stub"); server.generate(); } } catch (Exception ex) { ex.printStackTrace(); } } /** * Find the given argument switch. * * @param args Array of command line arguments * @param s Switch to find * @return Value of the argument or null if not found */ public static String getArg(String args[], String s) { String arg = null; if (args != null) { // Find the switch in the array for (int i = 0; i < args.length; i++) { // Does the switch match? if (args[i].startsWith(s)) { if (args[i].length() > s.length()) { // Get the value arg = args[i].substring(s.length()); break; } } } } return arg; } /** * Determines if the given argument switch exists. * * @param args Array of command line arguments * @param s Switch to find * @return true if the switch exists */ public static boolean argExists(String args[], String s) { boolean rc = false; if (args != null) { // Find the switch in the array for (int i = 0; i < args.length; i++) { // Does the switch match? if (args[i].startsWith(s)) { rc = true; break; } } } return rc; } }
图11.16 应用程序ServletGen
在第10章中,我们开发了一个非常基本的算术对象。我们从定义这个对象的接口开始(见图11.17)并最后实现了这个接口(见图11.18)。当时实现的时候,我们是通过手工编写一个客户代理和服务器端代码存根来实现对服务器端的算术对象的“lite”HTTP遂道方法调用的。
图11.18 Math.javapackage javaservlets.CodeGen;
/*** <p>This interface defines the methods available for
* performing math
* 下面的double在原文中是int
*/ public interface MathInterface{
/**
* <p>Adds two numbers
*/
double add(double a, double b); /*** <p>Subtracts two numbers
*/
double subtract(double a, double b); /*** <p>Multiplies two numbers
*/
double multiply(double a, double b); } 图11.17 MathInterface.javapackage javaservlets.CodeGen;
/** * <p>This class performs simple math functions in orderto
* illustrate remote method tunneling.
* 下面的double在原文中是int
*/ public class Math implements MathInterface{
/**
* <p>Adds two numbers
*/
public double add(double a, double b)
{
return (a + b);
} /*** <p>Subtracts two numbers
*/
public double subtract(double a, double b)
{
return (a - b);
} /*** <p>Multiplies two numbers
*/
public double multiply(double a, double b)
{
return (a * b);
} }
现在进入令兴奋的部分!取代手工编制客户代理和服务器代码存根,让我们使用新的代码生成器来为我们做所有的事情吧(为了提高可读性,Java命令被分成为两行书写)。
java javaservlets.CodeGen.ServletGen -ijavaservlets.CodeGen.MathInterface -cjavaservlets.CodeGen.Math -l Generating servlet client proxy Writing RemoteMathLiteClient.java Generationg servlet server stub Writing RemoteMathLiteServer.java
只要很短的时间,ServletGen就可以使用Reflection API技术找到在指定接口中(javaservlets.CodeGen.MathInterface)的所有的方法并生成“lite”HTTP遂道类型的客户代理和服务器代码存根(使用-l开关指定)。图11.19显示了RemoteMathLiteClient的代码,图11.20显示了RemoteMathLiteServer的代码。要知道所有的这些源文件都是完全通过机器生成的。
/* * @(#)RemoteMathClient * * Generated by javaservlets.CodeGen.ServletGenClient * on Sat Jan 16 13:57:11 EST 1999 * * This software is provided WITHOUT WARRANTY either expressed or * implied. * */ package javaservlets.CodeGen; import java.io.*; import javaservlets.tunnel.client.*; /** *This class implements the client for tunneling * calls to the javaservlets.CodeGen.Math object. */ public class RemoteMathClient extends javaservlets.tunnel.client.TunnelClient implements javaservlets.CodeGen.MathInterface { /** *
Constructs a new RemoteMathClient for the * given URL. This will create a new javaservlets.CodeGen.Math * object on the server as well. * @param url The server root URL for servlets */ public RemoteMathClient(String url) throws TunnelException, IOException { this(url, false); } /** *
Constructs a new RemoteMathClient for the * given URL. This will create a new javaservlets.CodeGen.Math * object on the server as well. * @param url The server root URL for servlets * @param usePackage Indicates whether to use the package name * in the servlet URL */ public RemoteMathClient(String url, boolean usePackage) throws TunnelException, IOException { // Append the package name if necessary if (usePackage) { url += "javaservlets.CodeGen."; } // Append the remote server name url += "RemoteMathServer"; // Set the URL _setURL(new java.net.URL(url)); // Initialize the client and server _initialize(); } public double add(double p0, double p1) { double retValue = 0; try { // Create an internal buffer ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create an object stream to write the request ObjectOutputStream out = (ObjectOutputStream) _createHeader(baos, 0); out.writeObject(new Double(p0)); out.writeObject(new Double(p1)); // Invoke the method ObjectInputStream in = (ObjectInputStream) _invokeMethod(baos.toByteArray()); // Get the return value Object retObject = in.readObject(); retValue = ((Double) retObject).doubleValue(); out.close(); in.close(); } catch (java.io.IOException ex) { ex.printStackTrace(); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } catch (TunnelException ex) { ex.printStackTrace(); } return retValue; } public double subtract(double p0, double p1) { double retValue = 0; try { // Create an internal buffer ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create an object stream to write the request ObjectOutputStream out = (ObjectOutputStream) _createHeader(baos, 1); out.writeObject(new Double(p0)); out.writeObject(new Double(p1)); // Invoke the method ObjectInputStream in = (ObjectInputStream) _invokeMethod(baos.toByteArray()); // Get the return value Object retObject = in.readObject(); retValue = ((Double) retObject).doubleValue(); out.close(); in.close(); } catch (java.io.IOException ex) { ex.printStackTrace(); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } catch (TunnelException ex) { ex.printStackTrace(); } return retValue; } public double multiply(double p0, double p1) { double retValue = 0; try { // Create an internal buffer ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create an object stream to write the request ObjectOutputStream out = (ObjectOutputStream) _createHeader(baos, 2); out.writeObject(new Double(p0)); out.writeObject(new Double(p1)); // Invoke the method ObjectInputStream in = (ObjectInputStream) _invokeMethod(baos.toByteArray()); // Get the return value Object retObject = in.readObject(); retValue = ((Double) retObject).doubleValue(); out.close(); in.close(); } catch (java.io.IOException ex) { ex.printStackTrace(); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } catch (TunnelException ex) { ex.printStackTrace(); } return retValue; } } 图11.19 生成的RemoteMathLiteClient.java /* * @(#)RemoteMathLiteServer * * Generated by javaservlets.CodeGen.ServletGenLiteServer * on Fri Jan 15 21:59:30 EST 1999 * * This software is provided WITHOUT WARRANTY either expressed or * implied. * */ package javaservlets.CodeGen; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import javaservlets.tunnel.server.*; /** *
This class implements the lite server for tunneling * calls to the javaservlets.CodeGen.Math object. 'Lite' servers use * simple data input and output streams and can be used * with JDK 1.0.2 */ public class RemoteMathLiteServer extends javaservlets.tunnel.server.TunnelLiteServer { /** *
Creates a new instance of the server object. * * @return Instance of the server object */ public Object _getNewInstance() throws ServletException { return new javaservlets.CodeGen.Math(); } /** *
Invokes the method for the ordinal given. If the method * throws an exception it will be sent to the client. * * @param Object Server object * @param ordinal Method ordinal * @param in Input stream to read additional parameters * @param out Output stream to write return values */ public void _invokeMethod(Object serverObject, int ordinal, DataInput in, DataOutput out) throws Exception { // Cast the server object javaservlets.CodeGen.Math o = (javaservlets.CodeGen.Math) serverObject; // Evaluate the ordinal switch (ordinal) { case 0: //add double p0_0 = ((DataInputStream) in).readDouble(); double p0_1 = ((DataInputStream) in).readDouble(); double r0 = o.add(p0_0, p0_1); ((DataOutputStream) out).writeDouble(r0); break; case 1: //subtract double p1_0 = ((DataInputStream) in).readDouble(); double p1_1 = ((DataInputStream) in).readDouble(); double r1 = o.subtract(p1_0, p1_1); ((DataOutputStream) out).writeDouble(r1); break; case 2: //multiply double p2_0 = ((DataInputStream) in).readDouble(); double p2_1 = ((DataInputStream) in).readDouble(); double r2 = o.multiply(p2_0, p2_1); ((DataOutputStream) out).writeDouble(r2); break; default: throw new Exception("Invalid ordinal: " + ordinal); } } }
图11.19 生成的RemoteMathLiteServer.java
在第10章中,我们开发了一个名为Indy的使用JDBC的简单对象。这个对象获取指定年份的Indianapolis 500赛事冠军的信息并将它们发送给客户端。我们定义了接口(见图11.21)并编写了它的实现(见图11.22)。
package javaservlets.CodeGen; /** *This interface defines the methods available for * performing queries on the Indianapolis 500 database */ public interface IndyInterface { /** *
Connects to the database. * * @return True if the database connection was established */ boolean connect(); /** *
Closes the database connection */ void close(); /** *
Given the year return the corresponding Indianapolis * 500 record * * @param year Year of the race * @return Indy 500 record or null if not found */ IndyRecord query(int year); }
图11.21 IndyInterface.java
package javaservlets.CodeGen; import java.sql.*; /** *图11.22 Indy.java 我们还手工编写了一个HTTP遂道类型的客户代理和服务器代码存根。现在让我们坐下来,放松,让计算机来为你进行全部的工作。Implements the IndyInterface to provide query capabilities * into the Indianapolis 500 database. */ public class Indy implements IndyInterface { // The JDBC Connection Connection m_connection = null; // A prepared statement to use to query the database PreparedStatement m_ps = null; /** *
Connects to the database. * * @return True if the database connection was established */ public boolean connect() { boolean rc = false; try { // Load the Bridge Class.forName("sun.jdbc.odbc.JdbcOdbcDriver").newInstance(); // Connect to the Access database m_connection = DriverManager.getConnection("jdbc:odbc:MyAccessDataSource"); // Go ahead and create a prepared statement m_ps = m_connection.prepareStatement ("SELECT Year, Driver, AvgSpeed from IndyWinners " + "WHERE Year = ?"); rc = true; } catch (Exception ex) { ex.printStackTrace(); } return rc; } /** *
Closes the database connection */ public void close() { // Close the connection if it was opened if (m_connection != null) { try { m_connection.close(); } catch (SQLException ex) { ex.printStackTrace(); } m_connection = null; } } /** *
Given the year return the corresponding Indianapolis * 500 record * * @param year Year of the race * @return Indy 500 record or null if not found */ public IndyRecord query(int year) { IndyRecord record = null; try { // Set the year parameter m_ps.setInt(1, year); // Execute the query ResultSet rs = m_ps.executeQuery(); // Make sure a record exists if (rs.next()) { // Create a new IndyRecord object record = new IndyRecord(); // Set the values record.year = rs.getInt(1); record.driver = rs.getString(2); record.speed = rs.getDouble(3); } rs.close(); } catch (SQLException ex) { ex.printStackTrace(); record = null; } return record; } }
java javaservlets.CodeGen.ServletGen -ijavaservlets.CodeGen.IndyInterface -cjavaservlets.CodeGen.Indy Generating servlet client proxy Writing RemoteINdyClient.java Generationg servlet server stub Writing RemoteIndyServer.java
这时ServletGen已经创建了基于正规HTTP遂道方法调用(使用ObjectInputStream和ObjectOutputStream来编发数据)的客户代理和服务器代码存根。图11.23显示了RemoteIndyClient的代码,图11.24显示了RemoteIndySer的代码。
/* * @(#)RemoteIndyClient * * Generated by javaservlets.CodeGen.ServletGenClient * on Mon May 04 23:31:58 EDT 1998 * * This software is provided WITHOUT WARRANTY either expressed or * implied. * */ package javaservlets.CodeGen; import java.io.*; import javaservlets.tunnel.client.*; /** *This class implements the client for tunneling * calls to the javaservlets.CodeGen.Indy object. */ public class RemoteIndyClient extends javaservlets.tunnel.client.TunnelClient implements javaservlets.CodeGen.IndyInterface { /** *
Constructs a new RemoteIndyClient for the * given URL. This will create a new javaservlets.CodeGen.Indy * object on the server as well. */ public RemoteIndyClient(String url) throws TunnelException, IOException { // Append the remote server name url += "RemoteIndyServer"; // Set the URL _setURL(new java.net.URL(url)); // Initialize the client and server _initialize(); } public boolean connect() { boolean retValue = false; try { // Create an internal buffer ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create an object stream to write the request ObjectOutputStream out = (ObjectOutputStream) _createHeader(baos, 0); // Invoke the method ObjectInputStream in = (ObjectInputStream) _invokeMethod(baos.toByteArray()); // Get the return value Object retObject = in.readObject(); retValue = ((Boolean) retObject).booleanValue(); out.close(); in.close(); } catch (java.io.IOException ex) { ex.printStackTrace(); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } catch (TunnelException ex) { ex.printStackTrace(); } return retValue; } public void close() { try { // Create an internal buffer ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create an object stream to write the request ObjectOutputStream out = (ObjectOutputStream) _createHeader(baos, 1); // Invoke the method ObjectInputStream in = (ObjectInputStream) _invokeMethod(baos.toByteArray()); out.close(); in.close(); } catch (java.io.IOException ex) { ex.printStackTrace(); } catch (TunnelException ex) { ex.printStackTrace(); } } public javaservlets.CodeGen.IndyRecord query(int p0) { javaservlets.CodeGen.IndyRecord retValue = null; try { // Create an internal buffer ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Create an object stream to write the request ObjectOutputStream out = (ObjectOutputStream) _createHeader(baos, 2); out.writeObject(new Integer(p0)); // Invoke the method ObjectInputStream in = (ObjectInputStream) _invokeMethod(baos.toByteArray()); // Get the return value Object retObject = in.readObject(); retValue = (javaservlets.CodeGen.IndyRecord) retObject; out.close(); in.close(); } catch (java.io.IOException ex) { ex.printStackTrace(); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } catch (TunnelException ex) { ex.printStackTrace(); } return retValue; } } 图11.23 生成的RemoteIndyClient.java /* * @(#)RemoteIndyServer * * Generated by javaservlets.CodeGen.ServletGenServer * on Mon May 04 23:31:59 EDT 1998 * * This software is provided WITHOUT WARRANTY either expressed or * implied. * */ package javaservlets.CodeGen; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import javaservlets.tunnel.server.*; /** *
This class implements the server for tunneling * calls to the javaservlets.CodeGen.Indy object. */ public class RemoteIndyServer extends javaservlets.tunnel.server.TunnelServer { /** *
Creates a new instance of the server object. * * @return Instance of the server object */ public Object _getNewInstance() throws ServletException { return new javaservlets.CodeGen.Indy(); } /** *
Invokes the method for the ordinal given. If the method * throws an exception it will be sent to the client. * * @param Object Server object * @param ordinal Method ordinal * @param in Input stream to read additional parameters * @param out Output stream to write return values */ public void _invokeMethod(Object serverObject, int ordinal, DataInput in, DataOutput out) throws Exception { // Cast the server object javaservlets.CodeGen.Indy o = (javaservlets.CodeGen.Indy) serverObject; // Evaluate the ordinal switch (ordinal) { case 0: //connect boolean r0 = o.connect(); ((ObjectOutputStream) out).writeObject(new Boolean(r0)); break; case 1: //close o.close(); break; case 2: //query int p2_0 = ((Integer) read(in)).intValue(); javaservlets.CodeGen.IndyRecord r2 = o.query(p2_0); ((ObjectOutputStream) out).writeObject(r2); break; default: throw new Exception("Invalid ordinal: " + ordinal); } } /** *
Helper method to read an object from the input stream * * @param in Input stream * @return The next object read from the input stream */ protected Object read(DataInput in) throws Exception { return ((ObjectInputStream) in).readObject(); } }
图11.24 生成的RemoteIndyServer.java
在这一章中,我们进入到了Java编程的一个新的水平,使用Java的内建特性自动创建其他的Java类。这种实现的基础就是Reflection
API技术。Reflection API技术是一组方法和类,它们可以提供有关类的内部结构的信息。我们使用这种强有力的API技术开发代码生成器来生成HTTP上的遂道方法调用所必须的客户代理和服务器端代码存根。
接下来,我们要终止一下Servlet的编程,开发一种可以减轻applet发布工作的应用程序。我们将通过自动创建一个包含applet程序所需的所有类文件的存档文件来实现这项工作。