diff --git a/fox-mock-agent/pom.xml b/fox-mock-agent/pom.xml index 84aeae8..2a05816 100644 --- a/fox-mock-agent/pom.xml +++ b/fox-mock-agent/pom.xml @@ -50,6 +50,11 @@ provided true + + + ognl + ognl + @@ -100,6 +105,10 @@ ch.qos.logback com.alibaba.arthas.deps.ch.qos.logback + + ognl + com.cxytiandi.foxmock.agent.ognl + @@ -109,6 +118,7 @@ ch.qos.logback:logback-classic ch.qos.logback:logback-core com.alibaba.arthas:arthas-repackage-logger + ognl:ognl diff --git a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/Express.java b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/Express.java new file mode 100644 index 0000000..783f4d9 --- /dev/null +++ b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/Express.java @@ -0,0 +1,48 @@ +package com.cxytiandi.foxmock.agent.express; + +public interface Express { + + /** + * 根据表达式获取值 + * + * @param express 表达式 + * @return 表达式运算后的值 + * @throws OgnlExpressException 表达式运算出错 + */ + Object get(String express) throws OgnlExpressException; + + /** + * 根据表达式判断是与否 + * + * @param express 表达式 + * @return 表达式运算后的布尔值 + * @throws OgnlExpressException 表达式运算出错 + */ + boolean is(String express) throws OgnlExpressException; + + /** + * 绑定对象 + * + * @param object 待绑定对象 + * @return this + */ + Express bind(Object object); + + /** + * 绑定变量 + * + * @param name 变量名 + * @param value 变量值 + * @return this + */ + Express bind(String name, Object value); + + /** + * 重置整个表达式 + * + * @return this + */ + Express reset(); + + +} diff --git a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/ExpressFactory.java b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/ExpressFactory.java new file mode 100644 index 0000000..3ff61b2 --- /dev/null +++ b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/ExpressFactory.java @@ -0,0 +1,16 @@ +package com.cxytiandi.foxmock.agent.express; + +public class ExpressFactory { + + private static final ThreadLocal EXPRESS_THREAD_LOCAL = new ThreadLocal() { + @Override + protected Express initialValue() { + return new OgnlExpress(); + } + }; + + public static Express getExpress(Object object) { + return EXPRESS_THREAD_LOCAL.get().reset().bind(object); + } + +} \ No newline at end of file diff --git a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/OgnlExpress.java b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/OgnlExpress.java new file mode 100644 index 0000000..95a19c0 --- /dev/null +++ b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/OgnlExpress.java @@ -0,0 +1,56 @@ +package com.cxytiandi.foxmock.agent.express; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import ognl.*; + +public class OgnlExpress implements Express { + + private static final MemberAccess MEMBER_ACCESS = new DefaultMemberAccess(true); + private static final Logger logger = LoggerFactory.getLogger(OgnlExpress.class); + private static final ObjectPropertyAccessor OBJECT_PROPERTY_ACCESSOR = new ObjectPropertyAccessor(); + + private Object bindObject; + private final OgnlContext context; + + public OgnlExpress() { + OgnlRuntime.setPropertyAccessor(Object.class, OBJECT_PROPERTY_ACCESSOR); + context = new OgnlContext(); + context.setMemberAccess(MEMBER_ACCESS); + } + + @Override + public Object get(String express) throws OgnlExpressException { + try { + return Ognl.getValue(express, context, bindObject); + } catch (Exception e) { + logger.error("Error during evaluating the expression:", e); + throw new OgnlExpressException(express, e); + } + } + + @Override + public boolean is(String express) throws OgnlExpressException { + final Object ret = get(express); + return ret instanceof Boolean && (Boolean) ret; + } + + @Override + public Express bind(Object object) { + this.bindObject = object; + return this; + } + + @Override + public Express bind(String name, Object value) { + context.put(name, value); + return this; + } + + @Override + public Express reset() { + context.clear(); + context.setMemberAccess(MEMBER_ACCESS); + return this; + } +} diff --git a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/OgnlExpressException.java b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/OgnlExpressException.java new file mode 100644 index 0000000..b9d541b --- /dev/null +++ b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/express/OgnlExpressException.java @@ -0,0 +1,15 @@ +package com.cxytiandi.foxmock.agent.express; + +public class OgnlExpressException extends Exception { + + private final String express; + + public OgnlExpressException(String express, Throwable cause) { + super(cause); + this.express = express; + } + + public String getExpress() { + return express; + } +} diff --git a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/model/FoxMockAgentArgs.java b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/model/FoxMockAgentArgs.java index ee4cf72..5432ffb 100644 --- a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/model/FoxMockAgentArgs.java +++ b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/model/FoxMockAgentArgs.java @@ -30,7 +30,7 @@ public class FoxMockAgentArgs { * mock方法白名单,如果文件夹中有多个方法会被全部mock,如果指定了此配置将只会mock这里指定的方法 * 格式: com.xx.xxService#getName|com.xx.xxService#getAge */ - private List mockMethodWhiteList; + private List mockMethodWhiteList = new ArrayList<>(); /** * mock数据通过http请求获取 diff --git a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/model/MockInfo.java b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/model/MockInfo.java new file mode 100644 index 0000000..108f9da --- /dev/null +++ b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/model/MockInfo.java @@ -0,0 +1,40 @@ +package com.cxytiandi.foxmock.agent.model; + +/** + * Mock信息 + * + * @作者 尹吉欢 + * @个人微信 jihuan900 + * @微信公众号 猿天地 + * @GitHub https://github.com/yinjihuan + * @作者介绍 http://cxytiandi.com/about + * @时间 2022-05-11 21:47 + */ +public class MockInfo { + + /** + * Mock 数据 + */ + private String f_mock_data; + + /** + * ognl匹配表达式 + */ + private String f_ognl_express; + + public void setMockData(String mockData) { + this.f_mock_data = mockData; + } + + public String getMockData() { + return f_mock_data; + } + + public void setOgnlExpress(String ognlExpress) { + this.f_ognl_express = ognlExpress; + } + + public String getOgnlExpress() { + return f_ognl_express; + } +} diff --git a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/transformer/MethodInvokeFilter.java b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/transformer/MethodInvokeFilter.java new file mode 100644 index 0000000..77d2e05 --- /dev/null +++ b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/transformer/MethodInvokeFilter.java @@ -0,0 +1,43 @@ +package com.cxytiandi.foxmock.agent.transformer; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.cxytiandi.foxmock.agent.express.Express; +import com.cxytiandi.foxmock.agent.express.OgnlExpressException; +import com.cxytiandi.foxmock.agent.express.ExpressFactory; +import com.cxytiandi.foxmock.agent.utils.StringUtils; + +/** + * 方法执行过滤 + *

采用ognl进行mock方法的匹配,如果只想对指定参数进行mock就配置ognl表达式

+ * + * @作者 尹吉欢 + * @个人微信 jihuan900 + * @微信公众号 猿天地 + * @GitHub https://github.com/yinjihuan + * @作者介绍 http://cxytiandi.com/about + * @时间 2022-05-10 21:54 + */ +public class MethodInvokeFilter { + + private static final Logger LOG = LoggerFactory.getLogger(MockClassFileTransformer.class); + + public static boolean filter(Object[] args, String express) { + if (args.length == 0 || StringUtils.isBlank(express)) { + return true; + } + + Express ex = ExpressFactory.getExpress(args); + for (int i = 0; i < args.length; i++) { + ex.bind("p" + i, args[i]); + } + + try { + return ex.is(express); + } catch (OgnlExpressException e) { + LOG.error("ognl filter exception", e); + } + + return false; + } +} diff --git a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/transformer/MockClassFileTransformer.java b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/transformer/MockClassFileTransformer.java index 71977ff..72d760c 100644 --- a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/transformer/MockClassFileTransformer.java +++ b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/transformer/MockClassFileTransformer.java @@ -3,9 +3,11 @@ import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.cxytiandi.foxmock.agent.model.ClassInfo; +import com.cxytiandi.foxmock.agent.model.MockInfo; import com.cxytiandi.foxmock.agent.storage.StorageHelper; +import com.cxytiandi.foxmock.agent.utils.JsonUtils; import com.cxytiandi.foxmock.agent.utils.StringUtils; -import com.google.gson.Gson; +import javassist.CannotCompileException; import javassist.CtClass; import javassist.CtMethod; @@ -59,17 +61,15 @@ public byte[] transform(ClassLoader loader, String className, Class classBein String data = StorageHelper.get(key); if (Objects.nonNull(data)) { match = true; + MockInfo mockInfo = null; LOG.info(String.format("mock methods %s, mock data is %s", key, data)); - if (data.startsWith("throw new")) { - String mockCode = "if(true){%s}"; - method.insertBefore(String.format(mockCode, data)); + if (data.contains("f_mock_data") || data.contains("f_ognl_express")) { + mockInfo = JsonUtils.fromJson(data, MockInfo.class); } else { - String mockCode = "if(true){" + - "return ($r)com.cxytiandi.foxmock.agent.utils.JsonUtils.parse(%s,%s,%s);" + - "}"; - method.insertBefore(String.format(mockCode, new Gson().toJson(data), "\""+className+"\"", "\""+methodName+"\"")); + mockInfo = new MockInfo(); + mockInfo.setMockData(data); } - + updateMethod(mockInfo, method, className, methodName); } } @@ -85,4 +85,16 @@ public byte[] transform(ClassLoader loader, String className, Class classBein throw new RuntimeException("transform error " + className, e); } } + + private void updateMethod(MockInfo mockInfo, CtMethod method, String className, String methodName) throws CannotCompileException { + if (mockInfo.getMockData().startsWith("throw new")) { + String mockCode = "if(com.cxytiandi.foxmock.agent.transformer.MethodInvokeFilter.filter($args,%s)){%s}"; + method.insertBefore(String.format(mockCode, JsonUtils.toJson(mockInfo.getOgnlExpress()), mockInfo.getMockData())); + } else { + String mockCode = "if(com.cxytiandi.foxmock.agent.transformer.MethodInvokeFilter.filter($args,%s)){" + + "return ($r)com.cxytiandi.foxmock.agent.utils.JsonUtils.parse(%s,%s,%s);" + + "}"; + method.insertBefore(String.format(mockCode, JsonUtils.toJson(mockInfo.getOgnlExpress()), JsonUtils.toJson(mockInfo.getMockData()), "\""+className+"\"", "\""+methodName+"\"")); + } + } } diff --git a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/utils/JsonUtils.java b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/utils/JsonUtils.java index 98d9df6..aec96ae 100644 --- a/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/utils/JsonUtils.java +++ b/fox-mock-agent/src/main/java/com/cxytiandi/foxmock/agent/utils/JsonUtils.java @@ -16,6 +16,10 @@ */ public class JsonUtils { + private static Gson gson = new Gson(); + + private JsonUtils() {} + public static Object parse(String data, String className, String methodName) { try { Type genericReturnType = null; @@ -28,11 +32,18 @@ public static Object parse(String data, String className, String methodName) { } } - Gson gson = new Gson(); Object value = gson.fromJson(data, genericReturnType); return value; } catch (Exception e) { throw new RuntimeException(e); } } + + public static String toJson(Object src) { + return gson.toJson(src); + } + + public static T fromJson(String json, Class classOfT) { + return gson.fromJson(json, classOfT); + } } diff --git a/fox-mock-example/pom.xml b/fox-mock-example/pom.xml index 2ca0d9d..8948f2f 100644 --- a/fox-mock-example/pom.xml +++ b/fox-mock-example/pom.xml @@ -32,12 +32,6 @@ org.springframework.boot spring-boot-starter-web - \ No newline at end of file diff --git a/fox-mock-example/src/main/java/com/cxytiandi/foxmock/example/FoxMockApp.java b/fox-mock-example/src/main/java/com/cxytiandi/foxmock/example/FoxMockApp.java index 1a255f3..1bfbc16 100644 --- a/fox-mock-example/src/main/java/com/cxytiandi/foxmock/example/FoxMockApp.java +++ b/fox-mock-example/src/main/java/com/cxytiandi/foxmock/example/FoxMockApp.java @@ -20,13 +20,15 @@ public static void main(String[] args) throws Exception { while (true) { UserService userService = new UserService(); - System.out.println(String.format("你好 %s", userService.getName2().getId())); - System.out.println(String.format("你好 %s", userService.getAge())); - System.out.println(String.format("你好 %s", userService.getAddrs())); - System.out.println(String.format("你好 %s", userService.getUsers())); - System.out.println(String.format("你好 %s", userService.getUsers2())); - System.out.println(String.format("你好 %s", userService.getUser())); - System.out.println(String.format("你好 %s", userService.getUserDetail())); + UserService.UserReq userReq = new UserService.UserReq(); + userReq.setId(2); + System.out.println(String.format("你好 getName2 %s", userService.getName2(userReq).getId())); + System.out.println(String.format("你好 getAge %s", userService.getAge())); + System.out.println(String.format("你好 getAddrs %s", userService.getAddrs())); + System.out.println(String.format("你好 getUsers %s", userService.getUsers())); + System.out.println(String.format("你好 getUsers2 %s", userService.getUsers2())); + System.out.println(String.format("你好 getUser %s", userService.getUser())); + System.out.println(String.format("你好 getUserDetail %s", userService.getUserDetail())); // 泛型测试 Result userDetailResult = userService.getUserDetail(); UserDetail userDetail = userDetailResult.getData(); @@ -37,7 +39,7 @@ public static void main(String[] args) throws Exception { }); } try { - userService.mockException(); + userService.mockException("yjh"); } catch (Exception e) { e.printStackTrace(); } diff --git a/fox-mock-example/src/main/java/com/cxytiandi/foxmock/example/UserService.java b/fox-mock-example/src/main/java/com/cxytiandi/foxmock/example/UserService.java index 2d6be96..44e94ad 100644 --- a/fox-mock-example/src/main/java/com/cxytiandi/foxmock/example/UserService.java +++ b/fox-mock-example/src/main/java/com/cxytiandi/foxmock/example/UserService.java @@ -12,7 +12,7 @@ * @时间 2022-04-20 00:25 */ public class UserService { - public UserInfo getName2() { + public UserInfo getName2(UserReq req) { return new UserInfo(); } @@ -44,7 +44,19 @@ public Result getUserDetail() { return result; } - public void mockException() { + public void mockException(String name) { } + + public static class UserReq { + private int id; + + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } } diff --git a/fox-mock-example/src/main/resources/mockdata/com.cxytiandi.foxmock.example.UserService#getName2 b/fox-mock-example/src/main/resources/mockdata/com.cxytiandi.foxmock.example.UserService#getName2 index 694b4f4..000f949 100644 --- a/fox-mock-example/src/main/resources/mockdata/com.cxytiandi.foxmock.example.UserService#getName2 +++ b/fox-mock-example/src/main/resources/mockdata/com.cxytiandi.foxmock.example.UserService#getName2 @@ -1 +1,4 @@ -{"id":1001} +{ + "f_mock_data": "{\"id\":1001}", + "f_ognl_express": "#p0.id.equals(2)" +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0c24776..43b9c62 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ 1.7.36 1.2.11 1.2.11 + 3.1.19 @@ -59,6 +60,11 @@ logback-core ${logback-core.version} + + ognl + ognl + ${ognl.version} + \ No newline at end of file