背景
最近我司在做一个低代码平台的项目,大概就是拖拖拽拽会生成一个表单,表单可能映射到一个数据表,对表单的数据的维护数据会入到这张表里面,但是一般简单的CURD操作可能这种需求就可以满足,但是复杂的,比如这张表的数据入表了我还需要操作其他的表,或者我可能要发一个通知消息或者之类的更复杂的业务,这种后置的业务一般都是复杂且不通用的,我们就希望能够对每个不同的业务表进行这种增强处理,而且线上的话可能存在几百台服务器,我们也不想进行服务的重启就想实现这种功能。然后插件的jar包要能使用业务系统里面的ioc容器内的对象,比如feignclient,resttemplate,jdbctemplate等等。如果业务系统内有插件内要使用的类加载业务系统的,否则加载插件内的(不要打破双亲委派)。
实现
我们设计了一个插件管理的模块,然后针对表单的curd的操作每个操作又大致分为before,after,afercommit等一些操作位置,在这些位置动态调用插件里面的class类。整个的系统交互流程


主要实现代码
- 加载外部jar
public static PluginClassLoader loadJar(String jarPath, String[] classNames, ConfigurableApplicationContext applicationContext) {
try {
PluginClassLoader pluginClassLoader = new PluginClassLoader(jarPath);
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
for (String className : classNames) {
// 加载类
Class obj = pluginClassLoader.loadClass(className);
String beanName = getBeanNameByClassName(className);
if (defaultListableBeanFactory.containsBean(beanName)) {
log.info("removeBeanDefinition: = {} ", beanName);
defaultListableBeanFactory.removeBeanDefinition(beanName);
}
// 注册进入ioc
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(obj);
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
beanDefinition.setScope("singleton");
defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinition);
}
return pluginClassLoader;
} catch (Exception e) {
log.error("loadJar fail", e);
}
return null;
}
- 调用执行外部jar
/**
* 从 ioc获取对象 执行增强的逻辑
*
* @throws Exception
*/
@Override
public Object invokeJar(String fileName, String classPath, String method, Class c, Object args) throws Exception {
InputStream in = null;
OutputStream out = null;
try {
String localName = fileProperties.getUploadDir() + File.separator + fileName;
File files = new File(localName);
if (!files.exists()) {
// 判断本地是否有 下载拉取包
files.createNewFile();
out = new FileOutputStream(files);
// 保存到本地
in = myMinioFileOperator.getFileBytes(fileName);
IoUtil.copy(in, out);
in.close();
out.close();
files = new File(localName);
// 针对启动时更新上传的包
PluginUtils.loadJar(localName, new String[]{classPath}, applicationContext);
}
// 执行
String name = PluginUtils.getBeanNameByClassName(classPath);
// 针对刚启动装载
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
if (!defaultListableBeanFactory.containsBean(name)) {
PluginUtils.loadJar(localName, new String[]{classPath}, applicationContext);
}
Object config = applicationContext.getBean(name);
if (EmptyUtils.isEmpty(args)) {
return config.getClass().getDeclaredMethod(method).invoke(config);
} else {
return config.getClass().getDeclaredMethod(method, c).invoke(config, args);
}
} finally {
IoUtil.close(in);
IoUtil.close(out);
}
}
- 类加载器定义
@Slf4j
public class PluginClassLoader extends SecureClassLoader {
private String jarFilePath;
public PluginClassLoader(String jarFilePath) {
this.jarFilePath = jarFilePath;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 从jar 包里面查找类
// log.info("findClass = {} classLoader = {}", name, name.getClass().getClassLoader());
ByteArrayOutputStream byteArrayOutputStream = null;
InputStream inputStream = null;
JarFile jarFile = null;
try {
String path = name.replace('.', '/').concat(".class");
String pluginUrl = "jar:file:" + jarFilePath + "!/" + path; // be careful
URL url = new URL(pluginUrl);
// InputStream inputStream = url.openStream();
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
jarFile = jarURLConnection.getJarFile();
JarEntry jarEntry = jarURLConnection.getJarEntry();
inputStream = jarFile.getInputStream(jarEntry);
byteArrayOutputStream = new ByteArrayOutputStream();
int b;
while ((b = inputStream.read()) != -1) {
byteArrayOutputStream.write(b);
}
byte[] data = byteArrayOutputStream.toByteArray();
Class cs = this.defineClass(name, data, 0, data.length);
log.info("findClass = {} classLoader = {}", name, cs.getClassLoader());
return cs;
} catch (Exception e) {
log.error("findClass fail", e);
} finally {
IoUtil.close(byteArrayOutputStream);
IoUtil.close(inputStream);
IoUtil.close(jarFile);
}
return null;
}
// 如果本地和包中有相同的类,以jar中为准的话需要重写loadClass方法,指定加载顺序不走双亲委派机制
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// log.info("loadClass = {}", name);
return super.loadClass(name, resolve);
}
}
网友评论