关于动态编译字节码技术参考:
https://blog.csdn.net/huxiang19851114/article/details/127881616
优化如下:
动态文本类改为界面配置及数据库保存

数据库表结构:
DROP TABLE IF EXISTS `compiler_info`;
CREATE TABLE `compiler_info` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` varchar(100) NOT NULL COMMENT '最后更新用户id',`class_name` varchar(100) NOT NULL COMMENT '类全路径名,如com.paratera.console.biz.model.User(唯一)',`info` text NOT NULL COMMENT '动态类内容',`description` varchar(200) COMMENT '动态类说明',`sign` varchar(100) COMMENT '签名标记',`old_sign` varchar(100) COMMENT '上一次签名标记',`create_at` datetime NULL ,`updated_at` datetime NULL ,PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `class_name`(`class_name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact COMMENT = '字节码动态编译配置表';
类加载对象引入缓存机制
通过compiler_info.sign签名标记(其实就是一个UUID)来判断,避免频繁调用生成大量的Object执行对象。
核心代码如下:
/*** 类加载对象缓存,key:compiler_info.sign*/
private static Map classMap = new HashMap<>();/*** Class执行对象缓存 key:compiler_info.sign*/
private static Map objMap = new HashMap<>();------------------------------------------------------------------------------
/*** 目标方法反射执行** @param className 全路径类名,与字节码文本配置的保持一致* @param methodName 执行方法名,与需要调度的字节码文本类方法名保持一致* @param args 方法参数,可以多个,根据字节码文本类具体情况来传* @return** @throws Exception
*/
public Object invoke(String className, String methodName, Object... args) throws Exception {//获取字符串代码内容Map compilerInfo = getCompilerInfo(className);String sign = (String) compilerInfo.get("sign");Class> clazz = null;Object obj = null;if(classMap.containsKey(sign)){clazz = classMap.get(sign);obj = objMap.get(sign);}else {try {//字节码编译处理,得到Class对象和执行对象clazz = this.compileToClass(className, (String) compilerInfo.get("info"));obj = clazz.getDeclaredConstructor().newInstance();classMap.put(sign,clazz);objMap.put(sign,obj);String oldSign = (String) compilerInfo.get("oldSign");classMap.remove(oldSign);objMap.remove(oldSign);} catch (Exception e) {throw new Exception("反射获取对象实例异常:" + e.getMessage());}}//反射调用目标方法Method[] test = clazz.getDeclaredMethods();List methods = Arrays.stream(test).filter(app ->StringUtils.equals(app.getName(), methodName)).toList();try {return methods.get(0).invoke(obj, args);} catch (Exception e) {throw new Exception("没有该动态编译运行方法或则参数不匹配");}
}
基于使用Spring MVC拦截器的方式弊端:
1、request body只能getReader()、getInputStream()一次,不重写preHandle后执行目标方法会报错
2、拦截器只能限定在项目内部使用,做成jar包插件无法实现拦截(已实验证明)
所以改成通过Filter过滤器来处理,Filter处理当然也有他的弊端,那就是任何请求都会进入doFilter方法,如果不做好排除判断,性能是比较低的
参考之前关于Mock模拟测试数据文章:
https://blog.csdn.net/huxiang19851114/article/details/127771679
我们做了如下优化:
模拟数据改为界面配置及数据库保存
主要增加了服务名称,用来进行服务隔离(避免大家的测试地址相同)

数据库表结构:
DROP TABLE IF EXISTS `mock_info`;
CREATE TABLE `mock_info` (`id` int(11) NOT NULL AUTO_INCREMENT,`on_off` varchar(6) NOT NULL COMMENT '开关,ON 开启,OFF 关闭',`server` varchar(50) NOT NULL COMMENT '服务名称,与引用服务配置的dmc.space相同',`method` varchar(20) NOT NULL COMMENT '方法类型 请使用全大写,如GET,POST,PUT等',`uri` varchar(255) NOT NULL COMMENT '访问路径',`profile` varchar(20) NOT NULL COMMENT 'Mock数据环境',`user_id` varchar(100) NOT NULL COMMENT '配置用户id',`create_at` datetime NULL ,`updated_at` datetime NULL ,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact COMMENT = 'mock模拟数据主表';DROP TABLE IF EXISTS `mock_detail`;
CREATE TABLE `mock_detail` (`id` int(11) NOT NULL AUTO_INCREMENT,`info_id` int(11) NOT NULL COMMENT 'mock_info表id',`header` varchar(500) COMMENT 'header传参,JSON结构字符串',`param` varchar(500) COMMENT 'param传参,JSON结构字符串',`body` varchar(500) COMMENT 'body传参,JSON结构字符串',`value` text COMMENT 'response返回,字符串,不限格式',`create_at` datetime NULL ,`updated_at` datetime NULL ,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact COMMENT = 'mock模拟数据入参返回明细表';
拦截器实现
实现逻辑大同小异,主要增加了dmc.space来作为开关,确定是否开启Mock模拟测试
import com.fasterxml.jackson.databind.ObjectMapper;
import com.paratera.dmc.plugin.facade.DmcClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils;
import org.springframework.web.filter.GenericFilterBean;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;/*** Mock模拟数据服务 过滤器*/
@ConditionalOnExpression("#{environment.getProperty('dmc.space') != null}")
@Configuration
@Slf4j
public class MockFilter extends GenericFilterBean implements Ordered {@Autowiredprivate DmcClient dmcClient;@Value("${spring.profiles.active}")private String profile;@Value("${dmc.space}")private String server;@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;String methodType = request.getMethod();String uri = request.getRequestURI();//如果发现是css、js、图片文件以及mock请求(避免dmcClient 请求进入死循环),直接放行if (uri.contains(".css") || uri.contains(".js") || uri.contains(".png")|| uri.contains(".jpg") || uri.indexOf("/s-api") != -1) {filterChain.doFilter(request, response);return;}//兼容dmc服务本身请求不带server参数server = StringUtils.isEmpty(server) ? "dmc" : server;ObjectMapper mapper = new ObjectMapper();//获取模拟数据配置信息Map mockInfo = dmcClient.getMockInfo(server, methodType, uri, profile);//如果没有录入该数据,则放过if (mockInfo == null) {filterChain.doFilter(request, response);return;}//判断开关是否开启,未开启,放过if (!StringUtils.equals("ON", (CharSequence) mockInfo.get("onOff"))) {filterChain.doFilter(request, response);return;}List 因为我们的数据中心在dmc-core,所以使用openfeign,增加一个对外访问dmc-core数据存储的接口
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;import java.util.Map;/*** dmc远程调用** @author huxiang*/@FeignClient(name = "dmcClient", url = "${dmc.mcs.url.dmc-core}")
public interface DmcClient {@GetMapping("/mock/s-api/info")public Map getMockInfo(@RequestParam("server") String server,@RequestParam("method") String method,@RequestParam("uri") String uri,@RequestParam("profile") String profile);@GetMapping("/compiler/s-api/info")public Map getCompilerInfo(@RequestParam("className") String className);
}
插件数据的维护中心,主要就是围绕上面的表进行增删改查,方便用户可视化操作,没什么可说的
维护数据平台一些公用并且固定的数据项,关于数据字典的实现可以参考:
https://blog.csdn.net/huxiang19851114/article/details/127556003
com.paratera.dmc dmc-plugin-java 0.0.1-SNAPSHOT
@SpringBootApplication(scanBasePackages = {"com.paratera.dmc"})
@EnableFeignClients(basePackages = {"com.paratera.dmc"})
根据自己服务名称配置,注意与界面数据维护中server保持一致(取数据字典值),如:
dmc.space=Console
dmc.mcs.url.dmc-core=http://localhost:25000
实现无需后台提供接口,前端通过配置化实现接口模拟数据,无阻塞开发。


测试,如果请求的主题和入参都匹配,返回结果,如:
@Test
public void wheAgentByUserId() throws Exception {String url = "/sys/wheAgentByUserId";MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(url).contentType(MediaType.APPLICATION_JSON).header("userid", "SELF-rQYRjc9mfQPP7F7apE8gGx6xyAAZYw6GNCTjS6wKPH8")).andReturn();int status = mvcResult.getResponse().getStatus();System.out.println("请求状态码:" + status);String result = mvcResult.getResponse().getContentAsString();System.out.println("接口返回结果:" + result);
}

如果匹配不到配置项,则继续进入项目访问目标方法,目标方法不存在,则报404
@Autowired
private DynamicCompiler dynamicCompiler;@Test
public void getQuota1() throws Exception {Object obj = dynamicCompiler.invoke("com.paratera.dmc.core.service.clustercoop.CLusterService","getQuota", "sc001");System.out.println(obj);}


上一篇:数据库索引分裂 问题分析