在一篇博客<<itoo项目实战之EJB中的RMI框架>>中已经对RMI这框架进行了详细的介绍,那么这一章我将带着大家介绍如何设计这个框架
一. 框架设计
一般在Web层调用EJB方法如下:
EJBFacadeLocaleJBFacadeLocal = eJBFacadeHomeLocal.create();
eJBFacadeLocal.eJBmethod();
上述代码中,通过JNDI获得的EJB的Home接口,使用create方法创建一个EJBLocalObject或EJBObject,客户端再通过EJBLocalObject调用EJB的具体方法,如createUser()、insert()、update()等方法。
同样,对于在物理位置上远离服务器的肥客户端,我们也希望以类似的方式实现。但是,EJBLocalObject或EJBObject并不能通过网络传送到远程肥客户端。那么,只能将远程客户端的EJB调用在服务器端进行实现,然后将处理结果送回远程肥客户端。
远程客户端调用EJB代码如下:
EJBFacadeLocaleJBFacadeLocal = (EJBFacadeLocal)serviceFactory.getService();
eJBFacadeLocal.eJBmethod(Objectparam);
远程客户端从serviceFactory获得一个EJBService,然后调用该Service的方法eJBmethod()。如果将方法eJBmethod和方法参数Objectparam通过HTTP协议传递到服务器端,由服务器端EJB相应的方法处理后,再将结果送回,这样就完成了远程客户端对EJB的虚拟调用。
上述过程非常类似WebService的JAX-RPC实现,了解本框架的RPC实现过程,也将有助于理解JAX-RPC实现原理。
二. 代理(Proxy)模式
在GOF设计模式中对代理模式的定义是:为其他对象提供一种代理,以控制对这个对象的访问。代理模式可以强迫客户端对一个对象的方法调用间接通过代理类进行。
通常代理模式有以下几种:访问代理(Access Proxy)、虚拟代理和远程代理等。 访问代理是对访问服务或数据对象实现安全权限控制,虚拟代理可实现对象不同方式的初始化,具体有两种方式:即时的和懒惰的,懒惰初始化是相对即时初始化而言,它不是在这个对象生成时立即进行初始化工作,而是只在被访问使用时的才进行初始化。
远程代理是用于屏蔽或保护客户端离开远距离的原始对象,当客户端访问一个对象需要经过漫长的网络调用或大数据读写时,可以通过在本地设置中间代理来代替远程的原始对象。
上图显示了代理模式的原理,代理通常是一个接口或类的实现,它与被代理的子类拥有相同的父类。
以Java代码为例,假设有一个接口为BaseInterface,定义行为myMethod,如下:
publicinterface BaseIF{
public Object myMethod();
}
有一个原始类OriginClass是BaseIF的具体实现:
public classOriginClass implements BaseIF{
public Object myMethod (){
return " hello , It is me! ";
}
}
在正常情况下,客户端调用原始类OriginClass如下:
BaseIF instance = new OriginClass();
String result = (String )instance. myMethod (); //实现方法操作
System.out.println(result); //将会输出 hello , It is me!
但是,如果这个原始类OriginClass处于远程,需要通过Internet或很长的网络调用才能访问到,那么有必要使用一个代理来代理这个原始类OriginClass,代理类如下:
public classProxyClass implements BaseIF{
public ObjectmyMethod (){
//通过网络协议调用远程的OriginClass
BaseIF instance = getRemoteOrigin ();
Returninstance.myMethod ();
}
…
}
ProxyClass类也是接口BaseIF的实现,在myMethod方法中,是通过getRemoteOrigin()获得OriginClass实例。在getRemoteOrigin()中封装的是远程网络调用细节代码。
客户端调用基本没有变化:
BaseIF instance = new Proxy Class();
String =(String)instance.myMethod ();
System.out.println(result); //将会输出 hello , It is me!
客户端调用ProxyClass的方法myMethod,实际就是调用了远程OriginClass的myMethod方法,Proxy类只是一个中间代理类,类似“二传手”。
如果不使用代理类ProxyClass,那么就要在客户端代码中直接实现对远程OriginClass的调用,这样会导致客户端代码在修改或拓展时非常不方便。很显然,使用代理ProxyClass可以封装这些网络调用细节,使得客户端代码变得非常简洁。
使用代理模式可以解决实际编程中很多问题,但是它的缺点是ProxyClass和OriginClass还是比较紧密地耦合在一起,使用动态代理就可以克服这个缺点。
三. 动态代理
动态代理(DynamicProxy)是JDK1.3以后推出的新API,它是代理模式的一种实现。动态代理利用Java的反射(Reflect)机制,可以在运行时刻将一个对象实例的方法调用分派到另外一个对象实例的调用,注意是运行时刻而不是在源代码编译时,动态代理模式可以在运行时刻创建继承某个接口的类型安全的代理对象,而无需在代码编译时编译这些代理类代码。
也就是说,如果使用了动态代理,前面讨论的代理模式示例中的ProxyClass类就不需要了。但是,ProxyClass类方法myMethod中的网络调用细节代码是如何在动态代理中实现的呢?
在动态代理中,对原始类的方法(如myMethod)调用将变成对一个叫invocationhandler类的方法调用,原始类的方法被编码进入java.lang.reflect的Method对象,方法参数则被包含进入一组对象数组。也就是说,ProxyClass类中的网络调用细节代码必须放置在invocationhandler 类的方法中实现。
动态代理API由两大部分组成,首先需要一个接口定义:
publicinterface BaseIF{
public void myMethod ();
}
第二步是实现invocationhandler,如下:
public ClassDynamicProxyClass implements java.lang.reflect.InvocationHandler
{
Object obj;
publicDynamicProxyClass(Object obj)
{ this.obj = obj; }
public Object invoke(Objectproxy, Method m, Object[] args) throws Throwable
{
if(m.getName().equals(“myMethod”)){
//通过网络协议调用远程的OriginClass
BaseIF instance = getRemoteOrigin ();
returninstance.myMethod ();
}
}
…
}
DynamicProxyClass中主要部分是invoke方法,在这个方法中,通过getRemoteOrigin获得远程OriginClass实例。
客户端调用代码如下:
DynamicProxyClassdynamicProxyClass = new DynamicProxyClass(obj)
BaseIFinstance = (BaseIF)
java.lang.reflect.Proxy.newProxyInstance(dynamicProxyClass.getClass().getClassLoader(),
Class[] { BaseIF.class },
dynamicProxyClass);
String =(String)instance.myMethod ();
System.out.println(result); //将会输出 hello , It is me!
上述代码显得繁琐一点,第一行是产生DynamicProxyClass实例,然后通过java.lang.reflect.Proxy的newProxyInstance产生代理实例,这个代理实例是在运行时动态产生的。
当通过instance.myMethod()调用时,系统将直接执行DynamicProxyClass的invoke方法,invoke方法有两个重要输入参数Method m和Object[]args,系统自动将方法myMethod编码进入参数m,如果myMethod有参数,如myMethod(Stringparam),那么方法参数param将被包含进入args中。
动态代理有一些HOOK(钩子),如果能理解了这些钩子的来龙去脉,那么就比较容易使用动态代理API了。
使用动态代理API要注意以下几点。
代理接口(如BaseIF.class)必须是一个接口,不能是普通类或抽象类。
Proxy.newProxyInstance的输入参数中,代理接口数组中不能包含相同的代理接口,即不能出现Class[] {BaseIF.class, BaseIF.class }。
所有的代理接口必须对于Proxy.newProxyInstance输入参数ClassLoader是可见的,即ClassLoader必须能够调用到代理接口。如dynamicProxyClass.getClass().getClassLoader()必须能够调用到BaseIF.class。
代理接口不能有冲突的方法名,如,不能有这样的两个方法:有同样的输入参数,但是返回的是不同类型。
在本框架设计中,将通过动态代理API实现远程客户端访问EJB。按照动态代理的使用规则,首先需要定义一个接口,而EJB的remote接口或local接口正好符合这个要求,下一步是如何从远程客户端发出请求数据以及获得EJB的处理结果。
四.反射(Reflection)和方法调用
Proxy.newProxyInstance属于Java反射(Reflection)的一种实现,除此之外,反射还有字段Field、方法Method等实现,Java反射机制是一种提供了类安全的,支持类和对象自省机制的API。通过反射机制,可以做到以下几点:
(1)创建新的类的实例和新的数组。如下列代码并非使用new来创建一个新的对象:
StringclassName = "com.jdon.bussinessproxy.web.ServiceFactoryImp";
Class c =Class.forName(className);
ServiceServerFactoryfactory = (ServiceServerFactory) c.newInstance();
上例中,通过Class.forName获得一个Class实例,使用Class.newInstance直接生成了对象实例,类似直接使用new获得对象实例:
ServiceFactoryserviceFactoryImp = new ServiceFactoryImp();
(2)访问并修改对象和类的字段。例如,能直接获得Java 类中各成员的名称并显示出来:
importjunit.framework.*;
importjava.lang.reflect.*;
public classTestReflectField extends TestCase {
public int oneField = 1;
public void testMthod()throws Throwable {
Class c =this.getClass();
Field fld= c.getField("oneField");
System.out.println("oneField = " + fld.getInt(this)); //结果oneField = 1
fld.setInt(this, 99); //改变oneField字段值为99
System.out.println("oneField = " + fld.getInt(this)); //结果oneField = 99
}
}
在例子中,通过Class实例getField方法来获得或者修改运行中对象的字段值。
(3)直接调用对象或类的方法。如下列代码:
importjunit.framework.*;
importjava.lang.reflect.*;
public classTestReflectField extends TestCase {
public int update(int x,int y) {
return x + y;
}
public void testMthod()throws Throwable {
Class c =this.getClass();
Class types[] =new Class[2];
types[0] =Integer.TYPE;
types[1] =Integer.TYPE;
Method m =c.getMethod("update", types);
ObjectinputParams[] = new Object[2];
inputParams[0]= new Integer(1);
inputParams[1]= new Integer(99);
Integer obj =(Integer) m.invoke(this, inputParams);
System.out.println(obj.intValue()); //输出结果是 100
}
}
上述代码功能实现类似于下列一行语句:
this.update(1,99);
通过使用反射的Method,从另外一个角度实现了方法调用,虽然代码显得繁琐一点,但在一些特殊情况下使用却非常方便,有很强的解耦性和简易性。
使用反射的Method语句Methodm = c.getMethod("update",types),可以灵活地在运行时刻指定方法的名称和方法参数类型。例如可以通过Method m = c.getMethod("add",types) 调用add方法。这点非常适合本框架系统的远程方法调用。
客户端通过上节的动态代理机制直接调用远程的方法,调用远程方法的关键代码是在DynamicProxyClass的invoke方法中:
if(m.getName().equals(“myMethod”)){
//通过网络协议调用远程的OriginClass
BaseIF instance = getRemoteOrigin ();
returninstance.myMethod ();
}
其中getRemoteOrigin()的实现是关键。getRemoteOrigin ()实际是将方法和参数Method m和Object[]args通过协议传送到远程服务器,服务器获得客户端调用某个EJB的方法名、参数类型以及参数数值后,可以通过方法反射机制直接调用该EJB的方法。
这样,通过客户端的动态代理机制和服务器端的方法反射机制,就可以完成远程客户端对服务器EJBService的访问。那么,如何将远程客户端调用的EJB方法名、参数类型和参数数值传送到服务器端?
五. HTTP协议和对象序列化
HTTP协议是一种请求/应答式的协议。以浏览器为例,当浏览器发送一个请求,在后台建立一个连接到服务器80端口的基于Socket的TCP连接,服务器处理后,返回该请求的应答。HTTP协议最新的版本是HTTP/1.1,HTTP/1.1由RFC 2616 定义。
HTTP协议主要分请求和响应两大部分。
请求部分:请求行、请求头和请求正文,当一个浏览器访问http://localhost/ProxyServle网址时,浏览器将发出如下HTTP请求:
POST/ProxyServlet HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding:gzip,deflate,compress,identity
param1=1234¶m2=6789¶m=jdon.com
其中第一行是请求行,定义了请求方法POST或GET等方法以及URI资源;第二行开始是请求头,提供一些有关客户端环境和请求正文的帮助信息;请求头结束后,是一个空白行(只有CRLF符号的行),是请求头和正文之间的分界线。
HTTP响应分3个部分:状态行、响应头部和响应正文。示例如下:
HTTP/1.1 200OK
Server:Apache/1.3.12 (Unix) mod_perl/1.24
Date: Fri, 20Jul 2003 19:31:10 GMT
Content-Type:text/html
Last-Modified:Fri, 20 Jul 2003 18:34:06 GMT
Content-Length:150
Connection:close
<html>
<head><title>welcome</title></head><body>
You areWelcome
</body></html>
状态行中包括状态码,如第一行中200表示成功,响应头部也包含一些例如服务器类型、日期时间、内容类型和长度等信息。
HTTP协议有如下特点。
简单快速,由于HTTP协议简单,使得HTTP两端的处理程序规模较小,处理速度比较快;相比WebService的SOAP协议,HTTP协议无需携带复杂的XML格式文本,因而更加轻便,通信速度也很快。
HTTP可以传输任意类型的对象,传输类型由Content-Type加以标记,利用这个特点可以实现本框架系统的对象序列化传输,将对象序列化成字节进行传输。
每次请求/响应结束后,基于Socket的TCP连接就断开,属于非长连接,优点是可以节省输出时间,抗网络干扰强;每次连接结束后不留下任何状态,需要应用系统进行HTTPSession等状态保存。HTTP协议可以穿透大多数安全防火墙,在远程通信方面,此点要优于Java的RMI。
HTTP/1.1还提供了身份认证、状态管理和Cache缓存等机制,特别是身份认证,结合J2EE提供的基于HTTP的安全认证机制,可以很方便地实现远程客户端和J2EE服务器的安全通信机制(更进一步可以采取HTTPS)。
综合以上特点,在本框架系统中,可以采取HTTP协议实现客户端和服务器通信的统一协议。
HTTP协议实际上是由一个个数据字节组成的,如果需要HTTP协议携带一个对象,那么就必须将这个对象编码转换成数据字节流保存在HTTP协议的正文部分,将对象编码变成字节流的过程就是对象序列化;当服务器端接受到序列化的字节流后,需要再将之反转为对象,这就是对象的反序列化。将对象从字节流中读出或者写入有两个主要类:ObjectOutputStream与ObjectInputStream 。ObjectOutputStream提供将对象写入输出流的writeObject方法,ObjectInputStream提供从输入流中读出对象的readObject方法。注意使用这些方法的对象是可被序列化的。
例如,将一个对象序列化到一个HTTP连接代码如下:
ObjectOutputStreamoos = new ObjectOutputStream(conn.getOutputStream());
oos.writeObject(myObject);
oos.close();
首先从一个HTTP连接中获取一个OutputStream,然后使用ObjectOutputStream将对象myObject序列化到这个HTTP连接中。
相反,从HTTP字节流中读取对象,代码如下:
ObjectInputStreamois = new ObjectInputStream(conn.getInputStream());
myObject =ois.readObject();
ois.close();
对象是否可以被序列化将也是非常重要的一个环节,能够被序列化的那些类必须继承实现java.io.Serializable接口(或继承实现Externalizable 接口)。
在Java中下列一些类型和对象都可被序列化:原始变量如intfloat型、数组、Collection等,对于JDK中一些类无法确定是否可被序列化时,使用JDK提供工具serialver就可以检查。
六. 框架设计图
结合前面的分析,针对本框架系统的设计需求,在图7-3中展示了如何结合HTTP协议实现方法的远程调用。
在图中的客户端中,要实行一个方法的调用,比如下列语句:
EJBFacadeLocaleJBFacadeLocal = (EJBFacadeLocal)serviceFactory.getService();
eJBFacadeLocal.eJBmethod(); //远程方法调用
eJBmethod是接口eJBFacadeLocal的一个方法。eJBFacadeLocal通过动态代理获得的实例、eJBmethod方法和方法参数则被整合进入动态代理API的invocationhandler中,因此需要在invocation handler的invoke方法中实现远程调用。
在上节动态代理中,这段代码示例是:
public Objectinvoke(Object proxy, Method m, Object[] args) throws Throwable
{
if(m.getName().equals(“myMethod”)){
//通过网络协议调用远程的OriginClass
BaseIF instance = getRemoteOrigin ();
returninstance.myMethod ();
}
}
其中getRemoteOrigin是实现远程调用的方法。在这个方法中将通过HTTP协议和对象序列化技术来实现远程调用,将eJBmethod方法和参数传送到服务器上,通过服务器的ServletProxy递交由EJB实现处理,处理结果再经过HTTP协议传回客户端。
由于eJBmethod方法和它的方法参数已经被整合进动态代理的Method m和Object[]args,因此真正需要被序列化的对象是动态代理的Method m和Object[] args。
运行serialver–java.lang.reflect.Method,检查发现java.lang.reflect.Method是不能被序列化的,因此,只能进一步分解Method。
研究服务器端的EJB方法调用发现,其实,EJB方法调用也并一定完全需要整个Method,它只需要Method实例的两个值方法名称m.getName()和参数类型m.getParameterTypes()。因此,可以将远程客户端调用EJB方法的方法名、参数类型和参数数值等3个参数序列化传送到服务器端。
在浏览器/服务器架构下,Web层相对EJB来说,是本地客户端,可以通过RMI直接实现EJB的方法调用。在这种情况下,本框架非常类似于前面章节中讨论的Web和EJB接口框架系统,本框架与该接口框架相比,有下列几个优点。
在原来的接口框架系统下调用EJB时,需要一段代码首先实现EJBHome的JNDI寻找定位,这在本框架系统中已经不必要了,避免了过多的EJB相关对象。
在Web层通过动态代理直接调用EJB方法实现了表现层和核心逻辑业务层的完全解耦,可以分别独立开发以及维护,表现层无需处理一些EJB出错信息,如RemoteException等。
由于在动态代理中使用了缓存机制,提高了EJB调用性能。
七. HTTPSession和缓存机制
尽管在J2SE1.4以后版本中,Java的反射机制性能得到了很大提高。但是在实际运行中,还是需要通过缓存Cache来进一步提高性能。
考察本系统有两处需要实现缓存:
一个是Proxy.newProxyInstance生成的动态对象实例,如果每次都使用这条反射语句获得动态对象实例,显然会影响速度,可以在第一次生成动态对象实例后将其保存起来,下次需要该对象实例时从缓存中获取。
另外一处是EJBLocalObject或EJBObject实例的生成,当EJB创建后,调用该EJB的客户端拥有一个指向EJBLocalObject或EJBObject的引用。
对于无状态Bean,每个无状态Home接口只有一个EJBLocalObject或EJBObject,EJBLocalObject或EJBObject被调用该无状态Bean的所有用户共享,容器负责创建或销毁无状态Bean的实例。
当每次通过调用无状态Bean的Home接口create()方法时,客户端会获得和上次同样的EJBLocalObject或EJBObject,对无状态Bean,将EJBLocalObject或EJBObject引用放入缓存是可以的。
对于有状态Bean,需要将EJBLocalObject或EJBObject引用加入HTTPSession缓存,因为一个客户端可能会在其Session周期内反复调用有状态Bean。
在实际操作中,无论是无状态Bean或有状态Bean调用,其实都可以使用HTTPSession来保存EJBLocalObject或EJBObject的引用,这样就不必客户端自己声明它调用的SessionBean是有状态还是无状态。
另外,在系统的开发和维护中,有状态Bean和无状态Bean都可能相互转换和变化,使用HTTPSession作为缓存就可以让系统对于这种变化有很强的适应性。使用HTTPSession作为缓存也是有缺点的,它消耗了一定的内存空间,这也是缓存机制的缺点。
八. 基于HTTP的安全机制
以上讨论基本解决了整个框架的流程设计以及相关性能问题,本标题讨论的是安全验证问题,当肥客户端从远程访问J2EE服务器时,必须对这种访问进行用户名和口令验证。
用户名和口令进行拦截验证有两种主要方式:一种是使用容器的安全验证机制,如基于HTTP的验证登录机制;还有一种是自己实现用户名和口令的验证。可以根据实际情况进行两种方式的选用。
首先简要地讨论一下第二种实现方式:开发者自己实施用户名和口令的验证。这种方式实现起来比较复杂,需要分两步:
(1)实现对用户第一次登录的信息进行验证,通过对照数据库(或LDAP)中的用户登录名和密码,检查用户输入和数据库保存是否一致,如果一致,表示验证通过。
(2)需要将用户验证成功后的登录信息传达给EJB安全机制,假设使用的是JBoss服务器,那么需要执行如下代码:
/以login构造一个principal
Principalprincipal = new DefaultPrincipal(login);
Subjectsubject = new Subject();
//将构造的principal加入subject
subject.getPrincipals().add(principal);
//使用JBoss提供的ClientLoginModule登录模块
//该模块只是将用户的登录信息告诉EJB容器,不实行验证
LoginModuleloginModule = new ClientLoginModule();
Hashtableoptions = new Hashtable(2);
//每个线程代表一个用户登录,每个用户有自己的基本空间
options.put("multi-thread", "true");
//将登录名和密码传送给Jboss
options.put("password-stacking","useFirstPass");
HashtablesharedState = new Hashtable(2);
sharedState.put("javax.security.auth.login.name",login);
sharedState.put("javax.security.auth.login.password",password);
//实行初始化
loginModule.initialize(subject,null, sharedState, options);
loginModule.login();
相比上述复杂的实现过程,使用第一种方式,也就是容器的安全登录验证机制则要简单得多,“用户安全管理系统”中也是采取这种机制,这个机制实现需要简单的两步。
(1)在服务器的Web.xml配置中表明是基于HTTP的基本认证,如下:
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
其他关于LoginModule的配置参考“用户安全管理系统”的章节。
(2)在客户端将登录信息编码进入HTTP协议。
根据RFC2617,必须在HTTP协议的请求头部以<username>:<password>的形式提供验证信息,username和password分别表示base64加密的用户名和密码。具体形式如下:
Authorization:Basic <username>:<password>
为了测试基于HTTP客户端/服务器连接以及安全验证,建立一个服务器端Servlet如下:
public classTestServlet extends HttpServlet {
//Initialize globalvariables
public voidinit(ServletConfig config) throws ServletException {
super.init(config);
}
//Process the HTTP Postrequest
public voiddoPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
Stringj_username = null;
Stringj_password = null;
try {
j_username = request.getParameter("j_username");
j_password = request.getParameter("j_password");
} catch(Exception e) {
e.printStackTrace();
}
response.setContentType("text/html");
PrintWriter out= new PrintWriter(response.getOutputStream());
out.println("<html>");
out.println("<head><title>j_username</title></head>");
out.println("<body> j_username=");
out.println(j_username);
out.println("<p> j_password=");
out.println(j_password);
out.println("</body></html>");
out.close();
}
//Get Servlet information
public StringgetServletInfo() {
return"servlettest.aservlet Information";
}
}
这个Servlet接受到客户端参数后将以HTML格式输出结果,再看客户端代码:
public classTestClient {
public static voidmain(String[] args) {
try {
String url = "http://localhost:8080/test/TestServlet";
//装入参数
Hashtable param = new Hashtable();
param.put("j_username", "banq");
param.put("j_password", "1970513");
HttpURLConnection conn =
(HttpURLConnection) (new URL(url).openConnection());
conn.setRequestMethod("POST");
conn.setFollowRedirects(true);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestProperty("Content-type",
"application/x-www-form-urlencoded");
//设置HTTP请求验证头部
String password = "banq:555";
String encoded = "Basic "+Base64.encode(password.getBytes("UTF-8"));
conn.setRequestProperty( "Authorization", encoded );
//模拟表单提交,将需要的Post参数写入HTTP连接
PrintWriter out =
new PrintWriter(newOutputStreamWriter(conn.getOutputStream()));
String paramString = "";
//以key=param& key=param& key=param…形式写入参数
for(Enumeration e = param.keys(); e.hasMoreElements(); ) {
String key = (String) e.nextElement();
String value = (String) param.get(key);
// no harm for an extra & at the end of theparameter list
paramString += key + "=" +URLEncoder.encode(value) + "&";
}
paramString = paramString.substring(0, paramString.length() - 1);
out.println(paramString);
out.close();
System.out.println (“status = ” = conn.getResponseCode());
//读取服务器的响应信息
BufferedReader br =
new BufferedReader(newInputStreamReader(conn.getInputStream()));
String line = null;
while ( (line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
conn.disconnect();
} catch(Exception e) {
e.printStackTrace();
}
}
}
该测试示例运行后,应该在客户端收到如下结果:
status = 200
<html>
<head><title>j_username</title></head>
<body>j_username=banq
<p>j_password=1970513
</body></html>
这是未启动J2EE服务器的安全验证机制情况下的结果,如果按照“用户安全管理系统”介绍的那样,启动了JBoss的安全验证机制,那么只需要改变Stringpassword = "banq:555"中用户和密码的值。如果是错误用户名和密码,那么将得到错误状态码: status = 401表示该请求未得到授权验证。
比较上述两种权限验证体系:开发者自己实施验证和依赖容器实施验证,它们各有优缺点,前者可适应不同J2EE平台,但是依赖具体的数据库实施,和具体应用程序耦合性较强;后者实现虽然比较依赖J2EE的系统配置,但是使得程序代码更加简洁,是框架型产品推荐用法。RMI框架设计也将采取后者方式。