Apache Neethi 3.2.2 集成使用实战详解
以 Maven 项目实战 Apache Neethi 3.2.2:解析 WS-Policy、合并与规范化、注册表管理与自定义断言构建器,全部代码与回显实测通过。
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) |
| JDK | OpenJDK 17.0.20.1 |
| Maven | Apache 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 聚合策略时才需要评估加大。
八、参考链接
- Apache Neethi 官方主页(含 API 文档链接):https://neethi.apache.org/
- Neethi 3.2.2 构件与 POM:https://repo.maven.apache.org/maven2/org/apache/neethi/neethi/3.2.2/
- Neethi 源码(含 builders/converters):https://github.com/apache/neethi
- WS-Policy 1.5 标准(框架语义与合并算子):https://www.w3.org/TR/ws-policy/
- WS-PolicyAttachment(Optional/Ignorable 属性定义):https://www.w3.org/Submission/WS-PolicyAttachment/
- Apache AXIOM 用户指南:https://axiom.apache.org/
- java.util.ServiceLoader 手册(构建器发现机制):https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html