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