博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
前后端中转服务remoteService的设计与实现
阅读量:6615 次
发布时间:2019-06-25

本文共 12030 字,大约阅读时间需要 40 分钟。

hot3.png

    对于常见的MVC模式,controller层常参与返回视图,而实际上造成了前后端紧耦合,而且并不方便单元测试,基于SOA协议、DCI架构,服务端应只专注行为,提供restful风格的服务、json数据。

    本文介绍的是一个remoteService中转服务中心的实现,其功能就是接收前端请求,根据前端请求中的服务接口名、方法名、参数数组去调用bean容器中的对应接口的方法,并返回json格式的数据到前端。

    实现后,前端拉取服务端数据的代码风格如下:

var queryContext = "com.alphalab.moonlight.demo3.IProjectItemQueryContext";var itemConditions = [];var itemCondition = {};itemCondition.jcls = "com.alphalab.framework.domain.ItemCondition";itemCondition.conditionField="name";itemCondition.operator="0";itemCondition.value="2017圣诞项目";itemConditions.push(itemCondition); var result = RemoteService.doPost(queryContext,"findListWithCondition",[itemConditions,"",""]);

    通过这种形式开发的好处如下:

1.前端代码简练整洁,无需关注服务端对应方法的地址、参数名,代码量少,可读性强。

2.实现了前后端松耦合,服务端controller专注于提供restful服务,对于单元测试很方便。

2.remoteService作为唯一的中转控制中心,可以集中统一处理接收参数时各种加密解密、安全性、用户登录超时、访问权限等业务,相当于一个数据总线的角色。

    remoteService实现上的关键逻辑:

1.前端公共组件封装ajax方法,接收包含包路径的接口名、方法名、参数数组,其中传递到后端时,需将参数数组转成json字符串,如传的参数数组中包含对象,则该对象应包括jcls属性,以便服务端识别该对象。

2.服务端接收参数后,根据接口名、方法名从bean容器中找到对应的controller名,通过反射机制找到目标方法,然后将json字符串参数进行类型转换,转成符合目标方法参数列的类型,最后通过反射机制调用方法并返回json结果到前端。

3.对json字符串参数的类型转换最为复杂,需判断传参数组中是否包含对象、对象数组、对象中是否包含动态属性,对于一些复杂类型的处理,如date类型,由于json转换java对象的date类型时默认转成timestamp类型,前端接收会显示为long,故处理date时,约定前端传日期为时间戳long类型,服务端添加一个处理器,使json对象的long类型转成目标java对象的date类型时能自动转换,服务端返回给前端的date类型依旧约定为long时间戳类型,此外选择统一时间戳类型考虑到时间戳精度比date类型高。

remoteService实现的源码如下:

服务端控制器:

/* * Copyright 2000-2020 ALPHA LAB.Inc All Rights Reserved. */package com.alphalab.framework.context; import java.io.UnsupportedEncodingException;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Type;import java.net.URLDecoder;import java.util.ArrayList;import java.util.Iterator;import java.util.List; import javax.servlet.http.HttpServletRequest; import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController; import com.alphalab.framework.util.StringUtil;import net.sf.json.JSONArray;import net.sf.json.JSONObject;import net.sf.json.util.JSONUtils;import org.apache.log4j.Logger;/** * 处理前端请求并调用服务端方法的远程服务中心. * @author allen  * @version 1.0.0 2018年1月10日 */@RestController @RequestMapping("/remoteService")public class RemoteService {		/**	 * LOG.	 */	private static final Logger LOG = Logger.getLogger(RemoteService.class);  		/**	 * 处理前端请求.	 * @param request request	 * @return Object	 */	@RequestMapping({"/handleRequest"})	@ResponseBody	public Object handleRequest(final HttpServletRequest request) {		LOG.info("serviceName:" + request.getParameter("serviceName"));		LOG.info("methodName:" + request.getParameter("methodName"));		LOG.info("parameters:" + request.getParameter("parameters"));		final String serviceName = request.getParameter("serviceName");		final String methodName = request.getParameter("methodName");		String parameters = request.getParameter("parameters");		//解码		try {			parameters = URLDecoder.decode(parameters, "UTF-8");		} catch (UnsupportedEncodingException e) {			LOG.error("parameters 解码有问题");		}   				final ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");		 	    /**	     * 1.查找方法,不必根据参数列查询,因为controller层的方法名一定唯一.	     */	    //getMethods返回的是接口的所有方法,包括自身的和从基类继承的	    Class interfaceClass = null;		try {			interfaceClass = Class.forName(serviceName);		} catch (ClassNotFoundException e) {			LOG.error("没有找到服务" + serviceName);		}	    final Method[] methods = interfaceClass.getMethods();	    Method targetMethod = null;	    Type[] types = null;	    for (Method method : methods) {			if (method.getName().equals(methodName)) {				targetMethod = method;			}		}	    if (targetMethod != null) {			types = targetMethod.getGenericParameterTypes();			int index = 0;			for (Type type : types) { 				LOG.info("第" + (index + 1) + "个参数的参数类型为" + type); 				index++;			}	  	    	 	    } else {	    	LOG.info("方法名没有找到");	    		    } 	    		/**		 * 2.对参数数组进行类型转换,拼接参数列表.		 */  		//添加日期类型的转换,如json对象中的long类型转成目标javabean对象的date类型 		JSONUtils.getMorpherRegistry().registerMorpher(new TimestampMorpher());				final Object[] parameObjs = new Object[types.length]; 		final JSONArray jsonArray = JSONArray.fromObject(parameters); 		if (types.length != jsonArray.size()) {			throw new RuntimeException("参数类型个数不匹配");		}				for (int i = 0; i < jsonArray.size(); i++) {			final Object item = jsonArray.get(i); 			if (item instanceof JSONArray) {				//对json数组进行类型转换				//json数组中只考虑list集合的情况,不考虑其他map类型的传入,且list中不考虑再包含list				final JSONArray jsonArr = JSONArray.fromObject(item);				parameObjs[i] = getParameArr(jsonArr);			} else if (item instanceof JSONObject) {				//对json对象进行类型转换				final JSONObject job = JSONObject.fromObject(item);				parameObjs[i] = getParameObj(job);			} else {				//其他类型为字符串、数值,这里都不进行转换				parameObjs[i] = item;			}		}						/**		 * 3.调用方法获取返回值.		 */		final String beanName = getBeanName(serviceName);		LOG.info("bean Name = " + beanName);		final Object objcect = ReflectionUtils.invokeMethod(targetMethod, ac.getBean(beanName), parameObjs);		Object result = null;		if (objcect instanceof JSONObject) {			result = JSONObject.fromObject(objcect);		} else if (objcect instanceof JSONArray) {			result = JSONArray.fromObject(objcect); 		} else {			result = objcect;		}		LOG.info("the result is"); 		LOG.info(result); 				return result;	} 			/**	 * 将JSONObject强制类型转换为对应的java对象.	 * @param job job	 * @return Object	 */	private static Object getParameObj(final JSONObject job) {		//如果是json对象,按约定需有jcls属性,否则这里可以抛个异常作为提示		final String jobClsName = job.get("jcls") + "";		if (StringUtil.isEmptyString(jobClsName)) { 			throw new RuntimeException("缺少jcls属性");		}		Class itemClass = null;		Object itemObj = null;		try {			itemClass = Class.forName(jobClsName); 		} catch (ClassNotFoundException e) {			LOG.error("没有找到类" + jobClsName);		} 		itemObj = JSONObject.toBean(job, itemClass);		//判断json对象是否持有动态属性		final List
dynamicAttrs = getDynamicAttrs(job, itemClass); if (dynamicAttrs.size() > 0) { //然后判断该类是否动态对象,有则进行设置属性 if ("com.alphalab.framework.domain.DynamicValueObject".equals(itemClass.getSuperclass().getName())) { //设置动态属性到对象中 setDynamicAttr(dynamicAttrs, job, itemObj, itemClass); } } return itemObj; } /** * 将JSONArray强制类型转换为对应的list集合. * @param jsonArr jsonArr * @return Object */ private static Object getParameArr(final JSONArray jsonArr) { Object itemObj = null; if (jsonArr.size() > 0) { //获得第一个集合元素,看是否为对象 final Object firstItemObj = jsonArr.get(0); if (firstItemObj instanceof JSONObject) { final JSONObject arrJob = JSONObject.fromObject(firstItemObj); final String jobClsName = arrJob.get("jcls") + ""; //如果为对象,则进行类型转换 if (StringUtil.isEmptyString(jobClsName) || "null".equals(jobClsName)) { throw new RuntimeException("缺少jcls属性,请检查参数数组"); } Class itemClass = null; try { itemClass = Class.forName(jobClsName); } catch (ClassNotFoundException e) { LOG.error("没有找到类" + jobClsName); } itemObj = JSONArray.toCollection(jsonArr, itemClass); } else if (firstItemObj instanceof String) { //如果为字符串 itemObj = JSONArray.toArray(jsonArr, String.class); } else if (firstItemObj instanceof Integer) { //如果为整数类型 itemObj = JSONArray.toArray(jsonArr, Integer.class); } else if (firstItemObj instanceof Double) { //如果为小数类型 itemObj = JSONArray.toArray(jsonArr, Double.class); } } else { itemObj = null; } return itemObj; } /** * 获取不在class中的JSONObject的属性名集合. * @param job JSONObject * @param itemClass Class * @return String[] */ private static List
getDynamicAttrs(final JSONObject job, final Class itemClass) { final List
dynamicAttrs = new ArrayList
(); //获取本类全部属性 String allClassFields = ""; final Field[] fields = itemClass.getDeclaredFields(); for (Field field : fields) { allClassFields += field.getName() + ","; } //获取前端全部属性 for (Iterator
iterator = job.keys(); iterator.hasNext();) { final String type = (String) iterator.next(); //获取不在类的属性 if (allClassFields.indexOf(type) == -1) { dynamicAttrs.add(type); } } return dynamicAttrs; } /** * 对目标对象设置动态属性. * @param dynamicAttrs 动态属性集合 * @param job 解析后的前端json对象 * @param targetObj 设置动态属性的目标object * @param itemClass 目标类的class */ private static void setDynamicAttr(final List
dynamicAttrs, final JSONObject job, final Object targetObj, final Class itemClass) { for (String filedName : dynamicAttrs) { //动态属性名 final String dynamicAttr = filedName; //动态属性值 final Object dynamicValue = job.get(filedName); //对object调用set方法 final Object[] args = new Object[2]; args[0] = dynamicAttr; args[1] = dynamicValue; //找到set方法 final Method[] methods = itemClass.getMethods(); Method targetMethod = null; for (Method method : methods) { if (method.getName().equals("set")) { targetMethod = method; } } try { //调用set方法 targetMethod.invoke(targetObj, args); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } /** * 根据服务名返回bean名. * @param serviceName serviceName * @return String */ private String getBeanName(final String serviceName) { String beanName = ""; final String[] items = serviceName.split("\\."); final String interfaceName = items[items.length - 1]; beanName = interfaceName.substring(1, interfaceName.length()); return toLowerCaseFirstOne(beanName); } /** * 首字母小写. * @param s s * @return String */ public static String toLowerCaseFirstOne(final String s) { if (Character.isLowerCase(s.charAt(0))) { return s; } else { return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString(); } } }

前端组件:

/** * moonlight Web开发 基础服务. * Copyright 2000-2020 ALPHA LAB.Inc * author:allen * date:2018-2-23 */define(["jquery"], function ($) {		//定义远程服务方法	var RemoteService = {};	RemoteService.doPost = function(serviceName, methodName, parameters){		var resultDatas = null;		var REMOTE_SERVLET = getContextPath()+"/remoteService/handleRequest";		//需将参数数组,即json对象转成json字符串		var paramData = {"serviceName": serviceName, "methodName":methodName, "parameters": JSON.stringify(parameters)};		$.ajax({   			url: REMOTE_SERVLET,			type:"POST",   			//设置为同步,这样返回的resultDatas就能被success回调函数赋值			async:false,			data:paramData,			//用以下配置可兼容post和get方法			contentType: "application/x-www-form-urlencoded;charset=UTF-8",			success: function (result) {				if(typeof result == "object" && !Array.isArray(result)){					//如果result是一个对象,判断其是否持有动态属性,有则加到新对象中					if(result.dynamicMap){						resultDatas = result;						for(var key in result.dynamicMap){							resultDatas[key]=result.dynamicMap[key];						}								}else{						resultDatas = result;					} 				}else if(Array.isArray(result)){					//如果result是一个集合,则进行判断是否持有动态属性,有则加到新结果集中					resultDatas = [];					$.each(result,function(index,item){ 						if(item.dynamicMap){							var itemData = item;							for(var key in item.dynamicMap){								itemData[key]=item.dynamicMap[key];							} 							resultDatas.push(itemData);						}else{							resultDatas.push(item);						}					});  				}else{					resultDatas = result;				} 			}, 			error: function() {				console.log('服务器端有错!');			}				});			return resultDatas; 	};			//获取项目根目录路径,即项目名	function getContextPath(){		var contextPath = document.location.pathname; 		var index =contextPath.substr(1).indexOf("/"); 		contextPath = contextPath.substr(0,index+1); 		delete index; 		return contextPath;   			}		var service = {		RemoteService:RemoteService		};	return service;	});

 

 

 

转载于:https://my.oschina.net/allenwe23/blog/1634879

你可能感兴趣的文章
Myeclipse中打开接口实现类的快捷键
查看>>
使用JdbcTemplate和JdbcDaoSupport
查看>>
Glibc 和 uClibc
查看>>
Mysql学习第三课-分析二进制日志进行增量备份和还原
查看>>
HDU 6073 - Matching In Multiplication | 2017 Multi-University Training Contest 4
查看>>
如何检测域名是否被微信屏蔽 微信域名检测接口API是如何实现
查看>>
POJ1611-The Suspects
查看>>
Linux下安装Python-3.3.2【转】
查看>>
LeetCode OJ:Merge Two Sorted Lists(合并两个链表)
查看>>
功能测试
查看>>
【BZOJ 1901】Dynamic Rankings
查看>>
Github-Client(ANDROID)开源之旅(二) ------ 浅析ActionBarSherkLock
查看>>
React-Native 之 GD (十六)首页筛选功能
查看>>
SSISDB5:使用TSQL脚本执行Package
查看>>
asp.net后台进程做定时任务
查看>>
java接口中多继承的问题
查看>>
索引笔记《二》确定需要建立索引的列
查看>>
libjpeg的问题
查看>>
嵌入式 详解udev
查看>>
云安全:这也是需要花大钱去建设的部分
查看>>