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

SpringBoot 动态加载jar包,动态配置 目前数据治理服务中有众多治理任务,当其中任一治理任务有改动需要升级或新增一个治理任务时,都需要将数据治理服务重启,会影响其他治理任务的正常运行。URLClassLoader 是一种特殊的类加载器,可以从指定的 URL 中加载类和资源。它的主要作用是动态加载外部的 JAR 包或者类文件,从而实现动态扩展应用程序的功。为了便于管理动态加载的jar包,自定义类加载器继承URLClassloader。/*** 自定义类加载器**/@Override// 从已加载的类集合中获取指定名称的类。 阅读详情

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

f1f643c9af69f8a59faa51abb5599050.gif

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号、CRM 等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本 

来源:网络

8640a1d0793ba6a70426fc707ddaeca6.jpeg


一、概述

1、背景

目前数据治理服务中有众多治理任务,当其中任一治理任务有改动需要升级或新增一个治理任务时,都需要将数据治理服务重启,会影响其他治理任务的正常运行。

2、目标

  • 能够动态启动、停止任一治理任务

  • 能够动态升级、添加治理任务

  • 启动、停止治理任务或升级、添加治理任务不能影响其他任务

3、方案

  • 为了支持业务代码尽量的解耦,把部分业务功能通过动态加载的方式加载到主程序中,以满足可插拔式的加载、组合式的部署。

  • 配合xxl-job任务调度框架,将数据治理任务做成xxl-job任务的方式注册到xxl-job中,方便统一管理。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

二、动态加载

1、自定义类加载器

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
 **/
public class MyClassLoader extends URLClassLoader {

    private Map<String, Class<?>> loadedClasses = new ConcurrentHashMap<>();

    public Map<String, Class<?>> 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();
            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保存已加载的类信息。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
public class DynamicLoad {

    private static Logger logger = LoggerFactory.getLogger(DynamicLoad.class);

    @Autowired
    private ApplicationContext applicationContext;

    private Map<String, MyClassLoader> myClassLoaderCenter = new ConcurrentHashMap<>();

    @Value("${dynamicLoad.path}")
    private String path;

    /**
     * 动态加载指定路径下指定jar包
     * @param path
     * @param fileName
     * @param isRegistXxlJob  是否需要注册xxljob执行器,项目首次启动不需要注册执行器
     * @return map<jobHander, Cron> 创建xxljob任务时需要的参数配置
     */
    public void loadJar(String path, String fileName, Boolean isRegistXxlJob) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        File file = new File(path +"/" + fileName);
        Map<String, String> 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<JarEntry> entries = jarFile.entries();

            // 创建自定义类加载器,并加到map中方便管理
            MyClassLoader myClassloader = new MyClassLoader(new URL[] { url }, ClassLoader.getSystemClassLoader());
            myClassLoaderCenter.put(fileName, myClassloader);
            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进行反射获取
                    myClassloader.loadClass(className);
                }
            }
            Map<String, Class<?>> loadedClasses = myClassloader.getLoadedClasses();
            XxlJobSpringExecutor xxlJobExecutor = new XxlJobSpringExecutor();
            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重复
                    String packageName = className.substring(0, className.lastIndexOf(".") + 1);
                    String beanName = className.substring(className.lastIndexOf(".") + 1);
                    beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);
                    // 2.3注册到spring的beanFactory中
                    beanFactory.registerBeanDefinition(beanName, beanDefinition);
                    // 2.4允许注入和反向注入
                    beanFactory.autowireBean(clazz);
                    beanFactory.initializeBean(clazz, beanName);
                    /*if(Arrays.stream(clazz.getInterfaces()).collect(Collectors.toSet()).contains(InitializingBean.class)){
                        initBeanClass.add(clazz);
                    }*/
                    initBeanClass.add(clazz);
                }

                // 3. 带有XxlJob注解的方法注册任务
                // 3.1 过滤方法
                Map<Method, XxlJob> annotatedMethods = null;
                try {
                    annotatedMethods = MethodIntrospector.selectMethods(clazz,
                            new MethodIntrospector.MetadataLookup<XxlJob>() {
                                @Override
                                public XxlJob inspect(Method method) {
                                    return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                                }
                            });
                } catch (Throwable ex) {
                }
                // 3.2 生成并注册方法的JobHander
                for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
                    Method executeMethod = methodXxlJobEntry.getKey();
                    // 获取jobHander和Cron
                    XxlJobCron xxlJobCron = executeMethod.getAnnotation(XxlJobCron.class);
                    if(xxlJobCron == null){
                        throw new CustomException("500", executeMethod.getName() + "(),没有添加@XxlJobCron注解配置定时策略");
                    }
                    if (!CronExpression.isValidExpression(xxlJobCron.value())) {
                        throw new CustomException("500", executeMethod.getName() + "(),@XxlJobCron参数内容错误");
                    }
                    XxlJob xxlJob = methodXxlJobEntry.getValue();
                    jobPar.put(xxlJob.value(), xxlJobCron.value());
                    if (isRegistXxlJob) {
                        executeMethod.setAccessible(true);
                        // regist
                        Method initMethod = null;
                        Method destroyMethod = null;
                        xxlJobExecutor.registJobHandler(xxlJob.value(), new CustomerMethodJobHandler(clazz, executeMethod, initMethod, destroyMethod));
                    }
                }

            }
            // spring bean实际注册
            initBeanClass.forEach(beanFactory::getBean);
        } catch (IOException e) {
            logger.error("读取{} 文件异常", fileName);
            e.printStackTrace();
            throw new RuntimeException("读取jar文件异常: " + fileName);
        }
    }
}

以下是判断该类是否有spring注解的工具类

apublic 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;
    }
}

注册xxljob执行器的操作是仿照的xxljob中的XxlJobSpringExecutor的注册方法。

3、动态卸载

动态卸载的过程,就是将动态加载的代码,从内存,spring以及xxljob中移除。

代码如下:

/**
 * 动态卸载指定路径下指定jar包
 * @param fileName
 * @return map<jobHander, Cron> 创建xxljob任务时需要的参数配置
 */
public void unloadJar(String fileName) throws IllegalAccessException, NoSuchFieldException {
    // 获取加载当前jar的类加载器
    MyClassLoader myClassLoader = myClassLoaderCenter.get(fileName);

    // 获取jobHandlerRepository私有属性,为了卸载xxljob任务
    Field privateField = XxlJobExecutor.class.getDeclaredField("jobHandlerRepository");
    // 设置私有属性可访问
    privateField.setAccessible(true);
    // 获取私有属性的值jobHandlerRepository
    XxlJobExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
    Map<String, IJobHandler> jobHandlerRepository = (ConcurrentHashMap<String, IJobHandler>) privateField.get(xxlJobSpringExecutor);
    // 获取beanFactory,准备从spring中卸载
    DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
    Map<String, Class<?>> loadedClasses = myClassLoader.getLoadedClasses();

    Set<String> beanNames = new HashSet<>();
    for (Map.Entry<String, Class<?>> entry: loadedClasses.entrySet()) {
        // 1. 将xxljob任务从xxljob执行器中移除
        // 1.1 截取beanName
        String key = entry.getKey();
        String packageName = key.substring(0, key.lastIndexOf(".") + 1);
        String beanName = key.substring(key.lastIndexOf(".") + 1);
        beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);

        // 获取bean,如果获取失败,表名这个类没有加到spring容器中,则跳出本次循环
        Object bean = null;
        try{
            bean = applicationContext.getBean(beanName);
        }catch (Exception e){
            // 异常说明spring中没有这个bean
            continue;
        }

        // 1.2 过滤方法
        Map<Method, XxlJob> annotatedMethods = null;
        try {
            annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                    new MethodIntrospector.MetadataLookup<XxlJob>() {
                        @Override
                        public XxlJob inspect(Method method) {
                            return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                        }
                    });
        } catch (Throwable ex) {
        }
        // 1.3 将job从执行器中移除
        for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
            XxlJob xxlJob = methodXxlJobEntry.getValue();
            jobHandlerRepository.remove(xxlJob.value());
        }
        // 2.0从spring中移除,这里的移除是仅仅移除的bean,并未移除bean定义
        beanNames.add(beanName);
        beanFactory.destroyBean(beanName, bean);
    }
    // 移除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) {
        beanFactory.removeBeanDefinition(beanName);
        // 父类bean定义去除
        rootBeanDefinitionMap.remove(beanName);
    }

    // 卸载父任务,子任务已经在循环中卸载
    jobHandlerRepository.remove(fileName);
    // 3.2 从类加载中移除
    try {
        // 从类加载器底层的classes中移除连接
        Field field = ClassLoader.class.getDeclaredField("classes");
        field.setAccessible(true);
        Vector<Class<?>> classes = (Vector<Class<?>>) field.get(myClassLoader);
        classes.removeAllElements();
        // 移除类加载器的引用
        myClassLoaderCenter.remove(fileName);
        // 卸载类加载器
        myClassLoader.unload();
    } catch (NoSuchFieldException e) {
        logger.error("动态卸载的类,从类加载器中卸载失败");
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        logger.error("动态卸载的类,从类加载器中卸载失败");
        e.printStackTrace();
    }
    logger.error("{} 动态卸载成功", fileName);

}

4、动态配置

使用动态加载时,为了避免服务重新启动后丢失已加载的任务包,使用动态配置的方式,加载后动态更新初始化加载配置。

以下提供了两种自己实际操作过的配置方式。

4.1 动态修改本地yml

动态修改本地yml配置文件,需要添加snakeyaml的依赖

4.1.1 依赖引入

<dependency>
 <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.29</version>
</dependency>

4.1.2 工具类

读取指定路径下的配置文件,并进行修改。

package com.jy.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 用于动态修改bootstrap.yml配置文件
 * @author lijianyu
 * @date 2023/04/18 17:57
 **/
@Component
public class ConfigUpdater {

    public void updateLoadJars(List<String> jarNames) throws IOException {
        // 读取bootstrap.yml
        Yaml yaml = new Yaml();
        InputStream inputStream = new FileInputStream(new File("src/main/resources/bootstrap.yml"));
        Map<String, Object> obj = yaml.load(inputStream);
        inputStream.close();

        obj.put("loadjars", jarNames);

        // 修改
        FileWriter writer = new FileWriter(new File("src/main/resources/bootstrap.yml"));
        DumperOptions options = new DumperOptions();
        options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        options.setPrettyFlow(true);
        Yaml yamlWriter = new Yaml(options);
        yamlWriter.dump(obj, writer);
    }
}
4.2 动态修改nacos配置

Spring Cloud Alibaba Nacos组件完全支持在运行时通过代码动态修改配置,还提供了一些API供开发者在代码里面实现动态修改配置。在每次动态加载或卸载数据治理任务jar包时,执行成功后都会进行动态更新nacos配置。

package cn.jy.sjzl.config;

import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

@Configuration
public class NacosConfig {
    @Value("${spring.cloud.nacos.server-addr}")
    private String serverAddr;

    @Value("${spring.cloud.nacos.config.namespace}")
    private String namespace;

    public ConfigService configService() throws NacosException {
        Properties properties = new Properties();
        properties.put("serverAddr", serverAddr);
        properties.put("namespace", namespace);
        return NacosFactory.createConfigService(properties);
    }
}
package cn.jy.sjzl.util;

import cn.jy.sjzl.config.NacosConfig;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.ConfigService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * nacos配置中,修改sjzl-loadjars.yml
 *
 * @author lijianyu
 * @date 2023/04/19 17:59
 **/
@Component
public class NacosConfigUtil {

    private static Logger logger = LoggerFactory.getLogger(NacosConfigUtil.class);

    @Autowired
    private NacosConfig nacosConfig;

    private String dataId = "sjzl-loadjars.yml";

    @Value("${spring.cloud.nacos.config.group}")
    private String group;

    /**
     * 从nacos配置文件中,添加初始化jar包配置
     * @param jarName 要移除的jar包名
     * @throws Exception
     */
    public void addJarName(String jarName) throws Exception {
        ConfigService configService = nacosConfig.configService();
        String content = configService.getConfig(dataId, group, 5000);
        // 修改配置文件内容
        YAMLMapper yamlMapper = new YAMLMapper();
        ObjectMapper jsonMapper = new ObjectMapper();
        Object yamlObject = yamlMapper.readValue(content, Object.class);

        String jsonString = jsonMapper.writeValueAsString(yamlObject);
        JSONObject jsonObject = JSONObject.parseObject(jsonString);
        List<String> loadjars;
        if (jsonObject.containsKey("loadjars")) {
            loadjars = (List<String>) jsonObject.get("loadjars");
        }else{
            loadjars = new ArrayList<>();
        }
        if (!loadjars.contains(jarName)) {
            loadjars.add(jarName);
        }
        jsonObject.put("loadjars" , loadjars);

        Object yaml = yamlMapper.readValue(jsonMapper.writeValueAsString(jsonObject), Object.class);
        String newYamlString = yamlMapper.writeValueAsString(yaml);
        boolean b = configService.publishConfig(dataId, group, newYamlString);

        if(b){
            logger.info("nacos配置更新成功");
        }else{
            logger.info("nacos配置更新失败");
        }
    }
}

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

三、分离打包

分离打包时,根据实际情况在pom.xml中修改以下配置

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.2.4</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <filters>
                            <filter>
                                <artifact>*:*</artifact>
                                <includes>
                                    <include>com/jy/job/demo/**</include>
                                </includes>
                            </filter>
                        </filters>
                        <finalName>demoJob</finalName>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,“长按”或“扫描”下方二维码噢:

4420ff7a150c477c351b023f5303684f.png

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

277efea727fffe3dcb9d75e31ac35226.png

c4e83c9a6c2da460ef0538de0dca1f14.pngc85331abbff51821102f3bc46961d2ea.png559da4208a05305fbd334845b7cb6fab.png46df7cac4545a0e4326cfbaf156fe1b7.png

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
使用 XJAR 对 Spring Boot JAR 包加密运行的实践与解析 Spring Boot 作为一个基于 Spring 的快速开发框架,以其极简的配置、强大的功能以及广泛的生态支持,成为了现代 Java 开发中的主流选择。它通过对 JAR 包中的 Java 类文件进行加密,阻止未经授权的用户反编译和查看源代码,从而保障应用程序的知识产权和商业逻辑。XJAR 的加密技术并不会改变 JAR 包的整体结构,而是在 JAR 包中嵌入加密算法和密钥,只有经过授权的用户才能解密并正常运行该 JAR 文件。加密后的类文件会被打包回 JAR 文件中,并嵌入解密所需的密钥。 阅读详情

相关推荐

Spring Boot动态加载Jar包与动态配置技术探究

在当今快节奏的软件开发领域,高效的开发框架是保持竞争力的关键。Spring Boot作为一款快速开发框架,以其简化配置、内嵌Web服务器、强大的开发工具等特性,成为众多开发者的首选。其背后的设计理念旨在提高开发效率,使得开发者能够更专注于业务逻辑而非繁琐的配置与部署。 然而,随着项目的不断演进和业务需求的增长,很多场景下需要实现系统的动态性和灵活性。这就引出了动态加载与动态配置的问题。传统的静态加载和配置方式在某些情况下显得力不从心,因此我们需要更加灵活、动态的机制来应对这些挑战。

fudaihb的博客 4030

Java实战:Spring Boot动态加载Jar包

本文将详细介绍如何在Spring Boot应用程序中动态加载Jar包。我们将探讨动态加载Jar包的基本概念,以及如何使用Spring Boot的类加载器和第三方库来实现Jar包的动态加载。

oandy0的博客 6082

SpringBoot 动态加载 Jar 包:实现灵活的动态配置方案

本文将深入探讨如何在SpringBoot应用中实现动态加载Jar包的方案。我们将首先回顾SpringBoot的基础知识,包括其核心概念和特点,以及动态加载Jar包的相关技术和Java类加载器机制。接着,我们将详细介绍动态加载的必要性,分析业务场景和动态加载的优势。然后,我们会提供几种实现动态加载的技术方案,并详细解释每一种方案的实现步骤和注意事项。此外,本文还会分享如何进行测试与验证、实际使用案例、常见问题与解决方案,以及对这一技术的总结与展望。

fudaihb的博客 3943

Java-springboot动态加载jar包,动态配置

服务中有众多任务,当其中任一任务有改动需要升级或新增一个任务时,都需要将服务重启,会影响其他任务的正常运行。因此采用java动态加载的方式,将不同任务插件化形式加载到服务中心,配合xxl-job任务调度框架统一管理。实现了插件化任务管理。

1 1万+

SpringBoot 动态加载Jar包,你真的会了吗?

首先,我们需要创建一个自定义的ClassLoader,以便能够在运行时加载jar包中的类。

java专栏 1736

​银行IT运维人必看!IT运维监控指标大全,一篇讲透

文章摘要:本文从七个维度系统阐述了IT运维指标体系,涵盖机房环境、基础设施、网络设施、数据存储、虚拟化平台、数据库中间件和应用层。作者结合20年金融行业经验,详细分析了各层级关键监控指标、正常范围及异常后果,并给出典型故障场景。重点强调现代运维需从被动响应转向主动预防,通过精细化指标管理实现系统稳定性保障。文章特别指出,在国产化与DevOps背景下,运维工作需兼顾传统架构与新兴技术,构建全方位监控体系,将运维从"故障报警"升级为"业务守护"。

最近开始研究AIGC领域技术,欢迎交流 1106

SpringBoot动态加载jar包中的bean

springboot动态加载jar中的bean,在有些业务场景下,需要SpringBoot来动态加载jar中的class文件,自动往spring容器中添加新的bean;如物联网设备上传的信息用物模型来解析,用java来解析物模型,但用户的设备千差万别,解析设备的物模型不可能包罗万象,设备往物联网平台上传的数据格式也是千差万别,这时就可以让用户自已实现解析物模型的java代码,然后打成jar包,上传到物联网平台,物联网平台就可以解析当前用的设备信息了。

专注技术交流、咨询 5070

R语言基础之R语言入门

R语言最初是由新西兰奥克兰大学统计系的教授Ross Ihaka和Robert Gentleman在S语言基础上开发完成的。是一门解释性语言。在我看来R语言是一门数学性极强的语言,或者说这是一门为数学而生的语言,因为其具有极其出色的计算与统计分析能力...

weixin_46912400的博客 5万+

利用spring IOC 实现 模块化开发 动态加载 jar包(包括第三方)不需要重启

模块化开发的方式有很多 基于rest、webservice 接口 或者服务器集群结合单点登录或者spring cloud 微服务等方式 。无非就是要实现 在不影响现有系统正常运行的情况下 上线 或者下线某些功能模块。更新系统的时候不影响正常不在更新范围内的功能。保证系统稳定。 下面我说一种基于spring IOC 方式实现的动态加载卸载jar文件 来实现模块化开发的功能。 这种方式 下各个模块之间...

napcleon1的博客 4719

Android Qcom USB Driver学习(五)

在前面的第二期中,我们知道一些非标的USB的充电类型Floatingcharger,其具有和普通DCP相似的大电流能力,但在最初检测的时候会限制Inputcurrentlimit到100mA,来进行检测是否是SDP类型,所以要支持大电流输入就需要进行vote操作,前面的几篇都有涉及,所以本文学习一下pmicusbcharger都相关的vote机制。...

Android驱动开发 2902

提升AI智能体回复质量:扣子平台提示词优化技巧与案例解析

本文深入探讨了在扣子平台上优化AI智能体提示词的关键技巧与实战案例。通过分析提示词设计的核心原则、业务场景适配策略以及平台高级功能应用,帮助开发者显著提升AI Agent的回复质量。文章特别强调了结构化提示词设计和A/B测试评估的重要性,并提供了电商客服场景的完整优化案例。

weixin_30897233的博客 538

SpringBoot 动态加载 Jar 包

动态加载Jar包是指在应用程序运行时,动态地加载和卸载Jar包中的类和资源。这种机制可以提高系统的灵活性和可扩展性,允许在不重启应用程序的情况下,添加、更新或删除组件。本文详细介绍了如何在Spring Boot应用程序中实现动态加载Jar包。我们首先了解了动态加载Jar包的基本概念和作用,然后学习了如何使用Spring Boot的类加载器和第三方库来实现Jar包的动态加载。我们还通过具体的示例展示了如何在Spring Boot应用程序中配置和使用Jar包的动态加载,以提高系统的灵活性和可扩展性。

Oasis 1453

力软敏捷开发框架源码7.0.6旗舰版 JNPF开发平台3.4.5 企业版 旗舰版

JNPF作为作为一款深受用户喜爱的低代码产品,凭借雄厚的科研技术力量、丰富的软件开发项目经验、严谨的项目开发流程和高质量的售后服务,已成功帮助千家企业完成数字化项目,建立了覆盖不同行业客户和不同发展阶段的软件产品服务体系。

huoooos0的博客 1841

基于 Spring Boot 的插件化 JAR 包热加载方案

在主程序中定义,规定插件的生命周期(安装、卸载)与业务行为。// 安装逻辑// 卸载逻辑// 插件元数据// 业务执行入口if (!throw new RuntimeException(getPluginName() + " 插件尚未就绪!");动态插件化方案非常适合私有化部署和定制化需求频繁的项目。核心收益:无需修改主程序源码,通过扩展接口实现业务解耦。jar插件的热部署、卸载、更新(springboot)

howeres的博客 1173

斯坦福HumanPlus机器人核心技术解析:从HST强化学习框架到HIT模仿学习的实现路径

本文深入解析了斯坦福HumanPlus人形机器人的核心技术实现路径。其核心在于HST强化学习框架与HIT模仿学习架构的协同:HST作为“小脑”,通过rsl_rl框架在仿真中训练稳健的低级运动控制;HIT作为“大脑”,基于改进的Transformer架构学习高级任务规划。文章详细拆解了从算法原理、代码实现到硬件部署的全流程,为复现这一前沿机器人系统提供了实战指南。

weixin_29082143的博客 283

手把手教你用nRF52832+RFX2401C设计远距离蓝牙模块(附完整原理图/PCB文件)

本文详细介绍了如何利用nRF52832 SoC与RFX2401C射频前端芯片设计远距离蓝牙模块,涵盖芯片选型、阻抗匹配、PCB布局等关键环节。通过实战指南和完整原理图/PCB文件,帮助工程师实现输出功率可达20dBm的蓝牙模块设计,适用于智能家居、运动追踪等物联网应用。

weixin_29214199的博客 287

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

点击关注公众号,Java干货及时送达????背景目前数据治理服务中有众多治理任务,当其中任一治理任务有改动需要升级或新增一个治理任务时,都需要将数据治理服务重启,会影响其他治理任务的正常运行。目标能够动态启动、停止任一治理任务能够动态升级、添加治理任务启动、停止治理任务或升级、添加治理任务不能影响其他任务方案为了支持业务代码尽量的解耦,把部分业务功能通过动态加载的方式加载到主程序中,以满足可插拔式的加载...

民工哥的博客 406

把 SAP OData 查询模式讲透:从 $select 到 $expand 的设计、实现与性能边界

摘要:本文深入探讨SAP生态中OData查询模式的核心设计与实现要点。文章指出,OData不仅是接口协议,更是HTTP、REST与数据模型的统一语言,其性能关键取决于查询选项($select、$filter等)的设计与实现质量。重点分析了$select的投影优化、$orderby的排序语义、$filter的数据库下推策略,以及$count的高效计数方法。通过具体示例和最佳实践,强调应将查询逻辑尽量下推至数据库层,避免在ABAP应用层处理大数据集,从而提升服务性能、降低资源消耗,实现Clean Core架构理

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 655

SpringBoot 动态加载 Jar 包,玩转动态配置!

动态加载Jar包是指在应用程序运行时,动态地加载和卸载Jar包中的类和资源。这种机制可以提高系统的灵活性和可扩展性,允许在不重启应用程序的情况下,添加、更新或删除组件。本文详细介绍了如何在Spring Boot应用程序中实现动态加载Jar包。我们首先了解了动态加载Jar包的基本概念和作用,然后学习了如何使用Spring Boot的类加载器和第三方库来实现Jar包的动态加载。我们还通过具体的示例展示了如何在Spring Boot应用程序中配置和使用Jar包的动态加载,以提高系统的灵活性和可扩展性。

2501_92606851的博客 547

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

SpringBoot第十二篇:热加载第三方jar包(解决嵌套jar读取、加载、动态配置、bean注册、依赖等问题),及其精髓

spring boot 动态加载模块(加载外部jar包) ImportBeanDefinitionRegistrar) Spring Boot 如何热加载jar实现动态插件? …

专注分享个人所得,希望给大家带来帮助 1万+

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

上一篇: 别再用if校验参数了,太Low!这才是专业的SpringBoot参数校验方式!
下一篇: 后端行情变了,差别真的挺大。。。
公众号-芋道源码
博客等级 码龄10年 1634粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值