springboot 动态加载jar包,插件式加载运行

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

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

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

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

springboot 动态加载jar包,插件式加载运行_第1张图片

包介绍:

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
 * 

*/ @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 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%2B8&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包,如图:

springboot 动态加载jar包,插件式加载运行_第2张图片

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

springboot 动态加载jar包,插件式加载运行_第3张图片

你可能感兴趣的:(spring,boot,jar,java)