虽然Swing客户端的表现力非常强,但是由于Swing客户端的部署安装非常麻烦,所以在需要无固定地点登录系统进行数据查询的场合,必须采用Web方式来实现。本章就来讨论使用Web实现客户端的一些实现技术。
16.1 Web端部署方式与相关辅助类
根据分层开发的思想,Web端通常需要使用应用服务器提供的服务,因此像Swing客户端一样需要一个部署方式以及调用应用服务器服务的方式。和Swing客户端方式不同的是运行Web端的服务器有可能和应用服务器是运行在同一个服务器的同一个JVM下的,这种方式被称为“并置应用”部署模式。当处于公网的时候这种方式是不被推荐的,因为应用服务器的所有服务接口暴露给了外部访问者,这样增加了系统被攻击的概率,比较好的方式是将应用服务器和数据库等都放在防火墙内部,对外只暴露Web服务器。不过如果应用只是供内网使用的,采用并置的方式则提高了系统的运行速度,因为同一JVM内的调用会比跨JVM的调用速度更快。
无论那种部署方式,Web端都是需要访问元数据以及应用服务器的,所以同样需要元数据加载器、远程服务定位器等,而且通常需要一个允许配置应用服务器地址、元数据地址等的配置文件。
Web端的配置文件的结构目前和ClientConfig是一致的,因此直接拷贝一份ClientConfig.xml重命名为WebConfig.xml即可,读取WebConfig.xml的WebConfig.java也是和ClientConfig.java相似的,所以同样拷贝一份ClientConfig.java并稍作修改以后保存为WebConfig.java。虽然ClientConfig.java的实现和WebConfig.java是非常相似的,但是不能通过继承等方式来实现代码重用,因为这种相似是临时的、不稳定的,在这种情况下采用“Ctrl+C”、“Ctrl+V”的“代码重用”方式是一种比较合理的选择。
16.1.1 SessionId的存储
和Swing一样,Web端在进入系统之前也首先要登录,登录成功以后服务器会分配一个SessionId,在Swing客户端中将SessionId保存到RemoteServiceLocator的一个静态变量中,但是由于HTTP服务的无状态特性并且一个Web服务器是同时为多个登录客户服务的,所以不能将这个SessionId保存到一个普通静态变量中。必须采取其他的机制将这个SessionId保存到能唯一标识这个登录用户的地方。
在Web中有如下几种方式用来存储SessionId。
(1) 隐藏字段
通过HTML的Hidden标记来保存信息:
<input type=hidden name="SessionId" value="ABCD123-CCEDMS-99818848">
这种方式需要在每个页面中都增加这个隐藏字段,在服务器端处理表单的时候也要及时读取这个字段,在每次将响应发送回客户端的时候也要将SessionId写到这个字段中。而且这种方式有保密性问题,恶意攻击者可以很容易地从HTML源代码中找到SessionId。
(2) URL重写技术
把SessionId写到页面链接的尾部,例如:
<a href="/InvReport.jsp">库存报表</a>
改为:
<a href="/InvReport.jsp?SessionId=ABCD123-CCEDMS-99818848">库存报表</a>
这种方式同样存在安全问题。
(3) Cookie
可以把一些小的信息片段保存在客户端,在以后的使用过程中,服务器端可以去读取客户端的Cookie。这种方式无须在每个页面中加入处理代码,用的时候直接调用服务端的方法就可以读取Cookie,安全性也比较好,但是由于早期用户对Cookie的误解造成很多用户将浏览器的Cookie功能关闭,这样系统就不能正常运行了。
(4) Session
这里的Session和应用服务器中的Session原理是一致的,不过是两个不同的东西,为了避免混淆,我们称这里的Session为WebSession。WebSession是Web服务器为每个登录客户端在服务器中设立的一个数据区,客户端只要将数据放入WebSession中,以后的调用中就可以直接从WebSession中取得这些数据了。通过这种方式分配的SessionId是保存在Web服务器端的,系统的安全性得到了提高,因此我们决定将分配的SessionId保存在WebSession中。
客户端浏览器登录Web服务器的时候,Web服务器同样会为此客户端分配一个SessionId以唯一标识此用户,不过这个WebSessionId是不用去关心的,Web服务器会正确处理WebSessionId的保存。如果浏览器支持Cookie的话,Web服务器就会将WebSessionId保存在Cookie中;如果浏览器不支持Cookie的话,就采用URL重写技术将SessionId通过URL传递。
和WebSession对应的是HttpSession接口,我们只要将应用服务器分配的SessionId作为一个Attribute保存到WebSession中即可,WebSession中每个Attribute必须有一个名字作为键,此处将保存SessionId的键命名为“SessionId”并定义在WebConstant类中作为常量:
public final static String SESSIONID = "SessionId";
为了简化调用,创建一个WebContextHelper类,并增加一个getSessionId静态方法用来从WebSession中取得SessionId:
public static String getSessionId(HttpServletRequest request)
{
HttpSession session = request.getSession();
return (String) session.getAttribute(WebConstant.SESSIONID);
}
16.1.2 Web端应用服务定位器
与Swing方式的远程服务定位器一样,Web端应用服务定位器使得取得应用服务器服务的过程透明化,无须考虑服务器的位置、如何连接服务器等问题,实现代码和RemoteServiceLocator非常相似。
【例11.1】在Web端定位服务的定位器。
代码如下:
// Web端服务定位器
public class WebEndServiceLocator
{
public static Object getService(HttpServletRequest request,
Class serviceIntfClass)
{
HttpSession webSession = request.getSession();
String sessionId = (String) webSession
.getAttribute(WebConstant.SESSIONID);
return getService(sessionId, serviceIntfClass);
}
public static Object getService(String sessionId,
Class serviceIntfClass)
{
WebConfig webConfig = WebConfig.getInstance();
HttpInvokerProxyFactoryBean proxyFactory =
new HttpInvokerProxyFactoryBean();
CommonsHttpInvokerRequestExecutor reqExecutor =
new CommonsHttpInvokerRequestExecutor();
proxyFactory.setHttpInvokerRequestExecutor(reqExecutor);
try
{
String serviceIntfName = serviceIntfClass.getName();
String serviceURL = webConfig.getServerURL().toString()
+ serviceIntfName;
if (!StringUtils.isEmpty(sessionId))
{
StringBuffer sb = new StringBuffer();
sb.append(serviceURL).append("?")
.append(SysConstants.SESSIONID).append("=")
.append(sessionId);
proxyFactory.setServiceUrl(sb.toString());
} else
{
proxyFactory.setServiceUrl(serviceURL);
}
proxyFactory.setServiceInterface(serviceIntfClass);
proxyFactory.afterPropertiesSet();
Object service = proxyFactory.getObject();
return service;
} catch (MalformedURLException e)
{
throw new RemoteServerException(
RemoteServerException.CONNECTSERVERERROR, e);
}
}
}
这里假设Web服务器和应用服务器处于不同的JVM中,所以和RemoteServiceLocator一样采用Spring提供的Remoting访问类来跨JVM进行方法调用。如果采用“并置应用”部署模式的话这种方式就会造成参数的返回结果的无谓的序列化与反序列化,这种情况下可以改由直接调用LocalServiceLocator来取得服务,这样的调用消耗就变成了JVM内的普通方法调用,提高了响应速度。
16.1.3 Web端元数据加载器工厂
在Web端同样需要去读取元数据,因此必须为Web端编写一个元数据加载器工厂,用来生成元数据加载器。
【例16.2】在Web端加载元数据的加载器工厂。
// Web端元数据加载器工厂
public class WebMetaDataLoaderFactory
{
private static MetaDataLoader loader;
public static IMetaDataLoader getLoader()
{
if (loader != null)
{
return loader;
}
WebConfig config = WebConfig.getInstance();
String entityCacheFile = config.getEntityCacheFile();
String metaDataPath = config.getMetaDataPath();
loader = new MetaDataLoader(metaDataPath, entityCacheFile);
loader.setCacheEnable(config.isMetaCacheEnabled());
return loader;
}
}






