专栏名称: 芋道源码
纯 Java 源码分享公众号,目前有「Dubbo」「SpringCloud」「Java 并发」「RocketMQ」「Sharding-JDBC」「MyCAT」「Elastic-Job」「SkyWalking」「Spring」等等
目录
相关文章推荐
芋道源码  ·  5.6K ... ·  15 小时前  
芋道源码  ·  为什么有些程序员上班时总是戴着耳机? ·  15 小时前  
芋道源码  ·  面试官:为什么数据库连接很消耗资源? ·  2 天前  
芋道源码  ·  我用这11招,让接口性能提升了100倍 ·  2 天前  
芋道源码  ·  2W字全面剖析 Mybatis 中的9种设计模式 ·  2 天前  
51好读  ›  专栏  ›  芋道源码

Spring Boot 动态加载jar包,动态配置太强了!

芋道源码  · 公众号  · Java  · 2025-04-24 09:30

主要观点总结

这是一篇关于「芋道快速开发平台」的知识星球的介绍文章,该星球提供了一系列资料,包括项目实战、面试题、系统设计、学习指南等,并推荐了一个开源项目。文章还介绍了动态加载和分离打包的概念,并展示了动态加载的实现,包括自定义类加载器、动态加载和动态卸载等,同时提供了动态修改本地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方法,回收资源。
  • 最后调用close方法。

2、动态加载

由于此项目使用spring框架,以及xxl-job任务的机制调用动态加载的代码,因此要完成以下内容

  • 将动态加载的jar包读到内存中
  • 将有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);
                }






请到「今天看啥」查看全文