Apache UIMA 快速入门与集成示例

以房间号抽取为例演示 Apache UIMA 3.6.1 快速入门:pom、TypeSystem、Annotator、描述符 XML、运行回显与 JCas 读回注解,扩展级联与 CPE 集成,输出均为实测。

Apache UIMAApache UIMA2026/10/12

Apache UIMA 快速入门与集成示例

一、项目结构总览

本章在 /opt/uima-demo 下构建一个完整的 UIMA 分析工程:从文本中抽取形如 9244-108 的房间号,聚合两个注解器,最后用 CPE 批量处理目录。实测环境:OpenJDK 17、uimaj-core 3.6.1(Maven Central 最新稳定版)。

/opt/uima-demo
├── pom.xml
├── descriptors/
│   ├── TypeSystem.xml              # 类型系统描述符
│   ├── RoomNumberAnnotator.xml     # 原语 AE 描述符(抽取)
│   ├── RoomNumberCounter.xml       # 原语 AE 描述符(统计)
│   ├── AggregateAE.xml             # 聚合 AE 描述符(级联)
│   ├── TextCollectionReader.xml    # Collection Reader 描述符
│   └── RoomNumberCPE.xml           # CPE 描述符(由代码生成)
├── data/in/room_notes.txt          # 测试文本
└── src/main/java/com/example/uima/
    ├── RoomNumber.java             # JCasGen 生成,勿手改
    ├── RoomNumberAnnotator.java    # 注解器:正则抽取
    ├── RoomNumberCounter.java      # 注解器:读回统计
    ├── TextCollectionReader.java   # 文本目录读取器
    ├── RunPipeline.java            # 运行入口:AE 管线
    └── RunCPE.java                 # 运行入口:CPE

二、pom.xml 全文

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>uima-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>17</maven.compiler.release>
    <uima.version>3.6.1</uima.version>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.13.0</version>
        <configuration>
          <release>17</release>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.apache.uima</groupId>
      <artifactId>uimaj-core</artifactId>
      <version>${uima.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.uima</groupId>
      <artifactId>uimaj-cpe</artifactId>
      <version>${uima.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.uima</groupId>
      <artifactId>uimaj-tools</artifactId>
      <version>${uima.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.uima</groupId>
      <artifactId>uimaj-json</artifactId>
      <version>${uima.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.7.36</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

四个 uimaj 构件的分工:uimaj-core 框架核心与 CAS/JCas;uimaj-cpe 集合处理引擎;uimaj-tools JCasGen 与 RunAE 工具;uimaj-json CAS-JSON 序列化(传递依赖 jackson-core 2.20.0)。

三、TypeSystem 与 JCas 代码生成

3.1 类型系统描述符

descriptors/TypeSystem.xml 声明自定义注解类型 com.example.uima.RoomNumber,继承 uima.tcas.Annotation,带一个 String 特征 pattern:

<?xml version="1.0" encoding="UTF-8"?>
<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
  <name>RoomNumber TypeSystem</name>
  <description>Demo type system declaring the RoomNumber annotation type</description>
  <version>1.0</version>
  <vendor>example</vendor>
  <types>
    <typeDescription>
      <name>com.example.uima.RoomNumber</name>
      <description>A room number such as 9244-108</description>
      <supertypeName>uima.tcas.Annotation</supertypeName>
      <features>
        <featureDescription>
          <name>pattern</name>
          <description>The raw matched pattern text</description>
          <rangeTypeName>uima.cas.String</rangeTypeName>
        </featureDescription>
      </features>
    </typeDescription>
  </types>
</typeSystemDescription>

3.2 生成 JCas 绑定类

cd /opt/uima-demo
mvn -B dependency:resolve
M2=~/.m2/repository/org/apache/uima
CP=$M2/uimaj-tools/3.6.1/uimaj-tools-3.6.1.jar:$M2/uimaj-core/3.6.1/uimaj-core-3.6.1.jar:$HOME/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar
java -cp $CP org.apache.uima.tools.jcasgen.Jg -jcasgeninput descriptors/TypeSystem.xml -jcasgenoutput src/main/java

生成的 RoomNumber.java 提供 getPattern/setPattern/getBegin/getEnd/getCoveredText 等强类型访问器。UIMA 3 的生成类不再包含 UIMA 2 时代的 _Type 伴生类。

四、Annotator 实现

4.1 抽取注解器 RoomNumberAnnotator

package com.example.uima;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.uima.UimaContext;
import org.apache.uima.analysis_component.JCasAnnotator_ImplBase;
import org.apache.uima.analysis_engine.AnalysisEngineProcessException;
import org.apache.uima.jcas.JCas;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.util.Level;

public class RoomNumberAnnotator extends JCasAnnotator_ImplBase {

  private Pattern pattern;

  @Override
  public void initialize(UimaContext context) throws ResourceInitializationException {
    super.initialize(context);
    String regex = (String) context.getConfigParameterValue("Pattern");
    if (regex == null || regex.isEmpty()) {
      regex = "(\\d{3,4})\\s*-\\s*\\d{1,4}";
    }
    pattern = Pattern.compile(regex);
    context.getLogger().log(Level.INFO, "RoomNumberAnnotator initialized, regex={0}", regex);
  }

  @Override
  public void process(JCas jcas) throws AnalysisEngineProcessException {
    Matcher matcher = pattern.matcher(jcas.getDocumentText());
    while (matcher.find()) {
      RoomNumber rn = new RoomNumber(jcas, matcher.start(), matcher.end());
      rn.setPattern(matcher.group());
      rn.addToIndexes();
    }
  }
}

要点:继承 JCasAnnotator_ImplBase;initialize 读取配置参数;process 里对每个命中创建 RoomNumber 并 addToIndexes()。

4.2 统计注解器 RoomNumberCounter

package com.example.uima;

import org.apache.uima.analysis_component.JCasAnnotator_ImplBase;
import org.apache.uima.analysis_engine.AnalysisEngineProcessException;
import org.apache.uima.jcas.JCas;

public class RoomNumberCounter extends JCasAnnotator_ImplBase {

  @Override
  public void process(JCas jcas) throws AnalysisEngineProcessException {
    int count = 0;
    StringBuilder values = new StringBuilder();
    for (RoomNumber rn : jcas.select(RoomNumber.class)) {
      count++;
      if (count > 1) {
        values.append(", ");
      }
      values.append(rn.getPattern());
    }
    System.out.printf("[counter] document length=%d, RoomNumber found=%d, values=[%s]%n",
            jcas.getDocumentText().length(), count, values);
  }
}

五、描述符 XML

5.1 原语 AE:RoomNumberAnnotator.xml

<?xml version="1.0" encoding="UTF-8"?>
<analysisEngineDescription xmlns="http://uima.apache.org/resourceSpecifier">
  <frameworkImplementation>org.apache.uima.java</frameworkImplementation>
  <primitive>true</primitive>
  <annotatorImplementationName>com.example.uima.RoomNumberAnnotator</annotatorImplementationName>
  <analysisEngineMetaData>
    <name>RoomNumberAnnotator</name>
    <description>Finds room numbers such as 9244-108 with a configurable regex</description>
    <version>1.0</version>
    <vendor>example</vendor>
    <configurationParameters searchStrategy="language_fallback">
      <configurationParameter>
        <name>Pattern</name>
        <description>Regular expression for room numbers</description>
        <type>String</type>
        <multiValued>false</multiValued>
        <mandatory>false</mandatory>
      </configurationParameter>
    </configurationParameters>
    <configurationParameterSettings>
      <nameValuePair>
        <name>Pattern</name>
        <value><string>(\d{3,4})\s*-\s*\d{1,4}</string></value>
      </nameValuePair>
    </configurationParameterSettings>
    <typeSystemDescription>
      <imports><import location="TypeSystem.xml"/></imports>
    </typeSystemDescription>
    <capabilities>
      <capability>
        <inputs/>
        <outputs>
          <type allAnnotatorFeatures="true">com.example.uima.RoomNumber</type>
        </outputs>
        <languagesSupported><language>en</language></languagesSupported>
      </capability>
    </capabilities>
    <operationalProperties>
      <modifiesCas>true</modifiesCas>
      <multipleDeploymentAllowed>true</multipleDeploymentAllowed>
      <outputsNewCASes>false</outputsNewCASes>
    </operationalProperties>
  </analysisEngineMetaData>
</analysisEngineDescription>

RoomNumberCounter.xml 结构相同(implementationName 换为 com.example.uima.RoomNumberCounter,capability 中 RoomNumber 声明在 inputs 而非 outputs)。

5.2 聚合 AE:AggregateAE.xml

<?xml version="1.0" encoding="UTF-8"?>
<analysisEngineDescription xmlns="http://uima.apache.org/resourceSpecifier">
  <frameworkImplementation>org.apache.uima.java</frameworkImplementation>
  <primitive>false</primitive>
  <delegateAnalysisEngineSpecifiers>
    <delegateAnalysisEngine key="RoomNumberAnnotator">
      <import location="RoomNumberAnnotator.xml"/>
    </delegateAnalysisEngine>
    <delegateAnalysisEngine key="RoomNumberCounter">
      <import location="RoomNumberCounter.xml"/>
    </delegateAnalysisEngine>
  </delegateAnalysisEngineSpecifiers>
  <analysisEngineMetaData>
    <name>RoomNumber Aggregate AE</name>
    <description>Aggregate AE: RoomNumberAnnotator followed by RoomNumberCounter</description>
    <version>1.0</version>
    <vendor>example</vendor>
    <configurationParameters searchStrategy="language_fallback"/>
    <configurationParameterSettings/>
    <capabilities>
      <capability>
        <inputs/>
        <outputs>
          <type allAnnotatorFeatures="true">com.example.uima.RoomNumber</type>
        </outputs>
        <languagesSupported><language>en</language></languagesSupported>
      </capability>
    </capabilities>
    <operationalProperties>
      <modifiesCas>true</modifiesCas>
      <multipleDeploymentAllowed>true</multipleDeploymentAllowed>
      <outputsNewCASes>false</outputsNewCASes>
    </operationalProperties>
    <flowConstraints>
      <fixedFlow>
        <node>RoomNumberAnnotator</node>
        <node>RoomNumberCounter</node>
      </fixedFlow>
    </flowConstraints>
  </analysisEngineMetaData>
</analysisEngineDescription>

三条实测得出的硬规则:flowConstraints 必须在 analysisEngineMetaData 内;<node> 用文本承载 key(不是 key 属性);聚合 AE 不得写 <typeSystemDescription>。

六、运行与回显

6.1 运行入口 RunPipeline

package com.example.uima;

import java.io.File;

import org.apache.uima.UIMAFramework;
import org.apache.uima.analysis_engine.AnalysisEngine;
import org.apache.uima.jcas.JCas;
import org.apache.uima.json.JsonCasSerializer;
import org.apache.uima.json.impl.JsonContentHandlerJacksonWrapper;
import org.apache.uima.resource.ResourceSpecifier;
import org.apache.uima.util.XMLInputSource;

public class RunPipeline {

  public static void main(String[] args) throws Exception {
    System.out.printf("[heap] effective max heap = %.0f MB%n",
            Runtime.getRuntime().maxMemory() / 1048576.0);

    String aePath = args.length > 0 ? args[0] : "descriptors/AggregateAE.xml";

    XMLInputSource xmlInput = new XMLInputSource(new File(aePath));
    ResourceSpecifier specifier = UIMAFramework.getXMLParser().parseResourceSpecifier(xmlInput);
    AnalysisEngine ae = UIMAFramework.produceAnalysisEngine(specifier);

    JCas jcas = ae.newJCas();
    jcas.setDocumentText("The seminar is in room 9244-108 and the workshop is in 3116-2. "
            + "Overflow seating is available in 2244-3A. The lab moved to 5117-C.");

    ae.process(jcas);

    int count = 0;
    for (RoomNumber rn : jcas.select(RoomNumber.class)) {
      count++;
      System.out.printf("[jcas] RoomNumber #%d begin=%d end=%d covered=\"%s\" pattern=\"%s\"%n",
              count, rn.getBegin(), rn.getEnd(), rn.getCoveredText(), rn.getPattern());
    }
    System.out.println("[jcas] total RoomNumber = " + count);

    StringWriter sw = new StringWriter();
    new JsonCasSerializer().serialize(jcas.getCas(), new JsonContentHandlerJacksonWrapper(sw));
    System.out.println("[json] " + sw);

    ae.collectionProcessComplete();
    ae.destroy();
    System.out.println("[done] pipeline finished");
  }
}

(StringWriter 需 import java.io.StringWriter;)

6.2 构建并运行

mvn -B package
mvn -B dependency:build-classpath -Dmdep.outputFile=target/cp.txt -q
java -Xms256m -Xmx512m -cp "target/classes:$(cat target/cp.txt)" com.example.uima.RunPipeline

实测回显([json] 行仅摘录首尾,中段以 ... 略):

[heap] effective max heap = 512 MB
Oct 12, 2026 2:46:35 PM com.example.uima.RoomNumberAnnotator initialize(29)
INFO: RoomNumberAnnotator initialized, regex=(\d{3,4})\s*-\s*\d{1,4}
[ae] produced: RoomNumber Aggregate AE (version 1.0)
[cas] document length = 129
[counter] document length=129, RoomNumber found=3, values=[9244-108, 3116-2, 2244-3]
[jcas] RoomNumber #1 begin=23 end=31 covered="9244-108" pattern="9244-108"
[jcas] RoomNumber #2 begin=55 end=61 covered="3116-2" pattern="3116-2"
[jcas] RoomNumber #3 begin=96 end=102 covered="2244-3" pattern="2244-3"
[jcas] total RoomNumber = 3
[json] {"_context":{"_types":{"DocumentAnnotation":{...},"RoomNumber":{"_id":"com.example.uima.RoomNumber",...},...}},
 "_views":{"_InitialView":{"RoomNumber":[{"sofa":1,"begin":23,"end":31,"pattern":"9244-108"},
 {"sofa":1,"begin":55,"end":61,"pattern":"3116-2"},{"sofa":1,"begin":96,"end":102,"pattern":"2244-3"}]}},...}
[done] pipeline finished

结果解读:级联顺序正确(先抽取后统计);2244-3A 按默认正则命中前缀 2244-3,5117-C 因 - 后无数字不命中——正则行为符合预期;CAS-JSON 由 uimaj-json 输出,含类型系统上下文与视图数据。

6.3 JCas 读回注解

读回即 6.1 中的 select 循环:

for (RoomNumber rn : jcas.select(RoomNumber.class)) {
    rn.getBegin(); rn.getEnd(); rn.getCoveredText(); rn.getPattern();
}

jcas.select(Class) 返回 SelectFSs 流式集合(UIMA 3 内置于 uimaj-core,无需 uimaFIT),支持 .count()、.at(typePriority) 等链式操作。

6.4 配置参数运行期覆盖(实测)

ae.setConfigParameterValue("Pattern", "2244-3A");
ae.reconfigure();

实测输出(只命中字面量):

[ae] parameter override applied: Pattern="2244-3A"
[jcas] RoomNumber #1 begin=96 end=103 covered="2244-3A" pattern="2244-3A"
[jcas] total RoomNumber = 1

注意:produceAnalysisEngine(specifier, params) 的 Map 实测不会注入配置参数;聚合 AE 上直接 setConfigParameterValue 实测也不会下发到委托组件,覆盖以委托描述符或聚合声明同名参数为准。

七、扩展:多 Annotator 级联与 CPE 集成

7.1 文本目录读取器 TextCollectionReader

package com.example.uima;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.uima.cas.CAS;
import org.apache.uima.collection.CollectionException;
import org.apache.uima.collection.CollectionReader_ImplBase;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.util.Progress;
import org.apache.uima.util.ProgressImpl;

public class TextCollectionReader extends CollectionReader_ImplBase {

  public static final String PARAM_INPUT_DIRECTORY = "InputDirectory";

  private File[] files;
  private int index;

  @Override
  public void initialize() throws ResourceInitializationException {
    String dirName = (String) getConfigParameterValue(PARAM_INPUT_DIRECTORY);
    if (dirName == null) {
      throw new ResourceInitializationException(
              ResourceInitializationException.CONFIG_SETTING_ABSENT,
              new Object[] { PARAM_INPUT_DIRECTORY });
    }
    File dir = new File(dirName);
    File[] all = dir.listFiles();
    List<File> txtFiles = new ArrayList<>();
    if (all != null) {
      Arrays.sort(all);
      for (File f : all) {
        if (f.isFile() && f.getName().endsWith(".txt")) {
          txtFiles.add(f);
        }
      }
    }
    files = txtFiles.toArray(new File[0]);
    index = 0;
  }

  @Override
  public void getNext(CAS cas) throws IOException, CollectionException {
    File file = files[index++];
    cas.setDocumentText(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8));
    cas.setDocumentLanguage("en");
  }

  @Override
  public boolean hasNext() throws IOException, CollectionException {
    return index < files.length;
  }

  @Override
  public Progress[] getProgress() {
    return new Progress[] { new ProgressImpl(index, files.length, Progress.ENTITIES) };
  }

  @Override
  public void close() throws IOException {
  }
}

注意 getNext 的参数类型是 CAS(实测按 JCas 覆写会编译失败)。配套描述符 TextCollectionReader.xml 与 5.1 节同构,根元素为 collectionReaderDescription,声明 mandatory 的 InputDirectory 参数并给默认值 data/in。

7.2 CPE 运行入口 RunCPE

package com.example.uima;

import java.io.File;
import java.io.FileOutputStream;
import java.util.Collections;

import org.apache.uima.UIMAFramework;
import org.apache.uima.cas.CAS;
import org.apache.uima.collection.CollectionProcessingEngine;
import org.apache.uima.collection.EntityProcessStatus;
import org.apache.uima.collection.StatusCallbackListener;
import org.apache.uima.collection.impl.metadata.cpe.CpeDescriptorFactory;
import org.apache.uima.collection.metadata.CpeCasProcessor;
import org.apache.uima.collection.metadata.CpeDescription;

public class RunCPE {

  public static void main(String[] args) throws Exception {
    CpeDescription cpeDesc = CpeDescriptorFactory.produceDescriptor();
    cpeDesc.addCollectionReader("descriptors/TextCollectionReader.xml");

    CpeCasProcessor annotator = CpeDescriptorFactory.produceCasProcessor("RoomNumberAnnotator");
    annotator.setDescriptor("descriptors/RoomNumberAnnotator.xml");
    cpeDesc.addCasProcessor(annotator);
    cpeDesc.setNumToProcess(-1);

    File cpeFile = new File("descriptors/RoomNumberCPE.xml");
    try (FileOutputStream out = new FileOutputStream(cpeFile)) {
      cpeDesc.toXML(out);
    }
    System.out.println("[cpe] generated CPE descriptor: " + cpeFile.getAbsolutePath());

    CollectionProcessingEngine engine =
            UIMAFramework.produceCollectionProcessingEngine(cpeDesc, Collections.emptyMap());

    engine.addStatusCallbackListener(new StatusCallbackListener() {
      @Override
      public void entityProcessComplete(CAS cas, EntityProcessStatus status) {
        int errors = status.isException() ? status.getExceptions().size() : 0;
        System.out.printf("[cpe] document processed, length=%d, errors=%d%n",
                cas.getDocumentText().length(), errors);
      }

      @Override
      public void collectionProcessComplete() {
        System.out.println("[cpe] collectionProcessComplete - finished");
        System.exit(0);
      }

      @Override
      public void initializationComplete() {
        System.out.println("[cpe] initializationComplete - CPE is running");
      }

      @Override
      public void batchProcessComplete() {
      }

      @Override
      public void paused() {
      }

      @Override
      public void resumed() {
      }

      @Override
      public void aborted() {
        System.exit(1);
      }
    });

    engine.process();
  }
}

运行:

java -Xms256m -Xmx512m -cp "target/classes:$(cat target/cp.txt)" com.example.uima.RunCPE

实测回显:

[cpe] generated CPE descriptor: /opt/uima-demo/descriptors/RoomNumberCPE.xml
INFO: RoomNumberAnnotator initialized, regex=(\d{3,4})\s*-\s*\d{1,4}
[reader] input directory=/opt/uima-demo/data/in, files found=1
[cpe] initializationComplete - CPE is running
[cpe] document processed, length=130, errors=0
[cpe] collectionProcessComplete - finished

代码同时把规范化的 CPE 描述符落盘为 descriptors/RoomNumberCPE.xml(含 casProcessors 的 errorHandling/checkpoint 与 cpeConfig 的 numToProcess/deployAs 等),后续可手工编辑后用 UIMAFramework.getXMLParser().parseCpeDescription(new XMLInputSource(...)) 加载复用。

八、JVM 堆限定与验证

启动脚本统一限定堆 256-512 MB:

java -Xms256m -Xmx512m -cp "target/classes:$(cat target/cp.txt)" com.example.uima.RunPipeline

三层实测验证:

  1. 程序内打印(RunPipeline 第一行输出):
[heap] effective max heap = 512 MB
  1. 运行期 jps -v(实测回显):
7236 RunAE -Xms256m -Xmx512m
  1. Maven 构建阶段的堆由 MAVEN_OPTS 控制(写入 /etc/profile.d/uima-build.sh),构建期间 jps -v 实测:
4887 Launcher -Xms256m -Xmx512m -Dclassworlds.conf=/usr/share/maven/bin/m2.conf ...

本工程实测峰值远低于 512 MB,256-512 MB 区间可稳定完成构建与运行。

九、常见问题

Q1:编译报 cannot find symbol: class RoomNumber?

实测原因:跳过了 JCasGen,工程里没有生成的 JCas 类。解决:先执行 3.2 节的 Jg 命令生成 RoomNumber.java,再 mvn -B package。

Q2:初始化报 No configuration parameter with name "Pattern" is declared?

实测原因:描述符里 configurationParameter 用属性(name=、type=)声明被解析器忽略。解决:改为 <name>/<type>/<multiValued>/<mandatory> 子元素写法。

Q3:聚合 AE 报 The key "" was used in the flow 或 unknown element type: flowConstraints?

实测两条对应修法:flowConstraints 移入 analysisEngineMetaData 内部;fixedFlow 节点用 <node>RoomNumberAnnotator</node> 文本形式而非 key 属性。

Q4:produceAnalysisEngine 传 Map 覆盖参数不生效?

实测确认该 Map 不用于配置参数注入。解决:produce 之后再 ae.setConfigParameterValue("Pattern", ...) 加 ae.reconfigure()。

Q5:日志抛 unknown format type: 4?

实测原因:UIMA Logger 把消息串当 MessageFormat 模板,正则中的 {3,4} 触发解析错误。解决:用 log(Level.INFO, "...{0}", regex) 参数化写法。

Q6:getNext 覆写报"method does not override"?

实测确认 CollectionReader 接口签名是 getNext(CAS cas),不是 getNext(JCas)。解决:参数改为 CAS,需要 JCas 视图时再 cas.getJCas()。

Q7:CPE 回调编译报 does not override abstract method aborted()?

实测确认 StatusCallbackListener 除 entityProcessComplete 外还必须实现 initializationComplete/batchProcessComplete/collectionProcessComplete/paused/resumed/aborted。补齐全部七个方法即可。

十、参考链接