聊聊Service Worker

本文介绍了Web Workers的基本概念及分类,重点解析Service Worker的工作原理、生命周期及其如何帮助Web应用实现离线访问等功能。通过实例展示了Service Worker的注册、安装、激活及更新流程。

Web Workers

web worker: 为Web内容在后台线程中运行脚本提供了一种简单的方法,线程可以执行任务而不干扰用户界面,即:运行在后台的 JavaScript

浏览器一般有三类 web Worker:
- Worker:专用的 worker,只能被创建它的 JS 访问,生命周期到创建它的页面关闭时结束。
- SharedWorker:共享的 worker,可以被好几个 JS 访问,生命周期到关联的页面都关闭时结束。
ServiceWorker:一个特殊的 worker,生命周期与页面无关,关联页面未关闭时,它也可以退出,没有关联页面时,它也可以启动,嗯总之就是一个神奇的worker

对于workers来说,它运行的上下文不同于当前的window对象所在的上下文,在专用worker的情况下,DedicatedWorkerGlobalScope 对象代表了worker的上下文;在共享worker的情况下,SharedWorkerGlobalScope对象代表了共享worker的上下文。

因此,在worker线程中运行的代码是有一些特殊情况的,比如,不能直接操作DOM以及使用window对象的一些默认属性和方法。但是在worker里,还是有很多window对象之下的东西是可以使用的,具体就要查阅文档了 https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers

worker线程和主线程各自都使用postMessage()发送消息和和onmessage事件来响应对方发送的消息,传递的信息包含在 Message 这个事件的data属性内里,数据传递的是副本。

当然一个 worker 可以生成另外的新的 worker,这些 worker 的宿主和它们父页面的宿主相同。

Service Worker

前两种worker主要是为了解决js执行耗时操作时影响UI响应的问题,而之所以说service worker是一种特殊的worker,是因为它想要把一个web APP变得更像native APP,可以支持离线访问。在service worker之前,离线缓存使用AppCache来做,从Firefox44起,当使用 AppCache 来提供离线页面支持时,会提示建议开发者使用 service workers 来实现离线页面。

  • 它是一种 JavaScript 工作线程,无法直接访问 DOM。 service worker通过响应 postMessage 接口发送的消息来与其控制的页面通信,页面可在必要时对 DOM 执行操作。
  • 它是一种可编程网络代理,能够控制并处理页面所发送的网络请求。
  • 它在不用时会被中止,并在下次有需要时重启,因此不能依赖于service worker的
    onfetch事件onmessage事件 处理程序中的全局状态。如果存在需要持续保存并在重启后加以重用的信息,service worker可以访问 IndexedDB API以及FireFox OS专用的Data Store API等数据存储机制。
  • service worker 广泛利用了 promise
  • service Workers 要求要在必须在 HTTPS 下才能运行,为了便于本地开发,localhost 也被浏览器认为是安全源。

生命周期

Service worker的生命周期完全独立于网页

  1. 如果要使用service worker,首先要在js中进行注册,注册的动作会让浏览器在后台启动service worker的安装步骤
  2. service worker获取的第一个事件为 install。该事件在工作线程执行时立即触发,并且只能被每个service worker调用一次。 如果更改service worker的代码,则浏览器将其视为一个不同的service worker,并且它将获得自己的 install 事件。在安装的过程中,如果所有需要离线缓存的静态资源都已经成功缓存,那么service worker就安装完成进入激活步骤,如果有文件下载失败或缓存失败,service worker就无法完成安装过程。
  3. 安装之后进入激活步骤,可以对旧的缓存进行管理
  4. 激活之后,service worker开始施展身手,对它作用域内的所有页面进行控制,首次注册service worker的页面需要再次加载时才会受控制。激活之后,service worker将处于以下两种状态之一:终止或处理onfetch和onmessage事件,从页面发出网络请求或消息后将会出现后一种状态。

如果 service worker 脚本版本处于 ACTIVATED 状态,功能事件处理完之后,service worker 线程会被终止,当再次有功能事件时,service worker 线程又会被启动,启动完成后 service worker 就可以立即进入 ACTIVATED 状态。

生命周期

浏览器内核会管理三种 service worker 脚本版本:

  • installing_version:处于 INSTALLING 状态的版本
  • waiting_version:处于 INSTALLED 状态的版本
  • active_version:处于 ACTIVATED 状态的版本

installing_version 一般是在 service worker 线程启动后的版本,这是一个中间版本,在正确安装完成后会转入 waiting_version。

waiting_version 一般在注册信息已被存储的版本状态,或者在再次打开 service worker 页面时,检查到 service worker 脚本版本的状态为 INSTALLED,也会进入此版本状态。waiting_version 的存在确保了当前 scope 下只有一个生效的 service worker。

active_version 一般在 activate 事件处理完成后,就会处于此版本状态,同一 scope 下只有一个 active Service Worker。需要特别注意的是,当前页面已有 active worker 控制,刷新页面时,新版本 Waiting(Installed) 状态的 service worker 并不能转入 active 状态。

Service worker 可以从 waiting_version 转入 active_version 的条件:

  • 当前 scope 下没有 active service worker 在运行。
  • 页面 JS 调用 self.skipWaiting 跳过 waiting 状态。
  • 用户关闭页面,释放了当前处于 active 状态的 service worker。
  • 浏览器周期性检测,发现 active service worker 处于 idle 状态,就会释放当前处于 active 状态的 service worker。

其中,INSTALLED和ACTIVATED是稳定状态,可以对缓存之类的资源进行管理。

E.G.

1. 注册

service worker需要在页面中进行注册才能启动安装步骤,注册时需要告诉在service worker中执行的代码的地址

if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
        navigator.serviceWorker.register('/sw.js').then(function(registration) {
            // Registration was successful
            console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }).catch(function(err) {
            // registration failed
            console.log('ServiceWorker registration failed: ', err);
        });
    });
}

此代码用于检查 Service Worker API 是否可用,如果可用,则在页面加载后注册位于 /sw.js 的service worker。
每次页面加载无误时,即可调用 register();浏览器将会判断service worker是否已注册并做出相应的处理。

register() 方法的精妙之处在于service worker文件的位置。 本例中service worker文件位于根目录。 这意味着service worker的作用域将是整个来源。 换句话说,service worker可以接收此网域上所有的 fetch 事件。 如果在 /example/sw.js 处注册service worker文件,则service worker将只能看到URL以 /example/ 开头(即 /example/page1/、/example/page2/)的页面的 fetch 事件。

chrome://inspect/#service-workers 可以看到目前运行中的service worker

这是Google的一个例子
https://cdn.rawgit.com/jakearchibald/80368b84ac1ae8e229fc90b3fe826301/raw/ad55049bee9b11d47f1f7d19a73bf3306d156f43/

2. 安装

启动注册后,需要为安装事件定义一个回调,决定哪些资源需要被缓存,在回调的内部,需要执行以下步骤:
1. 打开缓存
2. 缓存资源文件
3. 确定所有需要缓存的资源是否已经缓存完毕

在 install 事件中也可以执行其他任务,甚至不设置 install 事件侦听器也可以。

var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

3.开始工作

刷新当前页面后,service worker将开始接收 fetch 事件

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }
        return fetch(event.request);
      }
    )
  );
});

这里定义了 fetch 事件,并且在 event.respondWith() 中传入来自 caches.match() 的一个 promise。 此方法会检车这个请求,并从service worker创建的缓存中查找缓存的结果。

如果发现有匹配的结果,则返回命中的值,否则调用 fetch 发出网络请求,并将从服务器请求到的数据作为结果返回。

4. 更新service worker

  1. 更新您的service worker JavaScript 文件。用户访问更新了之后的站点时,浏览器会尝试在后台重新下载service worker的脚本文件。如果service worker文件与其当前所用文件存在字节差异,则将其视为“新service worker”。
  2. 新service worker将会启动,且触发 install 事件。
  3. 此时,旧service worker仍控制着当前页面,新service worker将进入 waiting 状态。
  4. 当网站上当前打开的页面关闭时,旧service worker将会被终止,新service worker将会取得控制权。
  5. 新service worker取得控制权后,触发其 activate 事件。(旧service worker退出时将触发 新service worker的Activate,新service worker将能够控制客户端。 可以执行在仍使用旧工作线程时无法执行的操作,如迁移数据库和清除缓存。)
    出现在 activate 回调中的一个常见任务是缓存管理,但缓存管理通常不在这个新service worker的install回调里面做,因为它installed的时候旧的service worker还在工作中。

比如说我们有一个名为 ‘my-site-cache-v1’ 的缓存,我们想要将该缓存拆分为一个页面缓存和一个博文缓存。这就意味着在安装步骤中我们创建了两个缓存:’pages-cache-v1’ 和 ‘blog-posts-cache-v1’,且在激活步骤中我们要删除旧的 ‘my-site-cache-v1’。

具体做法为:遍历service worker中的所有缓存,并删除未在缓存白名单中定义的任何缓存。

self.addEventListener('activate', function(event) {

  var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1'];

  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

等待阶段表示每次只能运行一个版本的service worker,但可以通过调用 self.skipWaiting() 用新的service worker把旧的那个逐出去,并在进入等待阶段时尽快激活自己(或立即激活,前提是已经处于等待阶段)。
skipWaiting() 在等待期间调用还是在之前调用并没有什么不同。 一般情况下是在 install 事件中调用它:

self.addEventListener('install', event => {
  self.skipWaiting();

  event.waitUntil(
    // caching etc
  );
});
[root@yfw ~]# cd /opt/openfire [root@yfw openfire]# cd ~/openfire-plugin-build [root@yfw openfire-plugin-build]# rm -rf src/RestApiPlugin.java [root@yfw openfire-plugin-build]# mkdir -p src [root@yfw openfire-plugin-build]# cat > src/RestApiPlugin.java << 'EOF' > public class RestApiPlugin implements org.jivesoftware.openfire.container.Plugin { > > static { > System.out.println("[PLUGIN-DEBUG] 💡 静态块执行:RestApiPlugin 类已被 JVM 加载!"); ugin.class"));em.out.println("[PLUGIN-DEBUG] 📦 类资源位置: " + RestApiPlugin.class.getResource("RestApiPlu > } > > public RestApiPlugin() { > System.out.println("[PLUGIN-DEBUG] 🆕 构造函数执行:RestApiPlugin 实例已创建"); > } > > @Override > public void initializePlugin(org.jivesoftware.openfire.container.PluginManager manager, java.io.File pluginDirectory) { ath()); System.out.println("[PLUGIN-DEBUG] 🎉 插件初始化成功!插件目录: " + pluginDirectory.getAbsolutePa > } > > @Override > public void destroyPlugin() { > System.out.println("[PLUGIN-DEBUG] 🔌 插件已销毁"); > } > } > EOF [root@yfw openfire-plugin-build]# # 编译 [root@yfw openfire-plugin-build]# /usr/lib/jvm/java-11-openjdk/bin/javac \ > -source 11 -target 11 \ > -cp "/opt/openfire/lib/xmppserver-4.9.2.jar" \ > -d classes \ > src/RestApiPlugin.java [root@yfw openfire-plugin-build]# [root@yfw openfire-plugin-build]# # 打包 [root@yfw openfire-plugin-build]# jar cf restapi.jar -C classes . [root@yfw openfire-plugin-build]# [root@yfw openfire-plugin-build]# # 清除旧插件和缓存 [root@yfw openfire-plugin-build]# rm -rf /opt/openfire/plugins/restapi [root@yfw openfire-plugin-build]# rm -rf /opt/openfire/work/plugins/restapi* [root@yfw openfire-plugin-build]# [root@yfw openfire-plugin-build]# # 部署 [root@yfw openfire-plugin-build]# mkdir -p /opt/openfire/plugins/restapi [root@yfw openfire-plugin-build]# cp restapi.jar /opt/openfire/plugins/restapi/ [root@yfw openfire-plugin-build]# [root@yfw openfire-plugin-build]# # 写入 plugin.xml(确保没问题) [root@yfw openfire-plugin-build]# cat > /opt/openfire/plugins/restapi/plugin.xml << 'EOF' > <?xml version="1.0" encoding="UTF-8"?> > <plugin> > <className>RestApiPlugin</className> > <name>REST API Plugin</name> > <description>Simple REST API Plugin with Debug Logs</description> > <author>Admin</author> > <version>1.0.0</version> > <date>2025-10-03</date> > <minServerVersion>4.0.0</minServerVersion> > </plugin> > EOF [root@yfw openfire-plugin-build]# [root@yfw openfire-plugin-build]# # 权限 [root@yfw openfire-plugin-build]# chown -R openfire:openfire /opt/openfire/plugins/restapi [root@yfw openfire-plugin-build]# chmod -R 755 /opt/openfire/plugins/restapi [root@yfw openfire-plugin-build]# [root@yfw openfire-plugin-build]# # 重启 [root@yfw openfire-plugin-build]# systemctl restart openfire [root@yfw openfire-plugin-build]# [root@yfw openfire-plugin-build]# # 查看日志 [root@yfw openfire-plugin-build]# tail -f /opt/openfire/logs/openfire.log at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?] at java.lang.Thread.run(Thread.java:829) [?:?] 2025.10.03 04:31:49.782 INFO [Thread-2]: org.jivesoftware.openfire.XMPPServer - Shutting down plugins ... 2025.10.03 04:31:49.782 INFO [Thread-2]: org.jivesoftware.openfire.container.PluginManager - Shutting down. Unloading all loaded plugins... 2025.10.03 04:31:49.789 INFO [Thread-2]: org.jivesoftware.openfire.XMPPServer - Shutting down 54 modules ... 2025.10.03 04:31:49.962 INFO [shutdown-thread-0]: org.jivesoftware.openfire.pubsub.DefaultPubSubPersistenceProvider - Flushing write cache to database 2025.10.03 04:31:49.966 INFO [shutdown-thread-0]: org.jivesoftware.openfire.OfflineMessageStore - Offline message cleaning - Stop old timer if started 2025.10.03 04:31:49.980 INFO [Thread-2]: org.jivesoftware.openfire.XMPPServer - Openfire stopped 2025.10.03 04:31:51.473 INFO [main]: org.jivesoftware.openfire.XMPPServer - Registering shutdown hook (standalone mode) 2025.10.03 04:31:52.088 INFO [main]: org.jivesoftware.util.cache.ConsistencyMonitor - Applying configuration for cache consistency check. Enabled: false 2025.10.03 04:31:52.110 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Routing Servers Cache 2025.10.03 04:31:52.112 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Routing Components Cache 2025.10.03 04:31:52.113 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Routing Users Cache 2025.10.03 04:31:52.113 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Routing AnonymousUsers Cache 2025.10.03 04:31:52.114 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Routing User Sessions 2025.10.03 04:31:52.118 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Roster 2025.10.03 04:31:52.121 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for RosterItems 2025.10.03 04:31:52.139 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Routing Result Listeners 2025.10.03 04:31:52.142 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Multicast Service 2025.10.03 04:31:52.147 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Offline Message Size 2025.10.03 04:31:52.150 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for VCard 2025.10.03 04:31:52.254 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Privacy Lists 2025.10.03 04:31:52.258 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for File Transfer Cache 2025.10.03 04:31:52.334 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Offline Presence Cache 2025.10.03 04:31:52.335 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Last Activity Cache 2025.10.03 04:31:52.339 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for User 2025.10.03 04:31:52.339 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Remote Users Existence 2025.10.03 04:31:52.352 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Components Sessions 2025.10.03 04:31:52.353 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Connection Managers Sessions 2025.10.03 04:31:52.353 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Incoming Server Session Info Cache 2025.10.03 04:31:52.353 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Sessions by Hostname 2025.10.03 04:31:52.353 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Client Session Info Cache 2025.10.03 04:31:52.355 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Directed Presences 2025.10.03 04:31:52.356 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created local-only cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for PEPServiceManager 2025.10.03 04:31:52.358 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for File Transfer 2025.10.03 04:31:52.361 INFO [main]: org.jivesoftware.openfire.pubsub.PubSubPersistenceProviderManager - Loading PubSub persistence provider: class org.jivesoftware.openfire.pubsub.CachingPubsubPersistenceProvider. 2025.10.03 04:31:52.363 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Published Items 2025.10.03 04:31:52.364 INFO [main]: org.jivesoftware.openfire.pubsub.CachingPubsubPersistenceProvider - Loading PubSub persistence provider to delegate to: class org.jivesoftware.openfire.pubsub.DefaultPubSubPersistenceProvider. 2025.10.03 04:31:52.368 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Default Node Configurations 2025.10.03 04:31:52.381 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Disco Server Features 2025.10.03 04:31:52.386 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Disco Server Items 2025.10.03 04:31:52.386 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Components 2025.10.03 04:31:52.612 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created local-only cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Entity Capabilities 2025.10.03 04:31:52.612 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created local-only cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Entity Capabilities Users 2025.10.03 04:31:52.662 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created local-only cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Group (Shared) Metadata Cache 2025.10.03 04:31:52.666 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Group 2025.10.03 04:31:52.667 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Group Metadata Cache 2025.10.03 04:31:52.700 INFO [main]: org.jivesoftware.openfire.pubsub.PubSubModule - 发布–订阅域:pubsub.localhost 2025.10.03 04:31:52.711 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for MUC Service Pings Sent 2025.10.03 04:31:52.713 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for MUC History 2025.10.03 04:31:52.717 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for MUC Service 'conference' Rooms 2025.10.03 04:31:52.717 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for MUC Service 'conference' Room Statistics 2025.10.03 04:31:52.723 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for MUC Service 'lobby' Rooms 2025.10.03 04:31:52.723 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for MUC Service 'lobby' Room Statistics 2025.10.03 04:31:52.733 INFO [main]: org.jivesoftware.openfire.muc.spi.MultiUserChatServiceImpl - Rescheduling user idle task, recurring every PT15M 2025.10.03 04:31:52.738 INFO [main]: org.jivesoftware.openfire.muc.spi.MultiUserChatServiceImpl - 多用户聊天域:conference.localhost 2025.10.03 04:31:52.757 INFO [main]: org.jivesoftware.openfire.muc.spi.MultiUserChatServiceImpl - Rescheduling user idle task, recurring every PT15M 2025.10.03 04:31:52.759 INFO [main]: org.jivesoftware.openfire.muc.spi.MultiUserChatServiceImpl - 多用户聊天域:lobby.localhost 2025.10.03 04:31:52.792 INFO [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - Plugin 'restapi' was removed from the file system. 2025.10.03 04:31:52.797 INFO [main]: org.jivesoftware.openfire.XMPPServer - Openfire 4.9.2 [2025年10月3日 上午4:31:52] 2025.10.03 04:31:52.794 ERROR [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - An unexpected exception occurred: java.lang.NullPointerException: null at org.jivesoftware.openfire.container.PluginManager.unloadPlugin(PluginManager.java:883) ~[xmppserver-4.9.2.jar:4.9.2] at org.jivesoftware.openfire.container.PluginMonitor$MonitorTask.run(PluginMonitor.java:305) [xmppserver-4.9.2.jar:4.9.2] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?] at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) [?:?] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?] at java.lang.Thread.run(Thread.java:829) [?:?] 2025.10.03 04:31:52.998 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for LDAP UserDN 2025.10.03 04:31:53.526 INFO [main]: org.jivesoftware.openfire.net.SASLAuthentication - Support added for the 'ANONYMOUS' SASL mechanism. 2025.10.03 04:31:53.526 INFO [main]: org.jivesoftware.openfire.net.SASLAuthentication - Support added for the 'CRAM-MD5' SASL mechanism. 2025.10.03 04:31:53.526 INFO [main]: org.jivesoftware.openfire.net.SASLAuthentication - Support added for the 'DIGEST-MD5' SASL mechanism. 2025.10.03 04:31:53.526 INFO [main]: org.jivesoftware.openfire.net.SASLAuthentication - Support added for the 'EXTERNAL' SASL mechanism. 2025.10.03 04:31:53.527 INFO [main]: org.jivesoftware.openfire.net.SASLAuthentication - Support added for the 'GSSAPI' SASL mechanism. 2025.10.03 04:31:53.527 INFO [main]: org.jivesoftware.openfire.net.SASLAuthentication - Support added for the 'JIVE-SHAREDSECRET' SASL mechanism. 2025.10.03 04:31:53.527 INFO [main]: org.jivesoftware.openfire.net.SASLAuthentication - Support added for the 'PLAIN' SASL mechanism. 2025.10.03 04:31:53.527 INFO [main]: org.jivesoftware.openfire.net.SASLAuthentication - Support added for the 'SCRAM-SHA-1' SASL mechanism. 2025.10.03 04:31:53.568 INFO [main]: org.jivesoftware.util.cache.CacheFactory - Created cache [org.jivesoftware.util.cache.DefaultLocalCacheStrategy] for Remote Server Configurations 2025.10.03 04:32:12.807 INFO [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - Plugin 'restapi' was removed from the file system. 2025.10.03 04:32:12.808 ERROR [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - An unexpected exception occurred: java.lang.NullPointerException: null at org.jivesoftware.openfire.container.PluginManager.unloadPlugin(PluginManager.java:883) ~[xmppserver-4.9.2.jar:4.9.2] at org.jivesoftware.openfire.container.PluginMonitor$MonitorTask.run(PluginMonitor.java:305) [xmppserver-4.9.2.jar:4.9.2] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?] at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) [?:?] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?] at java.lang.Thread.run(Thread.java:829) [?:?] 2025.10.03 04:32:32.810 INFO [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - Plugin 'restapi' was removed from the file system. 2025.10.03 04:32:32.810 ERROR [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - An unexpected exception occurred: java.lang.NullPointerException: null at org.jivesoftware.openfire.container.PluginManager.unloadPlugin(PluginManager.java:883) ~[xmppserver-4.9.2.jar:4.9.2] at org.jivesoftware.openfire.container.PluginMonitor$MonitorTask.run(PluginMonitor.java:305) [xmppserver-4.9.2.jar:4.9.2] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?] at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) [?:?] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?] at java.lang.Thread.run(Thread.java:829) [?:?] 2025.10.03 04:32:52.811 INFO [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - Plugin 'restapi' was removed from the file system. 2025.10.03 04:32:52.811 ERROR [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - An unexpected exception occurred: java.lang.NullPointerException: null at org.jivesoftware.openfire.container.PluginManager.unloadPlugin(PluginManager.java:883) ~[xmppserver-4.9.2.jar:4.9.2] at org.jivesoftware.openfire.container.PluginMonitor$MonitorTask.run(PluginMonitor.java:305) [xmppserver-4.9.2.jar:4.9.2] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?] at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) [?:?] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?] at java.lang.Thread.run(Thread.java:829) [?:?] 2025.10.03 04:33:12.813 INFO [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - Plugin 'restapi' was removed from the file system. 2025.10.03 04:33:12.813 ERROR [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - An unexpected exception occurred: java.lang.NullPointerException: null at org.jivesoftware.openfire.container.PluginManager.unloadPlugin(PluginManager.java:883) ~[xmppserver-4.9.2.jar:4.9.2] at org.jivesoftware.openfire.container.PluginMonitor$MonitorTask.run(PluginMonitor.java:305) [xmppserver-4.9.2.jar:4.9.2] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?] at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) [?:?] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?] at java.lang.Thread.run(Thread.java:829) [?:?] 2025.10.03 04:33:32.814 INFO [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - Plugin 'restapi' was removed from the file system. 2025.10.03 04:33:32.814 ERROR [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - An unexpected exception occurred: java.lang.NullPointerException: null at org.jivesoftware.openfire.container.PluginManager.unloadPlugin(PluginManager.java:883) ~[xmppserver-4.9.2.jar:4.9.2] at org.jivesoftware.openfire.container.PluginMonitor$MonitorTask.run(PluginMonitor.java:305) [xmppserver-4.9.2.jar:4.9.2] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?] at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) [?:?] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?] at java.lang.Thread.run(Thread.java:829) [?:?] 2025.10.03 04:33:52.816 INFO [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - Plugin 'restapi' was removed from the file system. 2025.10.03 04:33:52.816 ERROR [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - An unexpected exception occurred: java.lang.NullPointerException: null at org.jivesoftware.openfire.container.PluginManager.unloadPlugin(PluginManager.java:883) ~[xmppserver-4.9.2.jar:4.9.2] at org.jivesoftware.openfire.container.PluginMonitor$MonitorTask.run(PluginMonitor.java:305) [xmppserver-4.9.2.jar:4.9.2] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?] at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) [?:?] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?] at java.lang.Thread.run(Thread.java:829) [?:?] 2025.10.03 04:34:12.817 INFO [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - Plugin 'restapi' was removed from the file system. 2025.10.03 04:34:12.817 ERROR [PluginMonitorTask-2]: org.jivesoftware.openfire.container.PluginMonitor - An unexpected exception occurred: java.lang.NullPointerException: null at org.jivesoftware.openfire.container.PluginManager.unloadPlugin(PluginManager.java:883) ~[xmppserver-4.9.2.jar:4.9.2] at org.jivesoftware.openfire.container.PluginMonitor$MonitorTask.run(PluginMonitor.java:305) [xmppserver-4.9.2.jar:4.9.2] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?] at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) [?:?] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?] at java.lang.Thread.run(Thread.java:829) [?:?]
10-04
你已经非常接近成功了!我们来**彻底分析当前状态和问题根源**。 --- ## 🚨 关键结论:你的插件 **从未被加载**,因为 `.java` 源码文件仍然写入错误! 虽然你以为重新写了 `RestApiPlugin.java`,但日志中依然没有 `[PLUGIN-DEBUG] 💡 静态块执行...` 输出,说明: > ❌ 你的 `static {}` 块根本没有正确编译进 `.class` 文件 > ✅ 因为你再次在 `cat << 'EOF'` 时粘贴了断行、错位的代码 --- ### 🔍 证据:你在终端输入的内容仍然是坏的 看这段: ```bash cat > src/RestApiPlugin.java << 'EOF' public class RestApiPlugin implements org.jivesoftware.openfire.container.Plugin { static { System.out.println("[PLUGIN-DEBUG] 💡 静态块执行:RestApiPlugin 类已被 JVM 加载!"); ugin.class"));em.out.println("[PLUGIN-DEBUG] 📦 类资源位置: " + RestApiPlugin.class.getResource("RestApiPlu } ... EOF ``` 这一行是明显断裂的: ```java em.out.println("[PLUGIN-DEBUG] 📦 类资源位置: " + RestApiPlugin.class.getResource("RestApiPlu ``` 👉 它应该是完整的: ```java System.out.println("[PLUGIN-DEBUG] 📦 类资源位置: " + RestApiPlugin.class.getResource("RestApiPlugin.class")); ``` 但由于复制粘贴时终端渲染 emoji 或换行异常,导致命令被截断并拼接到下一行,最终生成了一个语法错误或逻辑混乱的 `.java` 文件。 即使能编译通过(Java 编译器有时会容忍部分格式),也不会输出你期望的日志。 --- ## ✅ 正确做法:使用最安全的方式写入源码 —— 分段 echo 或 here-document 手动键入 ### 方法一:用 `echo -e` 写入(推荐) ```bash cd ~/openfire-plugin-build rm -f src/RestApiPlugin.java echo -e 'public class RestApiPlugin implements org.jivesoftware.openfire.container.Plugin {\n' \ ' static {\n' \ ' System.out.println("[PLUGIN-DEBUG] 💡 静态块执行:RestApiPlugin 类已被 JVM 加载!");\n' \ ' System.out.println("[PLUGIN-DEBUG] 📦 类资源位置: " + RestApiPlugin.class.getResource("RestApiPlugin.class"));\n' \ ' }\n' \ '\n' \ ' public RestApiPlugin() {\n' \ ' System.out.println("[PLUGIN-DEBUG] 🆕 构造函数执行:RestApiPlugin 实例已创建");\n' \ ' }\n' \ '\n' \ ' @Override\n' \ ' public void initializePlugin(org.jivesoftware.openfire.container.PluginManager manager, java.io.File pluginDirectory) {\n' \ ' System.out.println("[PLUGIN-DEBUG] 🎉 插件初始化成功!插件目录: " + pluginDirectory.getAbsolutePath());\n' \ ' }\n' \ '\n' \ ' @Override\n' \ ' public void destroyPlugin() {\n' \ ' System.out.println("[PLUGIN-DEBUG] 🔌 插件已销毁");\n' \ ' }\n' \ '}' > src/RestApiPlugin.java ``` ✅ 这种方式避免了多行粘贴断行问题。 --- ### 方法二:手动逐行输入 `cat << 'EOF'`(不要复制粘贴整个块) ```bash cat > src/RestApiPlugin.java public class RestApiPlugin implements org.jivesoftware.openfire.container.Plugin { static { System.out.println("[PLUGIN-DEBUG] 💡 静态块执行:RestApiPlugin 类已被 JVM 加载!"); System.out.println("[PLUGIN-DEBUG] 📦 类资源位置: " + RestApiPlugin.class.getResource("RestApiPlugin.class")); } public RestApiPlugin() { System.out.println("[PLUGIN-DEBUG] 🆕 构造函数执行:RestApiPlugin 实例已创建"); } @Override public void initializePlugin(org.jivesoftware.openfire.container.PluginManager manager, java.io.File pluginDirectory) { System.out.println("[PLUGIN-DEBUG] 🎉 插件初始化成功!插件目录: " + pluginDirectory.getAbsolutePath()); } @Override public void destroyPlugin() { System.out.println("[PLUGIN-DEBUG] 🔌 插件已销毁"); } } ``` 然后按 `Ctrl+D` 结束输入。这样可以确保每一行都完整输入,不被终端误解。 --- ## ✅ 验证源码是否正确 ```bash cat src/RestApiPlugin.java ``` 你应该看到完整、无断裂的 Java 代码,特别是这两行: ```java System.out.println("[PLUGIN-DEBUG] 💡 静态块执行:RestApiPlugin 类已被 JVM 加载!"); System.out.println("[PLUGIN-DEBUG] 📦 类资源位置: " + RestApiPlugin.class.getResource("RestApiPlugin.class")); ``` --- ## ✅ 重新编译 & 部署(无需重复权限设置) ```bash # 编译 /usr/lib/jvm/java-11-openjdk/bin/javac \ -source 11 -target 11 \ -cp "/opt/openfire/lib/xmppserver-4.9.2.jar" \ -d classes \ src/RestApiPlugin.java # 打包 jar cf restapi.jar -C classes . # 清除旧插件和缓存 rm -rf /opt/openfire/plugins/restapi rm -rf /opt/openfire/work/plugins/restapi* # 部署 mkdir -p /opt/openfire/plugins/restapi cp restapi.jar /opt/openfire/plugins/restapi/ # 确保 plugin.xml 存在且正确 cat > /opt/openfire/plugins/restapi/plugin.xml << 'EOF' <?xml version="1.0" encoding="UTF-8"?> <plugin> <className>RestApiPlugin</className> <name>REST API Plugin</name> <description>Simple REST API Plugin with Debug Logs</description> <author>Admin</author> <version>1.0.0</version> <date>2025-10-03</date> <minServerVersion>4.0.0</minServerVersion> </plugin> EOF # 重启 Openfire systemctl restart openfire # 查看日志 tail -f /opt/openfire/logs/openfire.log ``` --- ## ✅ 成功标志:你会看到这些日志 ``` [PLUGIN-DEBUG] 💡 静态块执行:RestApiPlugin 类已被 JVM 加载! [PLUGIN-DEBUG] 📦 类资源位置: jar:file:/opt/openfire/plugins/restapi/restapi.jar!/RestApiPlugin.class [PLUGIN-DEBUG] 🆕 构造函数执行:RestApiPlugin 实例已创建 [PLUGIN-DEBUG] 🎉 插件初始化成功!插件目录: /opt/openfire/plugins/restapi INFO [PluginManager] Successfully loaded plugin: restapi ``` 一旦出现第一行,你就知道类被成功加载了! --- ## 🛠️ 补充验证:检查 `.class` 是否包含调试字符串 ```bash # 解压看看 unzip -p restapi.jar RestApiPlugin.class | strings | grep PLUGIN-DEBUG ``` 你应该看到: ``` [PLUGIN-DEBUG] 💡 静态块执行:RestApiPlugin 类已被 JVM 加载! [PLUGIN-DEBUG] 📦 类资源位置: ... [PLUGIN-DEBUG] 🆕 构造函数执行:RestApiPlugin 实例已创建 [PLUGIN-DEBUG] 🎉 插件初始化成功!插件目录: ... [PLUGIN-DEBUG] 🔌 插件已销毁 ``` 如果有,说明打包没问题。 --- ## 🧩 总结 | 问题 | 原因 | 解决方案 | |------|------|-----------| | ❌ 没有调试日志输出 | `cat << EOF` 粘贴时发生断行错误 | 使用 `echo -e` 或手动输入避免断裂 | | ⚠️ `NullPointerException` | Openfire 尝试卸载一个加载失败的插件 | 是结果不是原因,忽略它 | | 🔁 插件被反复标记“已删除” | 实际是每次扫描都尝试加载失败 | 修复源码后自动消失 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值