Activiti6 + Pf4J 解决serviceTask ClassNotFount 问题


一、背景

最近在使用 Springboot + Activiti6 重构和优化审批流程闭环管理的项目,前期由于该项目会不停的将软件应用到不同的用户与场景,同时可预知的未来可能还会存在各种用户的各种特定需求,所以为了以后的软件升级和代码维护,所以我准备使用插件的模式来做项目的设计。

不同用户的项目核心程序使用一套统一的核心逻辑代码,特定功能通过不同的用户插件实现特定功能,不影响其他用户版本的软件功能,核心代码通过接口的方式为插件提供功能接入,在应对不同用户的不同需求的时候只需要扩展接口开发插件,在插件中实现具体的核心功能逻辑即可。

因此在插件框架的选择上我选择了pf4j,因为使用起来比较简单。pf4j每个插件通过不同的ClassLoader将对应插件内的接口实现类加载到程序中。

二、项目设计

按照上面的思路,我将项目分为了三个模块

  • activiti-core
    负责整合项目的核心逻辑,包括前端接口,流程处理逻辑,与插件交互逻辑等应用的核心逻辑
  • activiti-plugin-api
    用来定义插件应该实现的接口,activiti-core 通过该模块与所有实现了这些插件api的插件进行交互。
  • activiti-plugin-xxx
    所有实现了 activiti-plugin-api 对应扩展接口的插件集合,用于处理不同用户的特定的业务逻辑。(包括不同要求的任务通知、不同要求的审批单生成逻辑等)

三、存在的问题

首先,根据上面的设计,所有的插件是不应该引用任何activiti-core的代码,在插件层面应该是对activiti-core模块没有任何感知的,插件只应该通过实现对应的 activiti-api 接口达到与activiti-core 交互的目的。

其次,具体的流程文件定义是在插件中的,流程中很大概率会存在类似于 serviceTask 这样的任务节点,比如:

<serviceTask id="servicetask1" name="文档上传" activiti:class="com.anke.activiti.plugin.prodef.jisan.UploadFileService">
      <extensionElements>
        <activiti:field name="uploadFile">
          <activiti:expression><![CDATA[${warnNoticeDoc}]]></activiti:expression>
        </activiti:field>
      </extensionElements>
</serviceTask>

这里我定义了一个文件上传的serviceTask节点,指定了使用 com.anke.activiti.plugin.prodef.jisan.UploadFileService 这个类来进行处理。

ClassNotFoundException

但是,由于PF4J每一个插件使用不同的ClassLoader,而Activiti的核心处理逻辑在activiti-core这个模块里面,这就会导致流程处理在执行到该节点的时候报错,由于不在同一个ClassLoader下,所以无法找到该节点的处理类。

Caused by: org.activiti.engine.ActivitiClassLoadingException: Class not found: com.anke.activiti.plugin.prodef.jisan.UploadFileService
	at org.activiti.engine.impl.util.ReflectUtil.loadClass(ReflectUtil.java:87)
	at org.activiti.engine.impl.util.ReflectUtil.instantiate(ReflectUtil.java:134)
	... 122 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.anke.activiti.plugin.prodef.jisan.UploadFileService
	at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader.loadClass(TomcatEmbeddedWebappClassLoader.java:72)
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1188)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at org.activiti.engine.impl.util.ReflectUtil.loadClass(ReflectUtil.java:288)
	at org.activiti.engine.impl.util.ReflectUtil.loadClass(ReflectUtil.java:68)
	... 123 common frames omitted

四、解决问题

既然问题是plugin的ClassLoader和核心模块的ClassLoader不是同一个导致的,那么想办法把他们结合起来就可以把问题解决了。

Activiti 的 EngineConfiguration 类中可以设置ClassLoader,这就有了解决的方法。

我们自定义一个ClassLoader,首先找到当前模块中加载的所有的插件,找到它们对应的ClassLoader,然后这个classLoader在查找类的时候优先从父ClassLoader中查找,如果查找不到接着去这些个ClassLoader中查找,这样就能解决插件中的类无法找到的问题了。

下面是解决代码 (activiti-core)


import com.anke.activiti.domain.activiti.custom.CommonCustomFormType;
import com.anke.activiti.domain.activiti.custom.FileCustomFormType;
import org.activiti.engine.form.AbstractFormType;
import org.activiti.spring.SpringAsyncExecutor;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.AbstractProcessEngineAutoConfiguration;
import org.pf4j.PluginManager;
import org.pf4j.PluginWrapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Mengfly
 */
@Configuration
public class ActivityConfiguration extends AbstractProcessEngineAutoConfiguration {

    @Value("${spring.datasource.url}")
    private String url;
    @Value(("${spring.datasource.driver-class-name}"))
    private String driverClass;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;

    @Bean
    public SpringProcessEngineConfiguration springProcessEngineConfiguration(
            PlatformTransactionManager transactionManager, SpringAsyncExecutor springAsyncExecutor,
            PluginManager pluginManager) throws IOException {
        // 设置Activiti的数据源
        DataSource build = DataSourceBuilder.create()
                .url(url).username(username).password(password).driverClassName(driverClass)
                .build();
        SpringProcessEngineConfiguration springProcessEngineConfiguration =
                baseSpringProcessEngineConfiguration(build, transactionManager, springAsyncExecutor);
        // 设置流程引擎的ClassLoader为我们自定义的ClassLoader
        springProcessEngineConfiguration.setClassLoader(buildPluginClassLoader(pluginManager));

        List<AbstractFormType> customFormTypes = new ArrayList<>();
        customFormTypes.add(new FileCustomFormType());
        customFormTypes.add(new CommonCustomFormType());
        // 添加自定义的参数类型
        springProcessEngineConfiguration.setCustomFormTypes(customFormTypes);
        return springProcessEngineConfiguration;
    }

	/**
	 * 自定义的ClassLoader,查找类的流程如下:
	 * 1. 到父ClassLoader中查找
	 * 2. 如果查找失败继续到各个插件中去查找,如果各个插件中也查找不到抛出ClassNotFoundException
	 */
    private ClassLoader buildPluginClassLoader(PluginManager pluginManager) {
        List<PluginWrapper> plugins = pluginManager.getPlugins();
        List<ClassLoader> totallyClassLoaderList = plugins.stream().map(PluginWrapper::getPluginClassLoader).collect(Collectors.toList());

        return new ClassLoader() {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                // 先从 父 classloader 中查找类
                try {
                    return super.findClass(name);
                } catch (Exception e) {
                    // 如果父类中查找不到,从这些plugin中去查找
                    for (ClassLoader classLoader : totallyClassLoaderList) {
                        try {
                            return classLoader.loadClass(name);
                        } catch (Exception ignored) {
                            // ignore
                        }
                    }
                }
                throw new ClassNotFoundException("Can't found class " + name);
            }
        };

    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值