- 浏览: 7873 次
- 性别:
- 来自: 北京
最新评论
一、目的:
JS客户端在编写程序时,可以像调用本地方法一样调用服务器端的Spring中Bean的方法,实现对Spring的动态调用,从而减少客户端的每个请求都要配置成action的麻烦,使业务模块的开发只关注业务逻辑的展示页面的开发。
二、设计思路:
1.服务器端利用Struts机制,提供统一的动态action,名称为DynamicAjaxAction,接收客户端的请求,并返回处理结果;
2.DynamicAjaxAction内部解析请求后,通过java反射机制动态调用spring中的baen方法;
3.客户端封装异步请求方法,提供调用接口;
总体架构如下示意图:
三、详细设计:
1.DynamicAjaxAction的设计:
public class DynamicAjaxAction extends ActionSupport { private ActionInvoker actionInvoker; private DynamicAjaxActionRouting routing; private static final Log log = LogFactory.getLog(DynamicAjaxAction.class); protected static ArrayList<String> ExcludeValues = new ArrayList<String>(); public DynamicAjaxAction() { ExcludeValues.add("_de"); ExcludeValues.add("serviceUrl"); ExcludeValues.add("cacheData"); } public String execute() throws Exception { log.debug("DynamicAjaxAction.execute"); String key = this.getServiceUrl(); Map<String, Object> session = ServletActionContext.getContext().getSession(); HttpServletResponse response = ServletActionContext.getResponse(); data = this.invokerAction(); if(data == null) ExtObject.writeJsonString(response, (new ExtResultObject(true)).toString()); else ExtObject.writeJsonString(response, data.toString()); return null; } protected Object invokerAction() throws Exception { Object[] params = this.getInvokerParams().toArray(); Object obj = actionInvoker.invokerAction(this.getRouting().getServiceName(), this.getRouting().getActionName(), params); return obj; } private String getServiceUrl() { HttpServletRequest request = ServletActionContext.getRequest(); String serviceUrl = request.getParameter("serviceUrl"); return serviceUrl; } private boolean isCacheData() { HttpServletRequest request = ServletActionContext.getRequest(); String serviceUrl = request.getParameter("cacheData"); if(StringUtility.isNullOrEmpty(serviceUrl)) return false; return serviceUrl.equals("true"); } protected DynamicAjaxActionRouting getRouting() { String serviceUrl = this.getServiceUrl(); routing = new DynamicAjaxActionRouting(serviceUrl); return routing; } @SuppressWarnings("unchecked") protected HashMap<String, Object> getRequestParams() { HttpServletRequest request = ServletActionContext.getRequest(); HashMap<String, Object> requestParams = new HashMap<String, Object>(); Object[] params = request.getParameterMap().keySet().toArray(); for(int i = params.length; i > 0; i --) { String item = params[i - 1].toString(); if(!ExcludeValues.contains(item)) { String[] value = request.getParameterValues(item); if(value.length == 0) { //do nothing } else if(value.length == 1) { requestParams.put(item, value[0]); } else { requestParams.put(item, value); } } } return requestParams; } @SuppressWarnings("unchecked") protected ArrayList getInvokerParams() { HttpServletRequest request = ServletActionContext.getRequest(); ArrayList requestParams = new ArrayList(); requestParams.addAll(this.getRouting().getParams()); log.debug("加入调用参数:" + StringUtility.arrayToString(this.routing.getParams())); Object[] params = request.getParameterMap().keySet().toArray(); for(int i = params.length; i > 0; i --) { String item = params[i - 1].toString(); if(!item.equals("serviceUrl") && !item.equals("_dc")) { Object value = request.getParameter(item); //log.debug("发现调用参数:" + item + "=" + value); requestParams.add(value); } } //新增加一个Session对象,允许DWR的方法接收请求的Session对象 requestParams.add(ServletActionContext.getRequest().getSession()); return requestParams; } public ActionInvoker getActionInvoker() { return actionInvoker; } public void setActionInvoker(ActionInvoker actionInvoker) { this.actionInvoker = actionInvoker; } }
DynamicAjaxAction是一个标准的Struts的Action,类的execute方法中,首先接收到请求的参数,重点是名称为serviceUrl的参数,此参数据形如“textService/test/abc”,参数分为三段,以“/”分隔,第一部分是bean的名称,第二部分为方法名称,第三部分为方法的参数,如果有多个参数,分别顺序添加即可。
然后,调用ActionInvoker的方法,通过反射访问目标方法,接收返回结果。
最后,接返回的结果写入response,返回给客户端。
当然, 需要将此类在Struts和Spring中分别进行配置,以确保客户端能正常访问。
2.java反射机制的封装:
public class ActionInvoker { private static final Log log = LogFactory.getLog(ActionInvoker.class); public Object invokerAction(String beanName, String methodName, Object[] parameters) { Object beanIns = BeanFactoryUtility.factory.getBean(beanName); Method methodIns = this.pairMethod(beanIns.getClass(), methodName, parameters); if(methodIns == null) { ExtObject obj = new ExtObject(); obj.addException("没有找到同名方法" + beanIns.getClass().getName() + "." + methodName); return obj; } Object[] mParams = this.getMethodParams(methodIns, parameters); log.debug("调用方法:" + beanName + "." + methodName + ",参数:" + StringUtility.arrayToString(mParams)); try { Object result = methodIns.invoke(beanIns, mParams); return result; } catch (Exception ex) { ExtObject obj = new ExtObject(); obj.add("exception", ex); return obj; } } //查询最匹配的方法 private Method pairMethod(Class cls, String methodName, Object[] parameters) { log.debug("查询方法:" + cls.getSimpleName() + "." + methodName + ",参数:" + StringUtility.arrayToString(parameters)); //根据方法名称查询 //名称相同,参数数目相同,则返回,否则返回异常 //名称相同,参数数目比传入的少,则将传入的参数处理成所需的参数 Method[] ms = this.findMethodByName(cls, methodName, parameters.length); if(ms.length == 0) return null; if(ms.length == 1) return ms[0]; Method m = this.pairMethod(ms, parameters); return m; //TODO 同名方法同参数数据方法,根据参数类型 //判断将调用参数的最可能类型,匹配最相近的方法 //转换不成功,则异常 //TODO 根据参数名称匹配最合适的方法 } /** * 根据调用参数信息,查询最匹配的方法 、围 * @param parameters 调用的参数数组 * @return 从第一个参数开始查询,将不匹配的方法去掉, * 然后再从可以匹配的方法中找到参数最多的,也就是最匹配的方法, * 如果参数最多的有多个,返回随机的一个(其实应该不可能,此处不再抛出异常) */ private Method pairMethod(Method[] ms, Object[] parameters) { //首先找到所有可以调用的方法 ArrayList<Method> methodsCanInvoke = new ArrayList<Method>(); for(Method m : ms) { if(isCanInvoke(m, parameters)) methodsCanInvoke.add(m); } if(methodsCanInvoke.size() == 0) return null; if(methodsCanInvoke.size() == 1) return methodsCanInvoke.get(0); //从查询的方法中找到参数最多的一个 Method result = methodsCanInvoke.get(0); for(Method m : methodsCanInvoke) { if(m.getParameterTypes().length > result.getParameterTypes().length) result = m; } return result; } private boolean isCanInvoke(Method m, Object[] parameters) { if(m.getParameterTypes().length > parameters.length) return false; int i = 0; for(Class c : m.getParameterTypes()) { if(!c.equals(this.getParamType(parameters[i]))) { return false; } i ++; } return true; } private Object[] getMethodParams(Method m, Object[] srcParams) { Object[] mParams = new Object[m.getParameterTypes().length]; for(int i = 0; i < mParams.length; i ++) { mParams[i] = this.formatParam(srcParams[i], m.getParameterTypes()[i]); } log.debug("处理后的方法参数:" + StringUtility.arrayToString(mParams)); return mParams; } private Class getParamType(Object value) { if(StringUtility.isInteger(value)) return Integer.class; if(StringUtility.isLong(value)) return Long.class; if(StringUtility.isDouble(value)) return Double.class; if(StringUtility.isUUID(value)) return UUID.class; return String.class; } private Object formatParam(Object value, Class type) { log.debug("转换参数值:" + value + "为类型" + type.getSimpleName()); if(type == int.class || type == Integer.class) { return Integer.parseInt(value.toString()); } else if(type == long.class || type == Long.class) { return Long.parseLong(value.toString()); } else if(type == double.class || type == Double.class) { return Double.parseDouble(value.toString()); } else if (type.equals(boolean.class)) { return Boolean.parseBoolean(value.toString()); } else if (type.equals(UUID.class)) { return UUID.fromString(value.toString()); } return value; } private Method[] findMethodByName(Class cls, String methodName, int paramCount) { ArrayList<Method> ms = new ArrayList<Method>(); for(Method m : cls.getMethods()) { if(m.getName().equals(methodName)) { if(m.getParameterTypes().length <= paramCount) { ms.add(m); } } } return ms.toArray(new Method[ms.size()]); } }
ActionInvoker是一个针对java动态调用方法的封装,用来根据传入的参数,动态调用请求的方法。
设计的基本思路是:根据beanName和方法名称,确定方法,如果可以确定唯一的目标方法(即方法没有重载),则将请求的参数,按要调用的方法所需的参数进行转换,转换成功后,即可进行调用。如果转换失败或没有发现目标方法,则调用不成功。
如果目标方法不唯一,那么要看请求的参数个数,确定方法中的参数个数小于等于请求的参数个数的方法为目标方法(这个逻辑可以根据自己的应用情况进行修改),然后按照方法的需要把参数转换为所需的类型即可调用。
调用成功后,返回结果。
3.客户端异步请求方法的实现 :
Ext.namespace("justgin.bap"); /** * 全局的Ajax请求类,类似于Ext.Ajax,但个性化了一些领域业务 */ justgin.bap.Ajax = new Ext.data.Connection({ autoAbort : false }); /** * 个性化的领域业务,包括: * 动态Ajax请求的封装(用户只需指定serviceUrl参数即可完成请求,serviceUrl的格式为: * {bean名称}.{方法名}.{参数1}.{参数2}.。。。.{参数n}); * 返回数据的处理(自动将返回的结果包括成json格式的object,供客户端使用); * 请求失败的处理(目前只是弹出提示窗口);等。 */ justgin.bap.Ajax.on('beforerequest', function(conn, config) { Ext.apply(config, {url: '/Domain/DynamicAjax.do', method: 'GET'}); Ext.apply(config, {params: {serviceUrl: config.serviceUrl}}); var successCallback = config.success; Ext.apply(config, {success: function(r, options) { var data = r.responseText.trim(); var o = data; try { o = eval('(' + data + ')'); } catch(e) {} ajaxRequestSuccess(successCallback, o, config.dataCache); }}) Ext.apply(config, {failure: function(r, options) { ajaxRequestFailuere('请求' + config.serviceUrl + '失败。'); }}); }); ajaxRequestSuccess = function(callback, o, dataCache) { callback.call(null, o, dataCache); } ajaxRequestFailuere = function(msg) { alert(msg); }
客户端异步请求的原理很简单,只是对Ext.Ajax.request的一个扩展,用户不需要指定请求的url和参数,只要指定请求的serviceUrl即可,请求失败时,可以根据应用的情况,统一实现一个异常处理的机制,使用者不需要专门为失败编写代码。请求成功后,调用使用都传入的回调方法,将responseText传递给使用都。
四、使用方法:
1.服务器端textService的建立:
public class testService { public ExtObject test(String id) { //id = "abc"; ExtObject eo = new ExtObject(); eo.add("id", "abc"); eo.add("name", "abcd"); return eo; } }
2.服务器端bean配置
<bean id="testService" class="justgin.jbap.samples.TestService"> </bean>
3.客户端调用
justgin.bap.ajax.request({ serviceUrl: 'testService/test/abc', success: function(data) { alert(data.id + data.name); } });
五、总结
动态调用Srping的实现目的主要为了提高js客户端访问服务器端业务逻辑的便捷性,为客户端与服务器端的通讯提供一种解决方案,为了开发者提供方便的使用方式,使其在在业务模块的设计时,更关注于内部逻辑。当然,相关的安全、性能等问题没有列入其中,具体的情况可以根据应用场景的不同进行优化。
下一篇会根据上述内容,针对业务模块开发时常用的列表数据和表单数据提供更具体的分析和设计,使动态调用更有针对性,例如service的参数可以直接是一个业务对象,最大限度地减轻客户端与服务器端通讯时,业务对象和Json之间转换的工作量,敬请关注。
相关推荐
DWR(Direct Web Remoting)是一种Java库,它允许JavaScript在Web浏览器中直接调用Java方法,从而实现实时的、双向的通信。在“DWR3实现服务器端向客户端精确推送消息”这一主题中,我们将深入探讨如何利用DWR3进行...
在Web应用中,Spring框架结合STOMP(Simple Text Oriented Messaging Protocol)协议,能够轻松地实现WebSocket的功能,包括广播订阅、权限认证和一对一通讯。 **一、WebSocket基础** WebSocket API是HTML5的一个...
在IT行业中,Spring框架是Java领域的一个重要工具,主要用于构建企业级应用。在这个场景中,我们关注的是Spring如何处理JSON数据以及客户端如何处理JSON。JSON(JavaScript Object Notation)是一种轻量级的数据交换...
DWR(Direct Web Remoting)是一种Java库,用于在Web应用程序中实现实时的、双向的JavaScript和服务器端代码交互。它打破了传统的HTTP请求-响应模型,允许前端JavaScript与后台Java对象直接通信,实现了真正的异步...
这个小项目展示了如何将移动设备与服务器端进行高效的数据交互,以实现对无线电频谱的监测功能。以下是关于这个项目的具体知识点详解: 1. **Android客户端开发**:Android是Google主导的开源移动操作系统,用于...
接着,我们需要在服务器端定义一个或多个远程服务接口,这些接口的方法将在JavaScript中被调用。例如,我们可以创建一个`MessageService`接口,提供`pushMessage`方法,用于向客户端推送消息。这个接口需要被DWR识别...
在IT行业中,Spring、DHTML、JavaScript和Java是四个至关重要的技术领域,它们共同构建了现代Web应用程序的基础。本文将详细解析这些技术及其在API开发中的应用。 首先,Spring框架是Java企业级应用开发的核心工具...
在IT行业中,Spring Cloud Gateway作为Spring Cloud生态体系中的一个关键组件,被广泛用于构建微服务架构中的API网关。这个框架允许我们集中处理各种请求,包括路由、过滤、安全等,极大地简化了服务间的通信。而...
它通过AJAX技术实现了浏览器与服务器之间的直接调用(Remote Procedure Call,RPC),使得动态更新和实时推送数据成为可能。DWR3中的Server Push特性是其亮点,它能够主动将服务器端的数据推送到客户端,非常适合...
本文将详细讲解如何使用Spring Boot、MyBatis、Spring MVC和Bootstrap技术栈开发一个停车位管理系统,并结合百度地图API实现车位的定位与导航功能。这些技术是现代Web开发中常用且强大的工具,它们各自承担着不同的...
标题中的“Spring2.5+DWR3.0整合实现autocomplete”是指在Web开发中,使用Spring 2.5框架与Direct Web Remoting (DWR) 3.0库结合,来实现一个自动补全(autocomplete)功能。这个功能常见于搜索引擎、表单输入等场景...
在IT领域,JavaScript(简称JS)和Java是两种广泛应用的编程语言,它们分别在Web前端和后端开发中发挥着重要作用。尽管它们在语法和用途上有所不同,但有时需要实现JS与JAVA之间的交互,比如在浏览器环境与服务器...
DWR(Direct Web Remoting)是一种JavaScript库,允许Web应用程序在客户端(浏览器)与服务器端之间进行实时通信,就像在本地应用程序中一样。通过DWR,开发者可以调用服务器上的Java方法并获取返回的结果,而无需...
这样,我们便成功地在Spring Cloud环境中实现了文件上传功能,同时利用了Feign的声明式调用和Zuul的路由与负载均衡能力。 总的来说,通过Spring Cloud的组件,我们可以构建一个高效且可扩展的文件上传系统,它具有...
LCDS**: Spring与Flex之间的通信通常通过BlazeDS或LiveCycle Data Services (LCDS)实现,它们是Adobe提供的服务器端组件,支持AMF(Action Message Format)协议,使得数据能高效地在Flex客户端和Spring服务之间传输...
Node.js,另一方面,基于V8 JavaScript引擎,允许开发者在服务器端使用JavaScript编写高性能的网络应用。Node.js的非阻塞I/O和事件驱动模型使其在处理高并发请求时表现出色。在本项目中,Node.js被用来构建前端部分...
在本项目中,我们主要探讨的是如何将Spring MVC与Mybatis框架进行整合,以实现一个完整的用户管理系统,包括用户登录和基本的CRUD(创建、读取、更新、删除)操作。Spring MVC作为Spring框架的一部分,是用于构建Web...
JavaScript则是一种解释型的、轻量级的编程语言,主要用于客户端的网页脚本,可以操控DOM元素,实现页面动态效果和用户交互。 在Flash和JavaScript的集成中,通常使用ActionScript(Flash的编程语言)编写Flash内容...
GWT是一个用于构建富互联网应用程序(RIA)的开源工具包,它允许开发者使用Java语言编写客户端代码,然后由GWT编译器转换成优化过的JavaScript,从而在浏览器上运行。Spring则是一个强大的后端框架,提供依赖注入、...
总结起来,这个案例展示了一个使用Spring Boot和WebSocket实现的简单聊天应用,包括客户端和服务器端的连接、消息传递以及服务器的主动推送。在实际应用中,还需要考虑更多细节,如错误处理、安全性、性能优化等。这...