为了支持业务代码尽量的解耦,把部分业务功能以插件的方式加载到主程序中,以满足组合式的部署,接下来我们就来聊聊关于springboot运行时加载jar包?以下内容大家不妨参考一二希望能帮到您!

springboot运行时加载jar包(springboot动态加载jar包插件式加载运行)

springboot运行时加载jar包

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

我们的应用场景是这样的:公司集成了xxl-job调度框架,而调度框架分为,调度中心和执行器两部分。所有的任务业务代码都写在一个执行器里,则会造成代码重并且不利于各服务器部署组织。比如我有30个自动任务需要处理,一共有3台服务器(执行器),写在一起的话,我所有的执行器都需要加载30个任务,而改造分开后,则根据情况可以把1~10分配到第一台执行器中执行。11~20分配到第二台执行器中执行。其它分配到第三台执行器执行。这样业务就很清晰了。而且30个任务分别30个工程或模块管理,耦合度低,有利于代码管理 。

废话说的有点多,那就个代码吧。

首先,看一下目前代码结构,如下图:

包介绍:

1、ts-server-executor:为xxl-job的执行器是最终的运行web服务,其它任务会以jar包插件的形式加载到服务中。

2、ts-jobs:表示自动任务业务包

3、ts-jobs-common:表示公司的依赖包、类等。

4、ts-jobs-mapper:表示统一的数据库访问层代码。

5、ts-jobs-modules:表示自动任务业务模块包。

6、ts-jobs-demo和ts-jobs-logs为具体业务任务包。

核心源码

package com.tsingsoft.executor.config; import com.tsingsoft.executor.utils.ClassLoaderUtil; import com.tsingsoft.executor.utils.PackageScanner; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.beanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.EnvironmentAware; import org.springframework.context.Annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import java.lang.reflect.Modifier; import java.net.URL; import java.util.List; /** * 启动时注册Bean核心类 * 此类用于动态加载jar中的类进行自动注册进系统。 * @author bask * @version 1.0 * @date 2022/7/5 * <p> */ @Slf4j public class PluginImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware { /** * 存储Jar文件基础路径 */ private String basePath; /** * 包名称集,多个名称则通过","逗号进行区分。 */ private String jarNames; /** * 包前缀,如:com.tsingsoft */ private String packagePrefix; @SneakyThrows @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { try { if(jarNames==null){ log.warn("加载包名称为空,如果需要加载则需要配置,请知晓!"); return; } String[] jarNameses = jarNames.split(","); String[] packagePrefixes = packagePrefix.split(","); for (int i = 0; i <jarNameses.length ; i ) { String jarName = jarNameses[i]; String path = "file:/" basePath "/" jarName; ClassLoader classLoader = ClassLoaderUtil.getClassLoader(path); URL url = new URL("jar:" path "!/"); packageScanner scanner = new PackageScanner(); for (int j = 0; j < packagePrefixes.length; j ) { packagePrefix = packagePrefixes[j]; List<String> pluginClasses = scanner.getClassesNamesByJar(packagePrefix,url); pluginClasses.stream().filter(x->x.startsWith(packagePrefix)).forEach(cls->{ log.info("pluginClass:{}",cls); if(cls.startsWith(packagePrefix) && cls!=null){ Class<?> clazz = null; try { clazz = classLoader.loadClass(cls); //注册 registerBean(clazz, registry); log.info("register bean [{}],Class [{}] success.", clazz.getName(), clazz); } catch (Exception e) { e.printStackTrace(); } }else { log.warn("存在空值》》》》》》》》》"); } }); } } log.info("加载对应jar包成功!"); }catch (Exception e){ e.printStackTrace(); log.warn("指定插件目录没有加载对应合法jar包"); } } /** * 註冊BEAN * @param c * @param registry */ private void registerBean(Class<?> c, BeanDefinitionRegistry registry) { String className = c.getName(); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(c); BeanDefinition beanDefinition = builder.getBeanDefinition(); if (isSpringBeanClass(c)) { registry.registerBeanDefinition(className, beanDefinition); } } /** * 方法描述 判断class对象是否带有spring的注解 * 存放實現 * * @param cla jar中的每一个class * @return true 是spring bean false 不是spring bean * @method isSpringBeanClass */ public boolean isSpringBeanClass(Class<?> cla) { if (cla == null) { return false; } //是否是接口 if (cla.isInterface()) { return false; } //是否是抽象类 if (Modifier.isAbstract(cla.getModifiers())) { return false; } try { if (cla.getAnnotation(Component.class) != null) { return true; } }catch (Exception e){ log.error("出现异常:{}",e.getMessage()); } try { if (cla.getAnnotation(Repository.class) != null) { return true; } }catch (Exception e){ log.error("出现异常:{}",e.getMessage()); } try { if (cla.getAnnotation(Service.class) != null) { return true; } }catch (Exception e){ log.error("出现异常:{}",e.getMessage()); } return false; } /** * 因加载顺序原因,则获取配置不用通过@Value来获取。 * @param environment */ @Override public void setEnvironment(Environment environment) { this.basePath = environment.getProperty("basePath"); this.packagePrefix = environment.getProperty("packagePrefix"); this.jarNames = environment.getProperty("jarNames"); } }

通过在启动程序加载核心类,则可以把外部第三方jar包加载以loadclass中。

其中涉及一下配置:


server.port=18012 spring.main.allow-bean-definition-overriding=true spring.datasource.url=jdbc:mysql://127.0.0.1:3306/******?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT+8&autoReconnect=true&useSSL=false spring.datasource.username=***** spring.datasource.password=****** # -----------动态加载jar配置------------------ # 加载存放jar的基础包名 basePath=E:/workspace/2022/ts-dynamic-project/plugins # jar名称配置:多个jar用","区分 jarNames=ts-jobs-demo-0.0.1-SNAPSHOT.jar,ts-jobs-logs-0.0.1-SNAPSHOT.jar # jar包代码基础路径 packagePrefix=com.tsingsoft mybatis-plus.mapper-locations=classpath:com/tsingsoft/**/mapper/*.xml # ---------------------------- xxl-job ------------------------------------- ### xxl-job admin address list, such as "http://address" or "http://address01,http://address02" xxl.job.admin.addresses=http://192.168.10.81:18011 ### xxl-job, access token xxl.job.accessToken=123456 ### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册,此处的AppName和界面设置执行器管理中AppName名,保持一致,这样才能完成自动注册。 xxl.job.executor.appname=ts-server-executor ### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。 xxl.job.executor.address= ### xxl-job executor server-info xxl.job.executor.ip= ### 设置为0表示执行器端口随机分配,如果指定端口,则直接填写端口,如:9999 xxl.job.executor.port=${server.port} #xxl.job.executor.port=9998 ### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径; xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler ### xxl-job executor log-retention-days xxl.job.executor.logretentiondays=30

插件包

其中工程中的plugins为打包后的jar包,如图:

打包的maven插件可参考下图: