添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐

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
@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%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