对于常见的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<String> 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<String> getDynamicAttrs(final JSONObject job, final Class itemClass) {
		final List<String> dynamicAttrs = new ArrayList<String>();
		//获取本类全部属性
		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<String> 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;
	
});