一、背景
日常项目中,有时会需要一些动态扩展的能力,在服务不需要重启的方式下,动态加载第三方的插件包,来扩展平台服务的功能。假设如果第三方的Jar包需要使用到Spring容器的相关操作,如果直接使用普通的类加载器进行动态加载的话,则Spring的相关注解其实是不会生效的,这时我们就需要将三方Jar包里面的内容也注册到Spring容器中,从而让三方的Jar包也支持Spring的能力。接下来就看看我们该怎么实现吧。
二、相关技术介绍
本章节可以学到的内容包含如下:
- 如何获取Jar包里面的类内容
- 如何使用自定义类加载器实现类的加载和卸载
- 如何将三方Jar包中含有Spring相关的类动态注册到Spring容器中
三、相关图示说明
四、相关实现介绍
1、实现自己的类加载器(ThirdJarClassLoader)继承URLClassLoader
public class ThirdJarClassLoader extends URLClassLoader {
private Map<String, Class<?>> loadedClasses = new ConcurrentHashMap<>();
public Map<String, Class<?>> getLoadedClasses() {
return loadedClasses;
}
public ThirdJarClassLoader(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();
return null;
}
}
public void unload() {
try {
for (Map.Entry<String, Class<?>> 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用来记录三方Jar包已经加载过的Class,用于后面卸载Jar的时候知道需要卸载那些Class。
2、加载第三方的Jar文件并注册到Spring容器
private static Map<String, ThirdJarClassLoader> classLoaderCenter = new ConcurrentHashMap<>();
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ClassInitUtils.applicationContext = applicationContext;
}
/**
* 动态加载指定路径下指定jar包
* @param file
* @return
*/
public static void loadJar(File file) {
String fileName = file.getName();
// 获取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<JarEntry> entries = jarFile.entries();
// 创建自定义类加载器,并加到map中方便管理
ThirdJarClassLoader thirdJarClassloader = new ThirdJarClassLoader(new URL[] { url }, ClassLoader.getSystemClassLoader());
classLoaderCenter.put(fileName, thirdJarClassloader);
Set<Class> 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进行反射获取
thirdJarClassloader.loadClass(className);
}
}
Map<String, Class<?>> loadedClasses = thirdJarClassloader.getLoadedClasses();
for(Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()){
String className = entry.getKey();
Class<?> clazz = entry.getValue();
// 2. 将有@spring注解的类交给spring管理
// 2.1 判断是否注入spring
Boolean flag = SpringAnnotationUtils.hasSpringAnnotation(clazz);
if(flag){
// 2.2交给spring管理
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 此处beanName使用全路径名是为了防止beanName重复
// 2.3注册到spring的beanFactory中
beanFactory.registerBeanDefinition(className, beanDefinition);
// 2.4允许注入和反向注入
beanFactory.autowireBean(clazz);
beanFactory.initializeBean(clazz, className);
initBeanClass.add(clazz);
}
}
// spring bean实际注册
initBeanClass.forEach(beanFactory::getBean);
} catch (Exception e) {
logger.error("读取{} 文件异常, {}", fileName, e.getMessage());
}
}
这边涉及到一个工具类,是如何判断哪些类是需要注册到Spring容器。
3、工具类
public class SpringAnnotationUtils {
private static Logger logger = LoggerFactory.getLogger(SpringAnnotationUtils.class);
/**
* 判断一个类是否有 Spring 核心注解
*
* @param clazz 要检查的类
* @return true 如果该类上添加了相应的 Spring 注解;否则返回 false
*/
public static boolean hasSpringAnnotation(Class<?> clazz) {
if (clazz == null) {
return false;
}
//是否是接口
if (clazz.isInterface()) {
return false;
}
//是否是抽象类
if (Modifier.isAbstract(clazz.getModifiers())) {
return false;
}
try {
if (clazz.getAnnotation(Component.class) != null ||
clazz.getAnnotation(Repository.class) != null ||
clazz.getAnnotation(Service.class) != null ||
clazz.getAnnotation(Controller.class) != null ||
clazz.getAnnotation(Configuration.class) != null) {
return true;
}
}catch (Exception e){
logger.error("出现异常:{}",e.getMessage());
}
return false;
}
}
上面代码中相关Spring的注解可以自行根据所需场景进行动态添加,代码示例中只加入了一下常用的Spring注解。
4、卸载第三方Jar包
/**
* 动态卸载指定路径下指定jar包
* @param fileName
* @return
*/
public static void unloadJar(String fileName) {
// 获取加载当前jar的类加载器
ThirdJarClassLoader thirdJarClassLoader = classLoaderCenter.get(fileName);
if (thirdJarClassLoader == null) {
return;
}
// 获取beanFactory,准备从spring中卸载
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
Map<String, Class<?>> loadedClasses = thirdJarClassLoader.getLoadedClasses();
Set<String> beanNames = new HashSet<>();
for (Map.Entry<String, Class<?>> entry: loadedClasses.entrySet()) {
// 1.1 截取beanName
String key = entry.getKey();
// 获取bean,如果获取失败,表名这个类没有加到spring容器中,则跳出本次循环
Object bean = null;
try{
bean = applicationContext.getBean(key);
}catch (Exception e){
// 异常说明spring中没有这个bean
beanFactory.destroySingleton(key);
beanNames.add(key);
continue;
}
// 2.0从spring中移除,这里的移除是仅仅移除的bean,并未移除bean定义
beanNames.add(key);
beanFactory.destroyBean(key, bean);
}
try {
// 移除bean定义
Field mergedBeanDefinitions = beanFactory.getClass()
.getSuperclass()
.getSuperclass().getDeclaredField("mergedBeanDefinitions");
mergedBeanDefinitions.setAccessible(true);
Map<String, RootBeanDefinition> rootBeanDefinitionMap = ((Map<String, RootBeanDefinition>) mergedBeanDefinitions.get(beanFactory));
for (String beanName : beanNames) {
if (beanFactory.containsBeanDefinition(beanName)) {
beanFactory.removeBeanDefinition(beanName);
}
// 父类bean定义去除
rootBeanDefinitionMap.remove(beanName);
}
// 3.2 从类加载中移除
// 从类加载器底层的classes中移除连接
Field field = ClassLoader.class.getDeclaredField("classes");
field.setAccessible(true);
Vector<Class<?>> classes = (Vector<Class<?>>) field.get(thirdJarClassLoader);
classes.removeAllElements();
// 移除类加载器的引用
classLoaderCenter.remove(fileName);
// 卸载类加载器
thirdJarClassLoader.unload();
} catch (NoSuchFieldException e) {
logger.error("动态卸载的类,从类加载器中卸载失败");
e.printStackTrace();
} catch (IllegalAccessException e) {
logger.error("动态卸载的类,从类加载器中卸载失败");
e.printStackTrace();
}
logger.error("{} 动态卸载成功", fileName);
}
至此整体的实现已经完毕,大家如果在实现过程有问题可以留言讨论,喜欢我的内容可以点赞、关注支持。