主要观点总结
这是一篇关于「芋道快速开发平台」的知识星球的介绍文章,该星球提供了一系列资料,包括项目实战、面试题、系统设计、学习指南等,并推荐了一个开源项目。文章还介绍了动态加载和分离打包的概念,并展示了动态加载的实现,包括自定义类加载器、动态加载和动态卸载等,同时提供了动态修改本地yml和nacos配置的方法。最后,文章鼓励读者加入知识星球,以获取更多的技术帮助。
关键观点总结
关键观点1: 「芋道快速开发平台」简介
该星球提供了一系列资料,包括项目实战、面试题、系统设计、学习指南等,并推荐了一个开源项目。
关键观点2: 动态加载与分离打包
介绍了动态加载和分离打包的概念,并展示了动态加载的实现,包括自定义类加载器、动态加载和动态卸载等。
关键观点3: 动态修改配置
提供了动态修改本地yml和nacos配置的方法,以便在运行时更新初始化加载配置。
关键观点4: 加入知识星球
鼓励读者加入知识星球,以获取更多的技术帮助,星球内容包括项目实战、面试招聘、源码解析、学习路线等。
正文
URLClassLoader 是一种特殊的类加载器,可以从指定的 URL 中加载类和资源。它的主要作用是动态加载外部的 JAR 包或者类文件,从而实现动态扩展应用程序的功。为了便于管理动态加载的jar包,自定义类加载器继承URLClassloader。
package cn.jy.sjzl.util;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 自定义类加载器
*
* @author lijianyu
* @date 2023/04/03 17:54
**/
publicclass MyClassLoader extends URLClassLoader {
private Map> loadedClasses = new ConcurrentHashMap<>();
public Map> getLoadedClasses() {
return loadedClasses;
}
public MyClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// 从已加载的类集合中获取指定名称的类
Class> clazz = loadedClasses.get(name);
if (clazz != null) {
return clazz;
}
try {
// 调用父类的findClass方法加载指定名称的类
clazz = super.findClass(name);
// 将加载的类添加到已加载的类集合中
loadedClasses.put(name, clazz);
return clazz;
} catch (ClassNotFoundException e) {
e.printStackTrace();
returnnull;
}
}
public void unload() {
try {
for (Map.Entry> entry : loadedClasses.entrySet()) {
// 从已加载的类集合中移除该类
String className = entry.getKey();
loadedClasses.remove(className);
try{
// 调用该类的destory方法,回收资源
Class> clazz = entry.getValue();
Method destory = clazz.getDeclaredMethod("destory");
destory.invoke(clazz);
} catch (Exception e ) {
// 表明该类没有destory方法
}
}
// 从其父类加载器的加载器层次结构中移除该类加载器
close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
-
自定义类加载器中,为了方便类的卸载,定义一个map保存已加载的类信息。key为这个类的ClassName,value为这个类的类信息。
-
同时定义了类加载器的卸载方法,卸载方法中,将已加载的类的集合中移除该类。由于此类可能使用系统资源或调用线程,为了避免资源未回收引起的内存溢出,通过反射调用这个类中的destroy方法,回收资源。
-
由于此项目使用spring框架,以及xxl-job任务的机制调用动态加载的代码,因此要完成以下内容
-
-
将有spring注解的类,通过注解扫描的方式,扫描并手动添加到spring容器中。
-
将@XxlJob注解的方法,通过注解扫描的方式,手动添加到xxljob执行器中。
package com.jy.dynamicLoad;
import com.jy.annotation.XxlJobCron;
import com.jy.classLoader.MyClassLoader;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.core.handler.impl.MethodJobHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author lijianyu
* @date 2023/04/29 13:18
**/
@Component
publicclass DynamicLoad {
privatestatic Logger logger = LoggerFactory.getLogger(DynamicLoad.class);
@Autowired
private ApplicationContext applicationContext;
private Map myClassLoaderCenter = new ConcurrentHashMap<>();
@Value("${dynamicLoad.path}")
private String path;
/**
* 动态加载指定路径下指定jar包
* @param path
* @param fileName
* @param isRegistXxlJob 是否需要注册xxljob执行器,项目首次启动不需要注册执行器
* @return map 创建xxljob任务时需要的参数配置
*/
public void loadJar(String path, String fileName, Boolean isRegistXxlJob) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
File file = new File(path +"/" + fileName);
Map jobPar = new HashMap<>();
// 获取beanFactory
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
// 获取当前项目的执行器
try {
// URLClassloader加载jar包规范必须这么写
URL url = new URL("jar:file:" + file.getAbsolutePath() + "!/");
URLConnection urlConnection = url.openConnection();
JarURLConnection jarURLConnection = (JarURLConnection)urlConnection;
// 获取jar文件
JarFile jarFile = jarURLConnection.getJarFile();
Enumeration entries = jarFile.entries();
// 创建自定义类加载器,并加到map中方便管理
MyClassLoader myClassloader = new MyClassLoader(new URL[] { url }, ClassLoader.getSystemClassLoader());
myClassLoaderCenter.put(fileName, myClassloader);
Set initBeanClass = new HashSet<>(jarFile.size());
// 遍历文件
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
if (jarEntry.getName().endsWith(".class")) {
// 1. 加载类到jvm中
// 获取类的全路径名
String className = jarEntry.getName().replace('/', '.').substring(0, jarEntry.getName().length() - 6);
// 1.1进行反射获取
myClassloader.loadClass(className);
}