Ubuntu 22.04 安装 Apache UIMA 指南

在 Ubuntu 22.04 服务器上从零安装并运行 Apache UIMA 3.6.1:通过 apt 安装 OpenJDK 17 与 Maven,创建工程引入 uimaj-core,编写最小分析管线完成运行验证与报错排查。

Apache UIMAApache UIMA2026/10/12

Ubuntu 22.04 安装 Apache UIMA 指南

一、环境信息

本文所有命令与输出均在 Ubuntu 22.04 服务器上以 root 用户实测取得。

PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"

实测软件版本:

软件实测版本说明
OpenJDK17.0.20.1apt 仓库 openjdk-17-jdk-headless,UIMA 3.x 要求 JDK 11+
Maven3.6.3apt 仓库自带版本
uimaj-core3.6.1Maven Central 当前最新稳定版

二、前置条件

  1. 服务器可以访问公网 Maven Central 仓库(https://repo.maven.apache.org/maven2)。
  2. 拥有 root 权限。
  3. apt 源可用(默认软件源即可)。

三、版本选型

  • Apache UIMA:选用 Maven Central 上 org.apache.uima:uimaj-core 最新稳定版 3.6.1,属于当前 3.6.x 维护线。
  • JDK:UIMA 3.x 要求 JDK 11+,实测使用 apt 提供的 OpenJDK 17(17.0.20.1)。
  • Maven:Ubuntu 22.04 apt 仓库提供 3.6.3,实测可满足构建;但必须显式锁定 maven-compiler-plugin 版本(见 4.3 节),否则老插件会把 source/target 回退到 5 导致编译失败。如需更新版本可改用 Apache 官方二进制包,本文以 apt 路径为准。

四、部署步骤

4.1 安装 OpenJDK 17 与 Maven

apt-get update
apt-get install -y openjdk-17-jdk-headless maven

验证:

java -version
mvn -version

实测回显:

openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "6.8.0-138-generic", arch: "amd64", family: "unix"

apt 安装的 Maven 默认即使用 OpenJDK 17 运行,无需额外配置 JAVA_HOME。

4.2 设置 JVM 堆与 MAVEN_OPTS

新建 /etc/profile.d/uima-build.sh,把 Maven 进程堆限定为 256-512 MB:

cat > /etc/profile.d/uima-build.sh <<'EOF'
export MAVEN_OPTS="-Xms256m -Xmx512m"
EOF
chmod +x /etc/profile.d/uima-build.sh
source /etc/profile.d/uima-build.sh

MAVEN_OPTS 生效验证:构建期间另开终端执行 jps -v,实测回显:

4887 Launcher -Xms256m -Xmx512m -Dclassworlds.conf=/usr/share/maven/bin/m2.conf -Dmaven.home=/usr/share/maven -Dlibrary.jansi.path=/usr/share/maven/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/opt/uima-demo

4.3 创建 Maven 工程并引入 uimaj-core

mkdir -p /opt/uima-demo/src/main/java/com/example/uima /opt/uima-demo/descriptors /opt/uima-demo/data/in
cd /opt/uima-demo

创建 pom.xml。两个关键点:显式锁定 maven-compiler-plugin 3.13.0(apt 版 Maven 默认解析到 3.1,不支持 release 17);runtime 引入 slf4j-jdk14 消除 slf4j NOP 告警。

<?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>

4.4 编写最小 UIMA 管线

类型系统描述符 descriptors/TypeSystem.xml:

<?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>

注解器 src/main/java/com/example/uima/RoomNumberAnnotator.java:

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();
    }
  }
}

分析引擎描述符 descriptors/RoomNumberAnnotator.xml(configurationParameter 必须用子元素声明,属性写法会被解析器忽略):

<?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>

运行主类 src/main/java/com/example/uima/RunPipeline.java:

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.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/RoomNumberAnnotator.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.");

    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);

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

测试文本 data/in/room_notes.txt:

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.

4.5 JCas 代码生成与构建

先用 JCasGen 从 TypeSystem.xml 生成 JCas 绑定类(命令行入口类为 Jg):

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

执行前需先解析一次依赖把 jar 下载到本地仓库:mvn -B dependency:resolve。

生成结果实测:

src/main/java/com/example/uima/RoomNumber.java

再执行构建:

mvn -B package
mvn -B dependency:build-classpath -Dmdep.outputFile=target/cp.txt -q

4.6 运行验证

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

实测回显:

[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: RoomNumberAnnotator (version 1.0)
[cas] document length = 129
[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
[done] pipeline finished

java 进程堆参数用 jps -v 验证(进程存活窗口内执行):

7236 RunAE -Xms256m -Xmx512m

五、配置说明

  • 环境变量统一写入 /etc/profile.d/uima-build.sh(MAVEN_OPTS),重新登录自动生效。
  • Maven 本地仓库默认在 /root/.m2/repository,磁盘紧张时用 mvn -Dmaven.repo.local=/data/m2repo ... 指定其他位置。
  • JVM 堆:Maven 阶段走 MAVEN_OPTS,java 直跑阶段在命令行加 -Xms256m -Xmx512m。示例管线实际峰值远低于 512 MB。
  • 描述符 import location= 相对描述符文件所在目录解析;跨目录共享类型时建议统一放在同一 descriptors 目录。
  • 如系统存在多个 JDK,可用 update-alternatives --config java 切换默认 java;Maven 则由 JAVA_HOME 决定。

六、升级与多版本管理

  • 升级 UIMA:修改 pom.xml 的 <uima.version> 后重新执行 JCasGen 与 mvn -B package。3.x 线内 API 兼容性好;从 UIMA 2 升级需检查 JCas 与 API 弃用项。
  • 多版本 JDK:Ubuntu 可通过 apt install openjdk-21-jdk-headless 等并存多个版本,update-alternatives --config java 切换默认。
  • 换用新版 Maven:下载 Apache 官方二进制包解压到 /opt/apache-maven-3.9.x,PATH 中将其 bin 目录前置即可,apt 版无需卸载。
  • Maven Central 保留全部历史版本,降级只需改版本号重建。

七、注意事项

  1. apt 的 Maven 3.6.3 默认绑定的 maven-compiler-plugin 是 3.1,不识别 maven.compiler.release,必须显式锁定插件版本,否则报 Source option 5 is no longer supported。
  2. 用 java 直接运行 uimaj-tools 里的工具类时 classpath 至少包含 uimaj-tools、uimaj-core 与 slf4j-api 三个 jar,缺 slf4j-api 会报 NoClassDefFoundError: org/slf4j/Logger。
  3. JCasGen 无参数启动会尝试打开 Swing 图形界面,无图形环境抛 HeadlessException,务必使用命令行参数。
  4. 官方 GUI 工具(CAS Visual Debugger、Document Analyzer、CPM)在服务器无图形环境不可用,自动化场景请走 java 直跑或 API。
  5. Ubuntu 的 ANSI_X3.4-1968 平台编码(见 mvn -version 回显)不影响 UTF-8 源码编译,工程内已通过 project.build.sourceEncoding=UTF-8 固定。

八、常见问题

Q1:编译报 Source option 5 is no longer supported. Use 7 or later.?

实测定位:apt Maven 3.6.3 默认解析的 maven-compiler-plugin 3.1 忽略 maven.compiler.release 属性。解决:pom 中显式声明 maven-compiler-plugin 3.13.0 并配置 <release>17</release>(见 4.3 节)。

Q2:运行 JCasGen 报 NoClassDefFoundError: org/slf4j/Logger?

实测定位:Jg 类依赖 slf4j-api,手工拼 classpath 时容易漏掉。解决:把 $HOME/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar 加入 -cp;仅有 api 时会打印 SLF4J NOP 提示但功能正常,要日志可再加 slf4j-jdk14。

Q3:JCasGen 启动即抛 java.awt.HeadlessException?

实测定位:不带参数运行 Jg 会进入 GUI 模式。解决:命令行模式必须带 -jcasgeninput descriptors/TypeSystem.xml -jcasgenoutput src/main/java。

Q4:运行工程报 ClassNotFoundException: com.example.uima.RunPipeline?

实测定位:classpath 拼接错误或未执行 dependency:build-classpath 生成 target/cp.txt。解决:按 4.5 节重新生成 cp.txt,并用 -cp "target/classes:$(cat target/cp.txt)" 启动。

Q5:java -version 显示的不是 17?

执行 update-alternatives --config java 选择 openjdk-17 条目;或检查 JAVA_HOME 是否被其他 profile 覆盖。

九、卸载方式

apt-get remove -y maven openjdk-17-jdk-headless
apt-get autoremove -y
rm -f /etc/profile.d/uima-build.sh
rm -rf /opt/uima-demo
rm -rf /root/.m2

十、参考链接