天马行空:
java开发项目时,调试比较麻烦。每次改一点东西就要重启,每次重启都要等半天。然而并不是每个业务都很明确,每个接口都很清晰。无奈只能一边在测试环境运行一边改代码然后打包发布。这样效率非常低。自己在本地写了一个项目用来mock dubbo的接口。但是每次改代码还是避免不了要重启。
希望用js的动态性解决这个问题。
思路:aop+js
mock所有需要的接口,全部是空实现。然后通过aop,将真正的实现调用js函数,将js的返回值转换后返回给rpc的consummer。
maven依赖
junit
junit
4.12
test
com.alibaba
fastjson
1.2.47
org.apache.httpcomponents
httpclient
4.5.6
org.springframework
spring-core
4.3.6.RELEASE
MyAspect.java
实现aop功能,利用环绕通知,在调用服务之前,读取相应的js代码并执行,然后将结果字符串反序列化为java对象并返回。
import java.util.Collection;
import javax.annotation.PostConstruct;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import utils.Js;
@Aspect
@Component
public class MyAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PostConstruct
public void init() {
logger.info("void MyAspect.init()");
}
/**
* 环绕通知
*
* @param joinPoint
* 可用于执行切点的类
* @return
* @throws Throwable
*/
@Around("execution(* com..impl..*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Signature signature = joinPoint.getSignature();
MethodSignature ms = (MethodSignature)signature;
logger.info("方法"+signature.toString());
Object[] args = joinPoint.getArgs();
String arg = null;
//String[] paramNames = ms.getParameterNames();
if(args!=null && args.length>0){
arg = JSONArray.toJSONString(args);
logger.info("参数"+ arg);
}
//String typename = signature.getDeclaringTypeName();//类全名
String funcName = signature.getName();//方法名
String clazzname = joinPoint.getTarget().getClass().getSimpleName();//简单类名
//System.out.println(clazzname);
try{
String res = Js.runFile("classpath:js/"+clazzname+".js", funcName, arg);
Class> retType = ms.getReturnType();
if(res==null){
return res;
}
if(retType.isArray()){
return JSONArray.parseArray(res,retType.getComponentType());
}else if(Collection.class.isAssignableFrom(retType)){
Type[] pts = AspectHelper.getActualReturnTypeArguments(joinPoint);
if(pts==null){
throw new RuntimeException("返回值类型的泛型类型未指定");
}
if(pts[0] instanceof WildcardType){
throw new RuntimeException("返回值类型的泛型类型为通配符");
}
if(pts[0] instanceof Class){
return JSONArray.parseArray(res,(Class) pts[0]);
}
throw new RuntimeException("wtf");
}else{
return JSONObject.parseObject(res, retType);
}
}catch(Exception e){
logger.error(e.getMessage());
Object ret = joinPoint.proceed();
if(ret!=null){
if(ret.getClass().isArray() || ret instanceof Collection){
logger.info("结果"+JSONArray.toJSONString(ret));
}else{
logger.info("结果"+JSONObject.toJSONString(ret));
}
}
return ret;
}
}
}
AspectHelper.java
用于获取返回值类型。包括部分有泛型类型的场景。这里并不能处理所有情况,比如返回List不指定泛型类型,或者List这样的指定具体类型没什么意义,或者泛型类型是接口等。当然这也是没有办法的事情,只能建议返回值类型不要太个性,要方便json字符串反序列化为java对象。
public class AspectHelper {
public static Type[] getActualReturnTypeArguments(ProceedingJoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
MethodSignature ms = (MethodSignature)signature;
Method method = ms.getMethod();
Type t = method.getGenericReturnType();
if(t instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType) t;
return pt.getActualTypeArguments();
}
return null;
}
public static Type[] getActualReturnTypeArguments(Method method){
Type t = method.getGenericReturnType();
if(t instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType) t;
return pt.getActualTypeArguments();
}
return null;
}
public List aaa(){
return null;
}
@Test
public void test() throws NoSuchMethodException, SecurityException, ClassNotFoundException{
Method method = AspectHelper.class.getMethod("aaa");
Type[] res = getActualReturnTypeArguments(method);
System.out.println(res[0] instanceof WildcardType);
//Class> c = Class.forName(res[0].getTypeName());
System.out.println((Class)res[0]);
}
}
Js.java
与java对象的方法对应关系:
java方法的入参,会作为数组传递给js函数。
如果java方法有0个入参,那么js函数的入参就是null。
js函数的返回值,会被序列化为json字符串,然后再被反序列化为java对象。
package utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import org.springframework.util.ResourceUtils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
public class Js {
private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
private static Invocable invocable = (Invocable) engine;
/**
* 测试java传参到js文件,并且js将返回结果返回给java。
*
* @throws FileNotFoundException
*/
//@Test
public void testArg() throws FileNotFoundException {
JSONObject json = new JSONObject();
json.put("name", "avril lavigne");
json.put("age", 110);
json.put("like", 3.14);
String arg = json.toJSONString();
/**
* 使用spring的ResourceUtils,方便读取文件
*/
File file = ResourceUtils.getFile("classpath:js/testArg.js");
String res = run(file,"main", arg);
System.out.println(res);
}
/**
* 将给定文件的内容作为js代码执行,并且将字符串作为入参和出参。
* @param file
* @param arg
* @return
*/
public static String run(File file,String funcName, String arg) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append(System.lineSeparator());
}
return run(sb.toString(),funcName, arg);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 将给定名称的文件的内容作为js代码执行,并且将字符串作为入参和出参。
* @param file
* @param arg
* @return
*/
public static String runFile(String file,String funcName, String arg) {
File f;
try {
f = ResourceUtils.getFile(file);
return run(f,funcName, arg);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 执行给定的js代码,并且将字符串作为入参和出参。
* 字符串是最通用的方式,所以设计成入参和出参都是字符串。
* @param js
* @param arg
* @return
*/
public static String run(String js,String funcName, String arg) {
Object res;
try {
Object param = null;
if (arg != null) {
try{
param = JSONObject.parseObject(arg);
}catch(Exception e){
param = JSONArray.parseArray(arg);
}
}
engine.eval(js);
res = invocable.invokeFunction(funcName, param);
if (res == null) {
return null;
}
return res.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
src/main/recources/js/com.zhou.impl.MockMyService.js
/**
* @param arg java调用时传进来的对象
* @return JSON字符串
* */
function queryUser(arg){
print('abc');
print(arg.name);
var res = {
a:'A',
b:'B',
c:[1,2,3]
};
return JSON.stringify(res);
}
function f(){
}