第三方的Jar包如何在运行时动态注册到Spring容器的实现

一、背景
日常项目中,有时会需要一些动态扩展的能力,在服务不需要重启的方式下,动态加载第三方的插件包,来扩展平台服务的功能。假设如果第三方的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);

    }

至此整体的实现已经完毕,大家如果在实现过程有问题可以留言讨论,喜欢我的内容可以点赞、关注支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

漂泊之云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值