前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot 动态加载 jar 包,动态配置太强了

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

作者头像
民工哥
发布2024-04-19 18:58:45
2100
发布2024-04-19 18:58:45
举报

背景

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

目标

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

方案

为了支持业务代码尽量的解耦,把部分业务功能通过动态加载的方式加载到主程序中,以满足可插拔式的加载、组合式的部署。 配合xxl-job任务调度框架,将数据治理任务做成xxl-job任务的方式注册到xxl-job中,方便统一管理。

动态加载

自定义类加载器

URLClassLoader 是一种特殊的类加载器,可以从指定的 URL 中加载类和资源。它的主要作用是动态加载外部的 JAR 包或者类文件,从而实现动态扩展应用程序的功。为了便于管理动态加载的jar包,自定义类加载器继承URLClassloader。

代码语言:javascript
复制
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方法。
动态加载

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

  • 将动态加载的jar包读到内存中
  • 将有spring注解的类,通过注解扫描的方式,扫描并手动添加到spring容器中。
  • 将@XxlJob注解的方法,通过注解扫描的方式,手动添加到xxljob执行器中。
代码语言:javascript
复制
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(,?jarEntry.getName().length()?-?);
????????????????????//?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(,?className.lastIndexOf(".")?+?);
????????????????????String?beanName?=?className.substring(className.lastIndexOf(".")?+?);
????????????????????beanName?=?packageName?+?beanName.substring(,?).toLowerCase()?+?beanName.substring();
????????????????????//?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注解的工具类

代码语言:javascript
复制
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的注册方法。

动态卸载

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

代码如下:

代码语言:javascript
复制
/**
?*?动态卸载指定路径下指定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(,?key.lastIndexOf(".")?+?);
????????String?beanName?=?key.substring(key.lastIndexOf(".")?+?);
????????beanName?=?packageName?+?beanName.substring(,?).toLowerCase()?+?beanName.substring();

????????//?获取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);

}

动态配置

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

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

动态修改本地yml

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

依赖引入
代码语言:javascript
复制
<dependency>
?<groupId>org.yaml</groupId>
????<artifactId>snakeyaml</artifactId>
????<version>1.29</version>
</dependency>
工具类

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

代码语言:javascript
复制
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);
????}
}
动态修改nacos配置

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

代码语言:javascript
复制
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,?);
????????//?修改配置文件内容
????????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配置更新失败");
????????}
????}
}

分离打包

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

代码语言:javascript
复制
<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>
本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-10,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 民工哥技术之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 目标
  • 方案
  • 动态加载
    • 自定义类加载器
      • 动态加载
        • 动态卸载
        • 动态配置
          • 动态修改本地yml
            • 动态修改nacos配置
            • 分离打包
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
            http://www.vxiaotou.com