为了针对日益严峻的跨系统传输异常,决定开发po+mq合体的中间件。在po功能的扩展上,针对请求参数、返回参数进行转换扩展。传统项目针对新加入的接口进行抽象处理,再进行转换,在服务发布的时间差内,会造成请求失败,数据丢失。考虑到中间件的稳定性,为了解决这个问题,我决定加入动态编译,动态注入,卸载的功能。每个接口都可以配置一个实现的子类,来满足异构数据的处理工作。如果有业务场景要求不能重启服务,大家可以参考一下我的做法!!! 下面聊一下主要实现逻辑:
一、首先新建抽象工厂类HttpFactoryService,定义两个抽象方法,也加入了xml转对象,对象转xml的方法,还有http请求方法:
package com.asd.po.rest;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.Method;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.asd.po.rest.bean.HttpRequestBean;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ResponseEntity;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;
/**
* @author zhanqi
* @since 2021/8/9 18:13
*/
public abstract class HttpFactoryService {
public abstract HttpRequestBean req(HttpRequestBean httpRequestBean);
public abstract ResponseEntity<String> resp(ResponseEntity<String> obj);
/**
* xml转对象
*
* @param xml
* @return
* @throws JAXBException
*/
protected <T> T convertToObj(String xml,Class<T> clazz) throws JAXBException {
StringReader reader = new StringReader(xml);
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
T obj = (T)jaxbUnmarshaller.unmarshal(reader);
return obj;
}
/**
* 将对象转为流程XML
*
* @param obj
* @return
* @throws JAXBException
*/
protected String convertToXML(Object obj) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(obj.getClass());
StringWriter writer = new StringWriter();
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.marshal(obj, writer);
String xmlStr = writer.toString();
xmlStr = StringUtils.replace(xmlStr, """, "'");
return xmlStr;
}
protected <T> T request(String url, Map<String, String> headers , Map<String, Object> formMap, String bodyJson, Method method, Class<T> clazz) {
String result = HttpRequest.post(url)
.headerMap(headers, true)
.form(formMap)
.body(bodyJson)
.timeout(600000)//超时,毫秒
.method(method)
.execute().body();
return JSON.parseObject(result, clazz);
}
protected <T> T request(String url,String bodyJson, Method method, Class<T> clazz) {
String result = HttpRequest.post(url)
.body(bodyJson)
.timeout(600000)//超时,毫秒
.method(method)
.execute().body();
return JSON.parseObject(result, clazz);
}
protected JSONObject request(String url, String bodyJson, Method method) {
String result = HttpRequest.post(url)
.body(bodyJson)
.timeout(600000)//超时,毫秒
.method(method)
.execute().body();
return JSON.parseObject(result);
}
}
二、请求抽象方法的参数bean HttpRequestBean:
package com.asd.po.rest.bean;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
/**
* @author zhanqi
* @since 2021/11/19 23:53
*/
@Data
@Builder
public class HttpRequestBean {
private String requestBody ;
private Map<String, String> headersMap;
private Map<String, String> params;
//参考号
private String refNo;
//请求方法
private String method;
private String url;
}
构建调度服务DistanceSpringContext:
package com.asd.po.rest;
import com.asd.po.entity.InterfaceConfig;
import com.asd.po.rest.bean.HttpRequestBean;
import com.asd.po.util.ClassUtils;
import com.asd.po.util.CustomJavaCompiler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author zhanqi
* @since 2021/8/9 23:48
*/
@Slf4j
@Service
public class DistanceSpringContext {
private final Map<String, HttpFactoryService> map = new ConcurrentHashMap<>();
public void DistanceSpringContext(List<InterfaceConfig> list) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
for (Class<?> c : ClassUtils.getAllAssignedClass(HttpFactoryService.class)) {
map.put(ClassUtils.getBeanId(c), (HttpFactoryService) c.newInstance());
}
for (InterfaceConfig e : list) {
CustomJavaCompiler compiler = new CustomJavaCompiler(e.getJavaClassCode());
boolean res = compiler.compiler();
if (!res) {
continue;
}
log.info("compilerSuccess:{},compilerTime:{}", compiler.getCompilerMessage(), compiler.getCompilerTime());
Class<?> clz = compiler.getCompilerClass();
map.put(ClassUtils.getBeanId(clz), (HttpFactoryService) clz.newInstance());
}
}
public HttpRequestBean setReq(String serviceName, HttpRequestBean httpRequestBean) {
HttpFactoryService obj = map.get(serviceName);
return obj.req(httpRequestBean);
}
public ResponseEntity<String> setResp(String serviceName, ResponseEntity<String> respObj) {
HttpFactoryService obj = map.get(serviceName);
return obj.resp(respObj);
}
/**
* 通过BeanDefinition注册bean到spring context,无则加入,有则修改
*
* @param javaClassCode 类路径
*/
public void register(String javaClassCode) {
CustomJavaCompiler compiler = new CustomJavaCompiler(javaClassCode);
boolean res = compiler.compiler();
if (!res) {
return;
}
log.info("compilerSuccess:{},compilerTime:{}", compiler.getCompilerMessage(), compiler.getCompilerTime());
Class<?> clz = compiler.getCompilerClass();
try {
map.put(ClassUtils.getBeanId(clz), (HttpFactoryService) clz.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 动态注销bean
*
* @param beanId
*/
public void cancel(String beanId) {
map.remove(beanId);
}
}
三、下面是java代码动态编译器CustomJavaCompiler
package com.asd.po.util;
import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CustomJavaCompiler {
//源码
private String sourceCode;
//类全名
private String fullClassName;
//获取java的编译器
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//存放编译之后的字节码(key:类全名,value:编译之后输出的字节码)
private Map<String, ByteJavaFileObject> javaFileObjectMap = new ConcurrentHashMap<>();
//存放编译过程中输出的信息
private DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<>();
//编译耗时(单位ms)
private long compilerTime;
public CustomJavaCompiler(String sourceCode) {
this.sourceCode = sourceCode;
this.fullClassName = getFullClassName(sourceCode);
}
/**
* 编译字符串源代码,编译失败在 diagnosticsCollector 中获取提示信息
*
* @return true:编译成功 false:编译失败
*/
public boolean compiler() {
if(compiler == null)
return false;
long startTime = System.currentTimeMillis();
//标准的内容管理器,更换成自己的实现,覆盖部分方法
StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnosticsCollector, null, null);
JavaFileManager javaFileManager = new StringJavaFileManage(standardFileManager);
//构造源代码对象
JavaFileObject javaFileObject = new StringJavaFileObject(fullClassName, sourceCode);
//获取一个编译任务
JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, diagnosticsCollector, null, null, Arrays.asList(javaFileObject));
//设置编译耗时
compilerTime = System.currentTimeMillis() - startTime;
return task.call();
}
/**
* 获取编译后的Class
* @return
*/
public Class<?> getCompilerClass() {
StringClassLoader scl = new StringClassLoader();
Class<?> clz = null;
try {
clz = scl.findClass(fullClassName);
} catch (Exception e) {
e.printStackTrace();
}
return clz;
}
/**
* 获取编译时产生的信息
* @return 编译信息(错误 警告)
*/
public String getCompilerMessage() {
if(compiler == null)
return "JRE环境未配置(请复制JDK路径下lib目录内的tools.jar到JRE路径下lib目录里)";
StringBuilder sb = new StringBuilder();
List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticsCollector.getDiagnostics();
for (Diagnostic diagnostic : diagnostics) {
sb.append(diagnostic.toString()).append("\r\n");
}
return sb.toString();
}
public long getCompilerTime() {
return compilerTime;
}
/**
* 获取类的全名称
* @param sourceCode 源码
* @return 类的全名称
*/
public static String getFullClassName(String sourceCode) {
String className = "";
Pattern pattern = Pattern.compile("package\\s+\\S+\\s*;");
Matcher matcher = pattern.matcher(sourceCode);
if (matcher.find()) {
className = matcher.group().replaceFirst("package", "").replace(";", "").trim() + ".";
}
pattern = Pattern.compile("class(\\s.*?\\s)[extends|\\{]");
matcher = pattern.matcher(sourceCode);
if (matcher.find()) {
className += matcher.group(1).replace("extends","").replace("HttpFactoryService","").trim();
}
return className;
}
/**
* 自定义一个字符串的源码对象
*/
private class StringJavaFileObject extends SimpleJavaFileObject {
//等待编译的源码字段
private String contents;
//java源代码 => StringJavaFileObject对象 的时候使用
public StringJavaFileObject(String className, String contents) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.contents = contents;
}
//字符串源码会调用该方法
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return contents;
}
}
/**
* 自定义一个编译之后的字节码对象
*/
private class ByteJavaFileObject extends SimpleJavaFileObject {
//存放编译后的字节码
private ByteArrayOutputStream outPutStream;
public ByteJavaFileObject(String className, Kind kind) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), kind);
}
@Override
public OutputStream openOutputStream() {
outPutStream = new ByteArrayOutputStream();
return outPutStream;
}
//在类加载器加载的时候需要用到
public byte[] getCompiledBytes() {
return outPutStream.toByteArray();
}
}
/**
* 自定义一个JavaFileManage来控制编译之后字节码的输出位置
*/
private class StringJavaFileManage extends ForwardingJavaFileManager {
StringJavaFileManage(JavaFileManager fileManager) {
super(fileManager);
}
//获取输出的文件对象,它表示给定位置处指定类型的指定类
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
ByteJavaFileObject javaFileObject = new ByteJavaFileObject(className, kind);
javaFileObjectMap.put(className, javaFileObject);
return javaFileObject;
}
}
/**
* 自定义类加载器, 用来加载动态的字节码
*/
private class StringClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
ByteJavaFileObject fileObject = javaFileObjectMap.get(name);
if (fileObject != null) {
byte[] bytes = fileObject.getCompiledBytes();
return defineClass(name, bytes, 0, bytes.length);
}
try {
return ClassLoader.getSystemClassLoader().loadClass(name);
} catch (Exception e) {
return super.findClass(name);
}
}
}
public static <T> T invokeMethod(Object object, String methodName, Class<?>[] classes, Object... args)
throws Exception {
Method method = object.getClass().getMethod(methodName, classes);
return (T) method.invoke(object, args);
}
}
四、抽象方法的子类TestHttp,这个可以存在数据库里面,通过订阅发布,动态新增变更各个节点的服务,动态编译、注入、卸载。
package com.asd.po.rest.extend;
import com.asd.po.rest.HttpFactoryService;
import com.asd.po.rest.bean.HttpRequestBean;
import org.springframework.http.ResponseEntity;
/**
* @author zhanqi
* @since 2021/11/20 0:00
*/
public class TestHttp extends HttpFactoryService {
@Override
public HttpRequestBean req(HttpRequestBean httpRequestBean) {
httpRequestBean.setUrl("http://127.0.0.1:8080/publish/custom");
httpRequestBean.setMethod("POST");
return httpRequestBean;
}
@Override
public ResponseEntity<String> resp(ResponseEntity<String> obj) {
return obj;
}
}
五、下面看一下数据里面的配置数据,TestHttp继承的子类逻辑可以动态修改新增,不需要重启服务,服务重启后会自动读取配置数据,自动编译注入实现类
版权声明:本文为zhanqi19900308原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。