[b][size=x-large]直接使用HproseServlet发布服务[/size][/b]
因为在快速入门里面我们已经详细通过图解方式介绍了通过直接使用HproseServlet发布服务的整个过程,这里就不再通过图解方式介绍了,下面我们更多关注的是代码部分。
通过HproseServlet发布服务很简单,直接通过配置方式就可以,如果要发布的类是现成的,您不需要编写一行代码就可以完成发布。
发布的方法可以是静态方法,也可以是实例方法。但必须是public方法。您还可以同时发布多个类中的方法。下面先介绍如何发布类中的实例方法。
[b][size=large]发布实例方法[/size][/b]
在前面快速入门一章里,您已经看过传输简单类型的例子了。所以下面将以传输复杂类型为例,来介绍发布多个类中的实例方法,其中还包括了继承、覆盖、重载方法发布等内容。
先来看第一个类:
package hprose.exam;
import java.util.HashMap;
import java.util.Map;
public class Exam1 {
protected String id;
public Exam1() {
id = "Exam1";
}
public String getID() {
return id;
}
public int sum(int[] nums) {
int sum = 0;
for (int i = 0, n = nums.length; i < n; i++) {
sum += nums[i];
}
return sum;
}
public Map swapKeyAndValue(Map strmap) {
Map.Entry[] entrys = strmap.entrySet().toArray(new Map.Entry[strmap.size()]);
strmap.clear();
for (Map.Entry entry: entrys) {
strmap.put(entry.getValue(), entry.getKey());
}
return strmap;
}
}
上面类中,有3个可以发布的方法getID,sum和swapKeyAndValue。
其中getID返回的是一个实例字段id,该字段定义成protected是为了后面在子类中可以修改其值,这样当客户端调用子类中发布的方法时,就会返回不同于父类方法的值了。如果看到这里您还不清楚说的是什么,没有关系,等后面看到子类定义和发布配置时,您就会明白啦,所以,暂时您可以不用理会这个细节。
sum方法的参数是一个整型数组,其结果是返回数组中所有整数的和。不过当您在客户端调用它时,并不必非要带入整型数组类型的参数,也可以是整型元素的列表。但仍然推荐使用类型相同的参数进行调用,以保证能够得到最好的效率和安全性。
swapKeyAndValue方法的参数是一个存储字符串键值对的Map,其结果为键值对调后的Map。虽然您在这里会发现可以用泛型的Map,但实际上Java的泛型只能在编译期进行检查,在运行时并不能得到泛型信息,因此,实际上并不能保证远程调用时接收到的Map中的键值对一定能转换为泛型中所标注的类型,当传输的类型与标注的类型不同时,就会发生运行时错误。因此当您标注泛型时,对于基本类型只能标注为反序列化默认类型映射中的类型,对于容器类型您可以标注为ArrayList、List、HashMap、Map或Collection,当然还可以标注为自定义可序列化类型。
例如,下面这个方法在本地调用时只要参数类型一致,肯定是没有问题的。但是在使用Hprose进行远程调用时,即使客户端指定的参数是一致的,也仍然会返回错误。
public Map swapKeyAndValue(Map strmap) {
Map.Entry[] entrys = strmap.entrySet().toArray(new Map.Entry[strmap.size()]);
strmap.clear();
for (Map.Entry entry: entrys) {
strmap.put(entry.getValue(), entry.getKey());
}
return strmap;
}
因为Java的Short类型在序列化时会转化为Hprose整型传输,而Hprose整型的反序列化默认类型映射为Java的Integer类型。Integer类型和Short类型永远不会匹配,发生错误就成为了必然。因此,您应该避免在您发布的方法中使用上述的泛型定义。
另一种在使用泛型时常见的错误是,泛型参数为自定义可序列化类型,客户端和服务器端当中泛型参数类型的名字相同,类型中结构定义也相同,但是客户端和服务器端中的这个类型却属于两个不同的包。这种情况下,这两个类型会被判定为不同的类型(实际上也确实是不同的类型),这种情况下的调用也会发生错误。这个问题如果暂时看不明白也没关系,我们在后面也会举例说明。
下面我们来看第二个类,为了说明继承、覆盖和重载方法的发布,第二个类将作为第一个类的子类。不过为了说明传输自定义可序列化类型,我们这里要先看一下在第二个类中所使用的自定义可序列化类型的定义。
package hprose.exam;
public enum Sex {
Unknown, Male, Female, InterSex
}
上面是一个枚举类型,这个枚举类型将在下面的User类中使用,在介绍User类之前,先来说明一下这个枚举类型。
Java的枚举类型默认是从0开始,按照顺序给每个枚举值编号的。因此在Sex类型被序列化时,Unknown、Male、Female、InterSex分别对应的是0,1,2,3。现在您可能已经明白这个顺序不是乱写的了,从位运算的角度来说,这个顺序是有意义的。例如:
Unknown | Male = Male
Unknown | Female = Female
Male | Female = InterSex
不要为InterSex这个性别感到惊奇,我们这个社会并不是一个非男即女的二元性别社会,我们不能也不应该否认双性人的存在,承认InterSex这个性别是对这个群体的尊重。虽然我们这里讨论的问题与此无关。
下面我们继续回到正题上来。
Java中当然不支持对枚举进行位运算,不过在其它语言(例如C#)中枚举类型是可以进行位运算的,因此,合理的安排枚举类型的顺序有时候会方便与其它语言的交互。毕竟使用Hprose后,就可以在多语言之间进行互通了,因此我们在编程时应当考虑到这一点。
下面我们来看User类的定义吧。
package hprose.exam;
import java.sql.Date;
public class User implements java.io.Serializable {
private String name;
private Sex sex;
private Date birthday;
private int age;
private boolean married;
public User() {
}
public User(String name, Sex sex, Date birthday, int age, boolean married) {
this.name = name;
this.sex = sex;
this.birthday = birthday;
this.age = age;
this.married = married;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
}
该类的定义有以下几点需要注意:
1. 必须要实现 java.io.Serializable 接口。
2. 必须要有一个无参构造方法。
3. 字段定义为私有,属性定义为共有。
4. 字段名和属性名应一一对应。
按照这种方式定义的类,既可以按照属性序列化传输,也可以按照字段序列化传输。
下面我们来看第二个要发布的类如何定义:
package hprose.exam;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
public class Exam2 extends Exam1 {
public Exam2() {
id = "Exam2";
}
public List getUserList() {
ArrayList userlist = new ArrayList();
userlist.add(new User("Amy", Sex.Female, Date.valueOf("1983-12-03"), 26, true));
userlist.add(new User("Bob", Sex.Male, Date.valueOf("1989-06-12"), 20, false));
userlist.add(new User("Chris", Sex.Unknown, Date.valueOf("1980-03-08"), 29, true));
userlist.add(new User("Alex", Sex.InterSex, Date.valueOf("1992-06-14"), 17, false));
return userlist;
}
}
这个类中,我们会发现在构造方法中,修改了父类中的id字段值,这样在该类的对象上调用继承来的getID方法时,将会返回"Exam2"这个值。
getUserList方法很简单,就是创建一个User的List,然后返回。
好了,我现在来看如何发布它们吧。我们先来看最简单的发布一个类:
1. 打开“HproseExamServer→配置文件→web.xml”。
2. 打开“Servlet”选单,点击“添加Servlet元素...”按钮,设置Servlet名称为HproseExamServlet,Servlet类为hprose.server.HproseServlet,URL模式为/Methods。
3. 在初始化参数部分,点“添加(A)...”按钮,设置参数名为class,参数值为hprose.exam.Exam2,这里的hprose.exam.Exam2对应上面创建的hprose.exam.Exam2类。
配置后的web.xml内容如下:
HproseExamServlet
hprose.server.HproseServlet
class
hprose.exam.Exam2
HproseExamServlet
/Methods
30
现在就配置好了对hprose.exam.Exam2的发布,但是运行结果可能会出乎您的预料,当您打开浏览器浏览服务页面时,您会发现只有getUserList这一个方法被发布了,从Exam1中继承来的方法都没有被发布,这是正确的吗?
是的,这是正确的,这是因为所有的类都是从祖先类Object继承下来的,如果将继承来的public方法都发布的话,势必会将从Object继承来的方法也一起发布,这肯定不是用户所期望的结果。另一个原因,用户定义的类的层次可能比较深,而其定义的基类上的方法可能并不想被一起发布。因此,默认情况下,只发布直接在该类中声明的public方法,而不会发布继承来的方法。
那如果想发布继承来的方法可以吗?当然可以。您可以这样发布。将上面配置文件中:
class
hprose.exam.Exam2
部分改为:
class
hprose.exam.Exam2|hprose.exam.Exam1
其中,“|”用来分隔参数,第一参数为创建对象的类名,第二个参数为第一个参数的祖先类的类名。
经过上面的配置修改后,您就会发现Exam2上的声明的方法和继承自Exam1的方法都被发布了。
那如果还要发布一个Exam1对象上的方法怎么办呢?您可以通过“,”来分隔要发布的不同的对象的类,但是您会发现不能简单的将配置修改为:
class
hprose.exam.Exam1,hprose.exam.Exam2|hprose.exam.Exam1
这样的话,Exam1对象上的方法将无法被客户端调用,因为它被Exam2对象上的从Exam1继承来的方法覆盖了。
怎样才可以让他们不会冲突呢?很简单,通过增加名字空间(或者叫别名前缀)的方式就可以避免这种来自不同类的相同名称甚至相同参数的方法的重载问题。
正确的修改如下:
class
hprose.exam.Exam1||ex1,hprose.exam.Exam2|hprose.exam.Exam1|ex2
我们发现,这里多了第三个参数,第三个参数就是名字空间,之所有又叫别名前缀是因为它实际上是通过附加在发布的方法前,并用下划线分隔名字空间和方法名来实现的。
从上面的配置您还可以发现,第二个参数可以空缺,空缺时表示发布的方法就是在创建对象的类上直接声明的方法。
经过上面的配置后,运行您的服务器,并打开浏览器浏览发布页,应该可以看到如下的发布列表:
Fa7{s7"ex1_sum"s19"ex1_swapkeyandvalue"s15"ex2_getuserlist"s7"ex2_sum"s9"ex1_getid"s9"ex2_getid"s19"ex2_swapkeyandvalue"}z
这就表示服务发布成功了。