现在开发的项目是一个BI项目,并且项目的报表开发逻辑修改比较多,报表的取数逻辑是固化在代码中,所以需要通过热部署的方式将代码发布到线上新建报表或者改逻辑不想重新发布,发布代码重新上线的流程太长,不能很快响应业务需求 2.实现步骤
流程如下:
从接口接收到jar包文件,扫描jar包文件里面的class文件资源自定义class类加载器MyClassLoader,将class文件进行加载,得出Class对象通过Class对象,调用DefaultListableBeanFactory的api将对象的bean对象重新初始化 2.1实现自定义类加载器
MyClassLoader类存在两个方法
loadClass(String name,InputStream is),用于给其他类调用使用,通过class文件的InputStream,调用defineClass方法返回Class对象loadClass(String name),该类的作用是用于该自定义类加载器加载类过程中,处理目前正在加载的类引用到其他外部类的加载过程,该场景如下,我需要通过loadClass(String name,InputStream is)方法加载B类,在该类的加载过程中,需要用到类A(比如B继承了A),这时候也会对应的加载类A,如果不重写会找不到类A
源代码如下
public class MyClassLoader extends ClassLoader {
public MyClassLoader(){
}
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
log.info("loadClass(String name),name={}",name);
return MyClassLoader.class.getClassLoader().loadClass(name);
}
public Class> loadClass(String name,InputStream is) throws ClassNotFoundException {
try {
log.info("loadClass(String name,InputStream is),name={}",name);
byte[] b = new byte[is.available()];
is.read(b);
Class> aClass = defineClass(name, b, 0, b.length);
return aClass;
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
}
2.2通过DefaultListableBeanFactory初始化bean对象
通过defaultListableBeanFactory工厂的removeBeanDefinition方法删除对应的bean对象,并且重新将新的Class对象放到工厂中,下次调用的时候就会通过最新的Class对象创建bean
import java.io.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.springframework.stereotype.Component;
@Component
public class SpringRegister implements ApplicationContextAware {
private ApplicationContext applicationContext;
private DefaultListableBeanFactory defaultListableBeanFactory;
public void upload(MultipartFile file) {
if (ObjectUtils.isEmpty(file)) {
return;
}
File dest = null;
try {
dest = FileUploadUtils.toFile(file);
log.info("path={}", dest.getPath());
} catch (IOException e) {
log.error(e.toString(), e);
}
try {
JarFile jarFile = new JarFile(dest);
Enumeration entries =
jarFile.entries();
List classList = new ArrayList<>();
MyClassLoader myClassLoader = new MyClassLoader();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();
ZipEntry entry = jarFile.getEntry(name);
InputStream inputStream = jarFile.getInputStream(entry);
if (name.endsWith(".class")) {
log.info("name={},entry={},inputStream={}", name, entry, inputStream);
Class> aClass = myClassLoader.loadClass(name.replace("/", ".").replace(".class", ""), inputStream);
classList.add(aClass);
}
}
classList.stream().forEach(aClass -> {
String beanName = DynamicTemplateDataGet.getBeanName(aClass.getSimpleName());
registerBean(beanName, aClass);
});
} catch (Exception e) {
log.error("e={}", e);
}
if (dest.exists()) {
dest.delete();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
}
public void registerBean(String beanName, Class> clazz) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
if (defaultListableBeanFactory.containsBeanDefinition(beanName)) {
defaultListableBeanFactory.removeBeanDefinition(beanName);
}
defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());//注册类信息
}
}
3.存在的问题
因为自定义类加载器加载的类是一个完全新的Class对象,如果存在原来的名称为A类中有对该B类进行引用,会导致A类动态加载的B类跟A类引用的B类不是同一个类,代码如下
class A{
private void test(){
//加载class文件反射出类B
//这时引用的b跟实际自定义类加载器加载出来的类不是同一个,会报错
B b=new MyClassLoad().loadClass("B");
}
}
尝试解决方式,通过父类去做接收,MyClassLoad加载B类的父类C的时候的 loadClass(String name)方法使用的是应用类加载器,所以解析出来的C类跟实际B类继承的类是同一个类(因为加载C类是同一个类加载器,同一个类加载器只会对一个类加载一次)
class A{
private void test(){
//这时候C是B的父类
C c=new MyClassLoad().loadClass("B");
}
}
如果反射调用的类需要引入spring,不想通过反射调用热部署的类,因为前面的加载过程已经将最新的Class对象刷新到spring中,可以这样处理
class A{
private void test(){
C c = SpringUtils.getBean("B");
}
}



