Apache Neethi 3.2.2 集成使用实战详解

以 Maven 项目实战 Apache Neethi 3.2.2:解析 WS-Policy、合并与规范化、注册表管理与自定义断言构建器,全部代码与回显实测通过。

Apache NeethiNeethiApacheJava2026/10/15

Apache Neethi 3.2.2 集成使用实战详解

目标:在 Ubuntu 22.04 上以 Maven 项目集成 Neethi 3.2.2,完成策略解析、合并规范化、注册表管理与自定义断言构建器四个可运行示例。已在 Ubuntu 22.04(OpenJDK 17)实测通过。

一、环境信息

项目值
操作系统Ubuntu 22.04.5 LTS
系统架构x86_64(amd64)
JDKOpenJDK 17.0.20.1
MavenApache Maven 3.6.3
组件版本Apache Neethi 3.2.2、axiom-api/axiom-impl 1.3.0
项目目录/opt/neethi-demo
策略文件目录/opt/neethi-demo/src/main/resources

表后逐项说明:Neethi 对 JDK 无硬性要求,本实验按 17 编译运行;策略文件与代码同仓库放置,便于版本管理;实验 JVM 统一 -Xms256m -Xmx256m。

二、依赖引入

2.1 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>neethi-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.neethi</groupId>
      <artifactId>neethi</artifactId>
      <version>3.2.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.ws.commons.axiom</groupId>
      <artifactId>axiom-api</artifactId>
      <version>1.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.ws.commons.axiom</groupId>
      <artifactId>axiom-impl</artifactId>
      <version>1.3.0</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

逐项说明:neethi 官方 POM 将 axiom 三件套声明为 provided,不会传递,业务工程必须显式引入 axiom-api(compile)与 axiom-impl(runtime,内含 woodstox StAX 实现);漏引 axiom-api 时编译报 package org.apache.axiom.om does not exist(实测)。

2.2 编译与依赖树

cd /opt/neethi-demo
mvn compile
mvn dependency:tree

实测回显(dependency:tree 节选):

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ neethi-demo ---
[INFO] com.example:neethi-demo:jar:1.0.0
[INFO] +- org.apache.neethi:neethi:jar:3.2.2:compile
[INFO] +- org.apache.ws.commons.axiom:axiom-api:jar:1.3.0:compile
[INFO] |  +- commons-logging:commons-logging:jar:1.2:compile
[INFO] |  +- jaxen:jaxen:jar:1.2.0:compile
[INFO] |  +- org.apache.james:apache-mime4j-core:jar:0.8.4:compile
[INFO] |  \- jakarta.activation:jakarta.activation-api:jar:1.2.1:compile
[INFO] \- org.apache.ws.commons.axiom:axiom-impl:jar:1.3.0:runtime
[INFO]    \- com.fasterxml.woodstox:woodstox-core:jar:6.2.6:runtime
[INFO]       \- org.codehaus.woodstox:stax2-api:jar:4.2.1:runtime
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

生成 classpath 供 java 直接运行:

mvn dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt -q

三、示例一:解析 WS-Policy 文档

3.1 策略文件 policy1.xml(两种安全方案可选)

<wsp:Policy xmlns:wsp="http://www.w3.org/ns/ws-policy"
            xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"
            xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
            wsu:Id="P1">
  <wsp:ExactlyOne>
    <wsp:All>
      <sp:TransportBinding wsp:Optional="false"/>
      <sp:IncludeTimestamp/>
      <sp:SignedSupportingTokens/>
    </wsp:All>
    <wsp:All>
      <sp:AsymmetricBinding/>
      <sp:EncryptedParts/>
    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

逐项说明:wsu:Id 给策略命名(解析后 Policy.getId() 可取到 P1);ExactlyOne 表示两种安全方案二选一;第一条 All 是"传输绑定 + 时间戳 + 签名令牌"组合,第二条是"非对称绑定 + 加密部分"组合。

3.2 业务代码 PolicyParseDemo.java

package com.example;

import org.apache.neethi.Policy;
import org.apache.neethi.PolicyEngine;
import org.apache.neethi.Assertion;
import org.apache.neethi.builders.PrimitiveAssertion;

import java.io.FileInputStream;
import java.util.List;

public class PolicyParseDemo {
    public static void main(String[] args) throws Exception {
        Policy policy = PolicyEngine.getPolicy(new FileInputStream("src/main/resources/policy1.xml"));
        System.out.println("policy id  = " + policy.getId());
        System.out.println("policy name = " + policy.getName());
        System.out.println("isEmpty = " + policy.isEmpty());
        int alt = 0;
        for (java.util.Iterator<List<Assertion>> it = policy.getAlternatives(); it.hasNext(); ) {
            List<Assertion> alternative = it.next();
            alt++;
            System.out.println("---- alternative " + alt + " ----");
            for (Assertion a : alternative) {
                if (a instanceof PrimitiveAssertion pa) {
                    System.out.println("assertion: " + pa.getName()
                            + " optional=" + pa.isOptional()
                            + " ignorable=" + pa.isIgnorable()
                            + " text=" + pa.getTextValue());
                } else {
                    System.out.println("composite: " + a.getClass().getSimpleName() + " name=" + a.getName());
                }
            }
        }
        System.out.println("total alternatives = " + alt);
    }
}

逐项说明:PolicyEngine.getPolicy(InputStream) 一步完成解析建树;getAlternatives() 逐条展开可选方案;断言是 PrimitiveAssertion 时可取 QName/optional/ignorable/textValue;Policy 未实现 Iterable,必须用 Iterator 显式循环(实测 for-each 报错)。

3.3 编译运行

LC_ALL=C.UTF-8 java -Xms256m -Xmx256m -cp "target/classes:$(cat /tmp/cp.txt)" com.example.PolicyParseDemo

实测回显:

policy id  = P1
policy name = null
isEmpty = false
---- alternative 1 ----
assertion: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}TransportBinding optional=false ignorable=false text=
assertion: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}IncludeTimestamp optional=false ignorable=false text=
assertion: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}SignedSupportingTokens optional=false ignorable=false text=
---- alternative 2 ----
assertion: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}AsymmetricBinding optional=false ignorable=false text=
assertion: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}EncryptedParts optional=false ignorable=false text=
total alternatives = 2

逐项说明:id 来自 wsu:Id 属性,name 未设置(null);ExactlyOne 下两条 All 各展开为一个 alternative,共 2 条;断言打印为标准 QName 格式 {命名空间}本地名。

四、示例二:合并与规范化(merge/normalize/serialize)

4.1 策略文件 policy2.xml(QoS 诉求,含可选断言)

<wsp:Policy xmlns:wsp="http://www.w3.org/ns/ws-policy"
            xmlns:qos="http://example.org/qos"
            xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
            wsu:Id="P2">
  <wsp:ExactlyOne>
    <wsp:All>
      <qos:ReliableMessaging/>
      <qos:Compression wsp:Optional="true"/>
      <qos:Auditing wsp:Optional="true"/>
    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

逐项说明:Compression 与 Auditing 是 optional 断言——语义为"可要可不要",规范化时各自展开为"有/无"两个分支。

4.2 业务代码 PolicyMergeDemo.java

package com.example;

import org.apache.neethi.Policy;
import org.apache.neethi.PolicyEngine;
import org.apache.neethi.Assertion;

import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import java.io.FileInputStream;
import java.io.StringWriter;
import java.util.List;

public class PolicyMergeDemo {
    public static void main(String[] args) throws Exception {
        Policy p1 = PolicyEngine.getPolicy(new FileInputStream("src/main/resources/policy1.xml"));
        Policy p2 = PolicyEngine.getPolicy(new FileInputStream("src/main/resources/policy2.xml"));
        System.out.println("p1 alternatives = " + count(p1) + ", p2 alternatives = " + count(p2));

        Policy merged = p1.merge(p2);
        System.out.println("merged alternatives (not normalized) = " + count(merged));
        Policy normalized = merged.normalize(true);
        System.out.println("normalized alternatives = " + count(normalized));

        StringWriter sw = new StringWriter();
        XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(sw);
        writer.writeStartDocument("UTF-8", "1.0");
        writer.writeCharacters("\n");
        normalized.serialize(writer);
        writer.writeEndDocument();
        writer.flush();
        System.out.println("---- normalized policy XML ----");
        System.out.println(sw.toString());
    }

    static int count(Policy p) {
        int n = 0;
        for (java.util.Iterator<List<Assertion>> it = p.getAlternatives(); it.hasNext(); ) { it.next(); n++; }
        return n;
    }
}

逐项说明:merge 产生组合策略;normalize(true) 深度展开为正则形;serialize 把规范化结果写回 XML(输出单行,可在日志层做换行美化)。

4.3 运行

LC_ALL=C.UTF-8 java -Xms256m -Xmx256m -cp "target/classes:$(cat /tmp/cp.txt)" com.example.PolicyMergeDemo 2>&1 | head -8

实测回显(计数部分):

p1 alternatives = 2, p2 alternatives = 1
merged alternatives (not normalized) = 2
normalized alternatives = 8

规范化输出的第一条 alternative(节选,完整为单行 XML):

<?xml version='1.0' encoding='UTF-8'?>
<wsp:Policy xmlns:wsp="http://www.w3.org/ns/ws-policy"><wsp:ExactlyOne><wsp:All><sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"></sp:TransportBinding><sp:IncludeTimestamp xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"></sp:IncludeTimestamp><sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"></sp:SignedSupportingTokens><qos:ReliableMessaging xmlns:qos="http://example.org/qos"></qos:ReliableMessaging><qos:Compression xmlns:qos="http://example.org/qos"></qos:Compression><qos:Auditing xmlns:qos="http://example.org/qos"></qos:Auditing></wsp:All><wsp:All>...

逐项说明:数学关系为 2(P1 方案数)× 4(P2 两个 optional 断言的 2×2 展开)= 8 条 alternative;未规范化时 merge 只显示外层 2 条(P2 仍嵌在组合内);序列化把正则形完整写出,包含 8 个 wsp:All。

五、示例三:注册表(PolicyRegistry)

5.1 业务代码 PolicyRegistryDemo.java

package com.example;

import org.apache.neethi.Policy;
import org.apache.neethi.PolicyEngine;
import org.apache.neethi.PolicyRegistry;
import org.apache.neethi.PolicyRegistryImpl;

import java.io.FileInputStream;

public class PolicyRegistryDemo {
    public static void main(String[] args) throws Exception {
        Policy p1 = PolicyEngine.getPolicy(new FileInputStream("src/main/resources/policy1.xml"));
        Policy p2 = PolicyEngine.getPolicy(new FileInputStream("src/main/resources/policy2.xml"));

        PolicyRegistry registry = new PolicyRegistryImpl();
        registry.register("service-security", p1);
        registry.register("service-qos", p2);

        Policy security = registry.lookup("service-security");
        Policy qos = registry.lookup("service-qos");
        System.out.println("lookup service-security -> id = " + security.getId());
        System.out.println("lookup service-qos      -> id = " + qos.getId());

        Policy missing = registry.lookup("not-exist");
        System.out.println("lookup not-exist -> " + missing);

        registry.remove("service-qos");
        Policy removed = registry.lookup("service-qos");
        System.out.println("after remove, lookup service-qos -> " + removed);

        registry.register("service-security", p1);
        System.out.println("register same key again OK, id = " + registry.lookup("service-security").getId());
    }
}

5.2 运行

LC_ALL=C.UTF-8 java -Xms256m -Xmx256m -cp "target/classes:$(cat /tmp/cp.txt)" com.example.PolicyRegistryDemo 2>&1 | grep -v QosAssertionBuilder

实测回显:

lookup service-security -> id = P1
lookup service-qos      -> id = P2
lookup not-exist -> null
after remove, lookup service-qos -> null
register same key again OK, id = P1

逐项说明:lookup 未命中返回 null(不抛异常),调用方必须判空;remove 生效后同样返回 null;重复 register 同名键直接覆盖旧值;接口只有 register/lookup/remove 三个方法,无枚举键集合的能力。

六、进阶用法:自定义断言构建器

6.1 构建器实现 QosAssertionBuilder.java

package com.example;

import org.apache.neethi.Assertion;
import org.apache.neethi.AssertionBuilderFactory;
import org.apache.neethi.builders.xml.XMLPrimitiveAssertionBuilder;
import org.w3c.dom.Element;

import javax.xml.namespace.QName;

public class QosAssertionBuilder extends XMLPrimitiveAssertionBuilder {
    public static final QName RELIABLE = new QName("http://example.org/qos", "ReliableMessaging");
    public static final QName COMPRESSION = new QName("http://example.org/qos", "Compression");
    public static final QName AUDITING = new QName("http://example.org/qos", "Auditing");

    @Override
    public Assertion build(Element element, AssertionBuilderFactory factory) throws IllegalArgumentException {
        System.out.println("[QosAssertionBuilder] building assertion for " + getQName(element));
        return super.build(element, factory);
    }

    @Override
    public QName[] getKnownElements() {
        return new QName[]{RELIABLE, COMPRESSION, AUDITING};
    }
}

逐项说明:继承内建 XMLPrimitiveAssertionBuilder(AssertionBuilder<Element> 实现),只拦截回调并复用父类的属性/文本解析;getKnownElements() 声明可处理的 QName 数组(注意是数组不是 List,实测接口签名)。

6.2 注册文件(ServiceLoader)

mkdir -p src/main/resources/META-INF/services
echo 'com.example.QosAssertionBuilder' > src/main/resources/META-INF/services/org.apache.neethi.builders.AssertionBuilder

说明:文件名必须是构建器接口全名 org.apache.neethi.builders.AssertionBuilder,内容为实现类全名,框架经 java.util.ServiceLoader 在首次解析时加载。

6.3 运行验证

解析 policy2.xml(含 qos 命名空间断言)时,每条断言触发一次回调(实测,PolicyMergeDemo 与 PolicyRegistryDemo 输出的前几行):

[QosAssertionBuilder] building assertion for {http://example.org/qos}ReliableMessaging
[QosAssertionBuilder] building assertion for {http://example.org/qos}Compression
[QosAssertionBuilder] building assertion for {http://example.org/qos}Auditing

说明:回调发生在断言构建阶段,构建结果仍是 PrimitiveAssertion(父类默认行为),业务可在 build 里返回携带结构化信息的自定义 Assertion 实现。

6.4 嵌套策略解析(Bonus)

策略元素内再嵌 wsp:Policy 时(如 WS-SecurityPolicy 的 TransportBinding),解析产物为 PolicyContainingPrimitiveAssertion,可逐层下钻:

top assertion: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}TransportBinding class=PolicyContainingPrimitiveAssertion
  nested: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}TransportToken class=PolicyContainingPrimitiveAssertion
  nested: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}IncludeTimestamp class=PrimitiveAssertion

说明:判断类型用 a instanceof PolicyContainingAssertion 后调用 getPolicy();嵌套深度不限。

七、常见问题(FAQ)

Q1:mvn compile 报 package org.apache.axiom.om does not exist? A:Neethi 官方 POM 把 AXIOM 声明为 provided 不传递。在业务 pom 显式加 org.apache.ws.commons.axiom:axiom-api:1.3.0(compile)与 axiom-impl:1.3.0(runtime),实测编译通过。

Q2:运行时报 woodstox/stax 相关 ClassNotFound? A:axiom-impl 是 runtime 依赖负责落地 StAX 实现。检查 dependency:tree 是否存在 com.fasterxml.woodstox:woodstox-core:6.2.6(实测存在),缺失说明 axiom-impl 没引或被 exclusion 排除。

Q3:for-each Policy 报 for-each not applicable to expression type? A:3.2.2 的 Policy 未实现 Iterable。用 getAlternatives() 拿 Iterator<List<Assertion>> 显式迭代(实测编译错误信息即如此),每个元素是一条 alternative。

Q4:PolicyEngine.getPolicy(InputStream) 需要调用方关闭流吗? A:需要。框架不代管外部流的生命周期,try-with-resources 是标准做法;本实验代码为聚焦策略语义省略了关闭,生产代码请补齐。

Q5:merge 后的 Policy 还能继续 merge 吗? A:可以,merge 返回新 Policy 对象且操作是链式的。但连续 merge 后建议在每个对外比较/序列化节点前 normalize(true),避免嵌套结构干扰语义判断(实测未规范化时 alternatives 计数偏低)。

Q6:自定义构建器没被调用怎么办? A:三查:services 文件名是否为 org.apache.neethi.builders.AssertionBuilder(接口全名,2.x 与 3.x 接口包名不同);实现类是否在运行 classpath;断言 QName 是否在 getKnownElements() 声明内。实测三条件齐备后每条 qos 断言都触发回调。

Q7:断言的属性和文本怎么读? A:PrimitiveAssertion.getAttribute(QName)/getAttributes() 读属性表,getTextValue() 读文本内容;XMLPrimitiveAssertionBuilder.build 已自动搬运元素属性与文本(实测 policy1.xml 中 wsp:Optional 属性反映在 isOptional())。

Q8:如何把 Policy 写回文件? A:Policy.serialize(XMLStreamWriter) 输出到任意 Writer;输出是紧凑单行,可用 StAX 的缩进包装或自行按 >< 替换美化。实测序列化含完整命名空间声明(xmlns:wsp、xmlns:sp、xmlns:qos)。

Q9:多个策略文件里 wsu:Id 相同会冲突吗? A:Neethi 不做全局去重,Id 只在单个解析产物上有意义;跨文档引用走 wsp:PolicyReference + PolicyRegistry(调用方自行解析引用并注入 registry),Id 冲突由使用方约束。

Q10:JVM 内存要给多少? A:策略文档通常 KB 级,256MB 堆绰绰有余(本实验全部 demo 用 -Xms256m -Xmx256m 运行,jps -v 可见该参数)。只有在解析超大 WSDL 聚合策略时才需要评估加大。

八、参考链接

  1. Apache Neethi 官方主页(含 API 文档链接):https://neethi.apache.org/
  2. Neethi 3.2.2 构件与 POM:https://repo.maven.apache.org/maven2/org/apache/neethi/neethi/3.2.2/
  3. Neethi 源码(含 builders/converters):https://github.com/apache/neethi
  4. WS-Policy 1.5 标准(框架语义与合并算子):https://www.w3.org/TR/ws-policy/
  5. WS-PolicyAttachment(Optional/Ignorable 属性定义):https://www.w3.org/Submission/WS-PolicyAttachment/
  6. Apache AXIOM 用户指南:https://axiom.apache.org/
  7. java.util.ServiceLoader 手册(构建器发现机制):https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html