使用网络classloader 实现业务功能动态修改加载
日常中我们很多时候是需要进行class的动态加载的而且不希望影响业务,对于java 应用我们
可以开发自己的类加载器可以方便的解决,使用网络类加载器就更加灵活了,可以更好的控制
类的版本以及权限控制,而且灵活性很高(类似rpc,但是运行时还是单体的,rpc 的jvm运行时是跨
主机的)
参考图
原理说明
还是基于契约的开发模式,包含了一个通用的common-contract 的模块(可以基于抽象以及接口定义)
cust jars 模块主要是基于契约开发的功能模块,开发完成之后基于ci/cd 直接部署到s3中(可以支持多版本
以及访问安全),对于业务入口可以基于网络类加载器加载s3 的jars,然后就可以动态的进行class 的运行加载了
一个参考实例
- 项目结构
- 契约模块
就是基于接口定义的
package com.dalong;
public interface LoginService {
String printName();
String printNameVersion(Integer version);
}
- 契约实现
注意maven 模块需要引入契约定义
package com.dalong;
public class MyApp implements LoginService{
@Override
public String printName(){
return MyUtils.randomUserName();
}
@Override
public String printNameVersion(Integer version) {
return MyUtils.randomUserNameVersion(version);
}
}
- 应用入口
一个简单spring boot web 项目,服务动态加载部分
package com.dalong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.xeustechnologies.jcl.JarClassLoader;
import org.xeustechnologies.jcl.JclObjectFactory;
import org.xeustechnologies.jcl.ProxyClassLoader;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
@Component
public class LoginServiceA {
private JarClassLoader jcl = new JarClassLoader();
private Logger logger = LoggerFactory.getLogger(LoginServiceA.class);
public String printName() throws MalformedURLException {
logger.info("load class");
// 每次都加载,当然我们可以自己cache,这样就不用每次都下载了,加载了
JarClassLoader jcl = new JarClassLoader();
//Loading classes from different sources
jcl.add(new URL("http://localhost:8080/userlogin-1-SNAPSHOT.jar"));
JclObjectFactory factory = JclObjectFactory.getInstance();
//Create object of loaded class
logger.info("load class from {}","http://localhost:8080/userlogin-1-SNAPSHOT.jar");
LoginService obj = (LoginService)factory.create(jcl, "com.dalong.MyApp");
return obj.printName();
}
public String printNameVersion(Integer version) throws MalformedURLException {
//Loading classes from different sources
JarClassLoader jcl = new JarClassLoader();
// 每次都加载,当然我们可以自己cache,这样就不用每次都下载了,加载了,此处模拟了多版本的能力
String requestURL = String.format("http://localhost:8080/userlogin-%d-SNAPSHOT.jar",version);
jcl.add(new URL(requestURL));
logger.info("load class from {}",requestURL);
JclObjectFactory factory = JclObjectFactory.getInstance();
//Create object of loaded class
LoginService obj = (LoginService)factory.create(jcl, "com.dalong.MyApp");
return obj.printNameVersion(version);
}
}
运行效果
对于契约实现为了简单可以直接暴露到一个静态服务器中就行
说明
对于开发来说,基于抽象类比接口更加好,因为接口都是需要实现的,抽象类就不一样了,好处是我们可以保证业务代码的二进制兼容,不然每次反而维护很复杂
以上是一个简单的示例,可以扩展下,实现比较强大的业务规则处理
参考资料
https://github.com/sofastack/sofa-ark
https://github.com/kamranzafar/JCL
https://github.com/rongfengliang/jcl-classloader-learning