对于常见的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 ListdynamicAttrs = 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; });