Java
本页介绍 Java API 与构建方面的变更。连接字符串、标签地址和默认值的变更适用于所有语言,相关说明见概述页 - 请先先阅读该页。
检查清单
- 将构建迁移到 Java 21,并且如果你自行构建 PLC4X,则迁移到 Maven 4。
- 更新已重命名的 Maven artifactId。
- 将
getConnectionManager()替换为getConnectionFactory()。 - 将
CachedPlcConnectionManager替换为PlcConnectionCache。 - 将
Scraper替换为Event-Pump。 - 更新你实现的任何
ConnectionStateListener。 - 更新你实现的任何
PlcBrowseItem、PlcBrowseRequestInterceptor或ArrayInfo。 - 重写 EtherNet/IP
EipTag的构造代码,因为它现在是不可变的。 - 重新检查根据
PlcConnectionMetadata分支的代码,它现在如实反映了实际情况。
构建
Java 21
不再支持 Java 11;新的基线是 Java 21。
plc4j-api 模块有意保持在 Java 17,以便其他驱动实现仍可面向 17。其余部分——SPI、驱动、工具——都需要 21。
Maven 4
PLC4X 的构建本身已迁移到 Apache Maven 4。只有当你从源码构建 PLC4X 时才与此相关;使用已发布的构件时,与以往一样可以配合 Maven 3 使用。
变更的 Maven 坐标
groupId 保持不变(org.apache.plc4x)。以下 artifactId 已更改,必须在你的 pom.xml 中更新:
| 0.13.1 | 1.0.0 |
|---|---|
plc4j-capture-replay | plc4j-tools-capture-replay |
plc4j-connection-cache | plc4j-tools-connection-cache |
plc4j-opm | plc4j-tools-opm |
表 1. 按照 plc4j-tools-* 模式重命名的 tools 模块
| 0.13.1 | 1.0.0 |
|---|---|
plc4j-transport-can | plc4j-transports-can |
plc4j-transport-pcap-replay | plc4j-transports-pcap-replay |
plc4j-transport-raw-socket | plc4j-transports-raw-socket |
plc4j-transport-serial | plc4j-transports-serial |
plc4j-transport-tcp | plc4j-transports-tcp |
plc4j-transport-test | plc4j-transports-test |
plc4j-transport-udp | plc4j-transports-udp |
plc4j-transport-socketcan | plc4j-transports-can-socketcan |
plc4j-transport-virtualcan | plc4j-transports-can-virtualcan |
表 2. 由单数改为复数命名的传输模块
| 0.13.1 | 1.0.0 |
|---|---|
plc4j-scraper | plc4j-tools-event-pump(是重写,而非重命名——参见 Scraper 被 Event-Pump 取代) |
plc4j-scraper-ng | 已移除(实验性) |
plc4j-transport-pcap-shared | 已移除(已废弃) |
表 3. 被替换和被移除的模块
1.0.0 还新增了模块——plc4j-spi- 的 buffers/config/drivers/values 拆分、plc4j-transports-api/-cotp/-tls 传输模块、plc4j-utils-audit-log 以及 plc4j-utils-subscription-emulation。这些是全新的构件,并非重命名,只有在使用它们提供的功能时才需要。 |
|---|
获取连接
创建连接的方法从 PlcConnectionManager 移到了新的 PlcConnectionFactory 接口中,而 PlcDriverManager 会以新的名称提供该接口:
// 0.13.1
PlcConnection connection = PlcDriverManager.getDefault()
.getConnectionManager()
.getConnection("s7://192.168.1.192");
// 1.0.0
PlcConnection connection = PlcDriverManager.getDefault()
.getConnectionFactory()
.getConnection("s7://192.168.1.192");PlcConnectionManager 依然存在。它现在继承了 PlcConnectionFactory,并新增了 close(),且仅由那些会持有自己所分发连接的管理器实现——箱中的连接缓存正是这样一个实现。这种区分正是拆分的意义所在:工厂负责创建连接,但不持有任何东西;管理器则持有自己创建的连接,因此必须能够被关闭。
如果你要接收一个连接来源作为参数,那么 PlcConnectionFactory 几乎总是你想要的类型:
// works with the driver manager and with the cache
public MyService(PlcConnectionFactory connectionFactory) { ... }连接缓存
CachedPlcConnectionManager 已重命名为 PlcConnectionCache,与该概念在 PLC4Go 中已有的名称保持一致。Maven 的 artifactId(plc4j-tools-connection-cache)和包名(org.apache.plc4x.java.utils.cache)保持不变。
其实现已重写,以便更可靠地处理资源,并且在连接丢失并重新建立后,能够透明地自动重新订阅。
| 0.13.1 | 1.0.0 |
|---|---|
CachedPlcConnectionManager | PlcConnectionCache |
PlcConnectionManagerClosedException | PlcConnectionCacheClosedException |
getBuilder(PlcConnectionManager) | getBuilder() + withConnectionFactory(…) |
withMaxLeaseTime(Duration) | withMaxLeaseTime(long, TimeUnit) |
withMaxWaitTime(Duration) | withMaxWaitTime(long, TimeUnit) |
withMaxIdleTime(Duration) | withMaxIdleTime(long, TimeUnit) |
// 0.13.1
CachedPlcConnectionManager cache = CachedPlcConnectionManager
.getBuilder(PlcDriverManager.getDefault().getConnectionManager())
.withMaxLeaseTime(Duration.ofSeconds(30))
.build();
// 1.0.0
PlcConnectionCache cache = PlcConnectionCache.getBuilder()
.withConnectionFactory(PlcDriverManager.getDefault().getConnectionFactory())
.withMaxLeaseTime(30, TimeUnit.SECONDS)
.build();withConnectionFactory(…) 不是可选的。如果没有它,build() 会抛出 IllegalStateException,而旧的构建器则会默认使用一个全新的 DefaultPlcDriverManager。 |
|---|
构建器新增了 withScheduler(…)、withPingTimeout(…)、withIdlePingThreshold(…) 和 withCloseTimeout(…);缓存新增了 getCachedConnectionCount() 和 getActiveLeaseCount()。removeCachedConnection(String) 现在返回一个 boolean,用于说明是否移除了任何内容,而 close() 不再声明受检异常。
完整选项请参见 Connection Cache 页面。
Scraper 被 Event-Pump 取代
Scraper(plc4j-scraper)已经移除,同时移除的还有实验性的 plc4j-scraper-ng。它的替代者是 Event-Pump(plc4j-tools-event-pump),它承担同样的职责——按计划轮询一组标签并把每个响应交给监听器——但 API 更小,并且能正确处理响应速度慢于配置间隔的 PLC。
这是一次重写,而非重命名:没有可以无缝替换的类映射。Event-Pump 页面中有一个 从 Scraper 迁移 章节,逐步讲解了这一过程。
开始之前有一点值得了解:触发间隔现在除了整秒之外,还可以用毫秒表示(intervalMillis / initialDelayMillis)。如果用两种单位给出同一个设置,程序会在启动时失败,而不是悄悄选择其中一个。
你可以实现的 API 接口
ConnectionStateListener
双方法形式被替换为接受一个事件的单一方法,因为「已连接」和「已断开」从来都不是唯一值得报告的内容。
// 0.13.1
public class MyListener implements ConnectionStateListener {
@Override public void connected() { ... }
@Override public void disconnected() { ... }
}
// 1.0.0
public class MyListener implements ConnectionStateListener {
@Override
public void onConnectionStateChanged(PlcConnectionStateChangedEvent event) {
switch (event.getChangeType()) {
case CONNECTED -> ...;
case DISCONNECTED -> ...; // graceful, close() was called
case CONNECTION_LOST -> ...; // unexpected: network error, timeout
case TAGS_CHANGED -> ...; // available tags changed, re-browse
case MODE_RUN,
MODE_STOP,
MODE_CONFIG -> ...; // the PLC changed operating mode
}
}
}PlcConnectionStateChangedEvent 还携带 getDetails(),即描述所发生事件的可读字符串。
旧的 disconnected() 同时涵盖了优雅关闭和连接丢失两种情况。如果你的监听器在 disconnected() 上重新连接,现在应当只在 CONNECTION_LOST 上这样做——在 DISCONNECTED 上重新连接会与你自己的 close() 相冲突。 |
|---|
PlcBrowseItem
isSubscribable() 返回的 boolean 无法说明如何订阅某个项目。它已被 getSupportedSubscriptionTypes() 取代:
// 1.0.0
default Set<PlcSubscriptionType> getSupportedSubscriptionTypes() {
return Collections.emptySet();
}
default boolean isSubscribable() {
return !getSupportedSubscriptionTypes().isEmpty();
}两者都是默认方法,因此自定义实现依然可以正常编译——但在覆写 getSupportedSubscriptionTypes() 之前,它会报告“不可订阅”。调用方无需任何改动:isSubscribable() 仍然回答同样的问题。
PlcBrowseRequestInterceptor
该签名新增了结果所属的查询,因此拦截器可以判断结果是由多个查询中的哪一个产生的:
// 0.13.1
boolean intercept(PlcBrowseItem item);
// 1.0.0
boolean intercept(String queryName, PlcQuery query, PlcBrowseItem item);ArrayInfo
ArrayInfo 新增了 getBase() 和 isRange(),两者都是默认方法,因此现有实现仍可正常编译。
更重要的是已有方法的含义。Javadoc 过去将 [6] 描述为一个六元素数组,但这从来不是驱动程序的实际行为。getLowerBound() 和 getUpperBound() 表示的是地址中所写的索引,两端均为闭区间:对于 [6],两者都是 6;对于 [0..7],getSize() 为 8。
之所以存在 isRange(),是因为仅凭边界相等无法区分 myTag[4](单个元素,标量)和 myTag[4..4](只含一个元素的列表)。PlcTag.getArrayInfo() 会返回所接收值的形状——标量返回空,数组则每个维度返回一个条目——这样消费者无需了解协议即可区分这两者。
Option
Option 新增了 isSecret(),用于报告某个配置选项是否包含机密信息。这是一个默认方法,返回 false,因此现有实现无需改动即可继续编译。
这正是脱敏逻辑现在所做的判断,而不再根据参数名称来猜测。如果你自行声明了配置参数,请为敏感项标注 @Secret——基于名称的检查仍然作为兜底手段存在,但它永远会落后一个参数。
现有 API 的行为变更
连接会如实报告其驱动实际支持的能力
ConnectionBase 对 isReadSupported()、isWriteSupported()、isSubscribeSupported() 和 isBrowseSupported() 一律回答 true,无论基于它的驱动实现了什么。因此,工具用来决定提供哪些选项的元数据根本不值得读取。
现在每个连接都会声明自己支持什么,依据这些标志分支的代码会看到不同——而且正确——的答案。如果你的应用曾据此构建界面,请预期可提供的选项会变少,而且这样才是对的。
EtherNet/IP 的 EipTag 不可变
EipTag 是 PLC4J 中唯一暴露 setter 的标签类。setType(…) 和 setElementNb(…) 已移除;请改为向构造函数传入类型和元素数量。
小于一的元素数量会被规整为一而不是原样保留,因此以 ":INT:0" 方式构造的标签,或通过过去会把数量留在零的 (tag, type) 构造函数创建的标签,现在读取到的是一个元素而不是零个。
值的序列化
如果你解析 PlcValue 的 XML 或 JSON 渲染结果,以下内容会发生变化:
WORD、DWORD和LWORD序列化为dataType="uint",而不再通过带符号的写入器。PlcBYTE序列化为位串。PlcTIME渲染为 ISO-8601 字符串,与PlcLTIME一致。
这些调整使同一值的 Java 与 Go 渲染结果保持一致。
标签地址错误按标签报告
在 EtherNet/IP、Modbus、OPC UA、S7 和模拟驱动中,无法解析的地址现在会针对该标签单独报告,而不是让整个请求失败——这正是 API 一直承诺的行为。一个混合了错误地址与正确地址的请求,现在会为正确的地址返回结果,并为错误的地址返回错误码。
如果你编写了自己的驱动
所有驱动都已迁移到新的共享 SPI,即 SPI3:无依赖的读写缓冲区、更新后的代码生成框架、可插拔的传输系统以及分层的协议驱动模型。基于 0.13 SPI 编写的驱动无法在 1.0.0 下编译。
这没有机械式的迁移方法。最有用的参考是 1.0.0 源码树中形态相当的驱动;plc4j-driver-modbus 和 plc4j-driver-s7 模块分别涵盖了一个简单的请求/响应协议和一个面向连接的协议。
开始之前,有两件事值得了解:
- 驱动会声明它支持的传输方式。
getMetadata().getSupportedTransportCodes()会在连接时进行检查,若连接指定的传输方式不在该集合中,连接将被拒绝。 - 连接元数据是推导出来的,而不是声明出来的。
ConnectionBase过去不管驱动实现了什么,都会回答true、isReadSupported()、isWriteSupported()、isSubscribeSupported()和isBrowseSupported()。现在每个标志都根据你的连接类是否覆写了对应的钩子来推导——onRead、onWrite、onSubscribe以及onBrowse或onBrowseWithInterceptor。你无需声明任何东西;实现钩子,元数据自然就有了。
如果你的协议没有订阅功能,可以从 plc4j-utils-subscription-emulation获得,这是 Modbus、EtherNet/IP、AB-ETH、SLMP 和 UMAS 现在用于 CYCLIC和 CHANGE_OF_STATE订阅的基于轮询的仿真层。
1.0.0 中的新增内容
这些不属于迁移工作,但升级到 1.0.0 后值得了解:
- 审计日志⟧——将某一次连接的完整轨迹记录到文件中,通过连接串参数开启,完全无需修改代码。当迁移后的连接行为与旧连接不一致时,这是最值得优先使用的工具。
PlcCertificateAuthentication——X.509 用户认证,目前由 OPC UA 驱动使用。- OPC UA 新增了结构化值(
PlcStruct)、浏览支持,以及从服务器类型模型推导而非从输入数据猜测的标签数据类型。 - 对象 PLC 映射(OPM⟧)模块已移植到 SPI3。
- 新增一种 TCP 传输,使用每连接一个虚拟线程的阻塞式 I/O——这正是 Java 21 带来的能力。
- S7 连接可以通过
readDeviceIdentification()上报设备自述信息。
评论
登录后参与评论
KnowForge