Apache Commons BSF 2.4.0 快速入门与集成示例

Apache Commons BSF 2.4.0 快速入门,含 Maven 工程搭建、JavaScript/BeanShell/Jython 多引擎集成、对象双向共享、jar 直跑、引擎依赖速查与 JDK 17/8 双实测结论。

Apache Commons BSFApache Commons BSF2026/10/15

Apache Commons BSF 2.4.0 快速入门与集成示例

目标:以 Apache Commons BSF 2.4.0 为例,从零完成工程搭建与最小集成,跑通 JavaScript(Rhino)、BeanShell、Jython 三个脚本引擎的表达式求值、函数回调与 Java 对象双向共享,并给出 JDK 17 与 JDK 8 的双实测兼容性结论;本文全部代码与回显均实测通过。

Apache Commons BSF(Bean Scripting Framework)的核心价值:给 Java 应用一个统一的脚本执行门面——不管底层是 JavaScript、Python 还是 BeanShell,调用方都面对同一套 BSFManager/BSFEngine 接口。它同时解决了三件事:按语言名统一求值入口(eval/exec)、把 Java 对象暴露给脚本(declareBean)、把脚本函数暴露给 Java(engine.call)。BSF 目前为 Dormant(休眠)状态,最新稳定版即本文实测的 2.4.0(2006-10-06 构建),坐标 bsf:bsf:2.4.0。本文在一台 Ubuntu 22.04 服务器(OpenJDK 17 + Maven 3.6.3)上完成全部验证,环境与安装步骤见专栏安装篇。

一、环境信息

本节固定实测基线,所有代码与回显均来自该环境。

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • JDK:OpenJDK 17.0.20.1(另在 OpenJDK 1.8.0_504 上复测,见第七章)
  • 构建工具:Apache Maven 3.6.3
  • 组件:Apache Commons BSF 2.4.0(bsf:bsf:2.4.0,传递依赖 commons-logging:1.0.4)
  • 脚本引擎:Rhino 1.7R2(JavaScript)、BeanShell 2.0b4、Jython 2.2.1
  • 工程目录:/root/bsf-demo
  • 版本自证:BSFManager.getVersion() 实测返回 240.20061006

安装前置(JDK/Maven/依赖引入的完整步骤)见专栏《Ubuntu 22.04 安装 Apache Commons BSF 指南》,本文从「依赖已在 pom 中声明」直接开始。

二、十分钟最小可用

本节用一个可运行的工程把 BSF 的核心闭环跑起来:pom.xml + 一个 BsfVerify.java + 一条编译命令 + 一条运行命令。

2.1 工程与依赖

工程采用标准 Maven 布局(目录 /root/bsf-demo),pom.xml 关键内容如下(完整文件见安装篇 4.4 节):

<dependencies>
  <dependency>
    <groupId>bsf</groupId>
    <artifactId>bsf</artifactId>
    <version>2.4.0</version>
  </dependency>
  <dependency>
    <groupId>rhino</groupId>
    <artifactId>js</artifactId>
    <version>1.7R2</version>
  </dependency>
</dependencies>

依赖关系实测(mvn -B dependency:tree 回显):

[INFO] +- bsf:bsf:jar:2.4.0:compile
[INFO] |  \- commons-logging:commons-logging:jar:1.0.4:compile
[INFO] \- rhino:js:jar:1.7R2:compile

要点:BSF 本体自带引擎注册表(语言名 → 引擎类),但引擎实现类不在 bsf.jar 里,运行期必须额外提供目标语言的引擎 jar。JavaScript 对应 rhino:js:1.7R2,其他语言的对应关系见第六章速查表。

2.2 最小验证代码

src/main/java/BsfVerify.java(完整内容):

import org.apache.bsf.BSFEngine;
import org.apache.bsf.BSFManager;

public class BsfVerify {
    public static void main(String[] args) throws Exception {
        System.out.println("BSF version = " + BSFManager.getVersion());
        BSFManager mgr = new BSFManager();
        Object r1 = mgr.eval("javascript", "<inline>", 1, 1, "1 + 2 * 3");
        System.out.println("eval '1 + 2 * 3' = " + r1 + " (" + r1.getClass().getName() + ")");
        Object r2 = mgr.eval("javascript", "<inline>", 1, 1, "'Apache BSF'.toUpperCase()");
        System.out.println("eval toUpperCase = " + r2);
        mgr.declareBean("name", "Ubuntu 22.04", String.class);
        Object r3 = mgr.eval("javascript", "<inline>", 1, 1, "'BSF on ' + name");
        System.out.println("JS reads name    = " + r3);
        mgr.exec("javascript", "<inline>", 1, 1, "function add(a, b) { return a + b; }");
        BSFEngine engine = mgr.loadScriptingEngine("javascript");
        Object r4 = engine.call(null, "add", new Object[]{7, 35});
        System.out.println("engine.call add  = " + r4);
        mgr.terminate();
        System.out.println("BsfVerify OK");
    }
}

逐行拆解四个核心动作:

  1. new BSFManager():创建脚本管理器,一个实例持有一组已加载的引擎与一个对象注册表,线程内复用、用完 terminate() 释放。
  2. mgr.eval("javascript", "<inline>", 1, 1, script):求值脚本并取返回值。第 1 参语言名,第 2 参来源名(仅用于错误信息,可写 <inline>),第 3/4 参行列号(从 1 起),第 5 参是脚本内容(String 或 Reader)。
  3. mgr.declareBean("name", value, type):把 Java 对象注册为脚本全局变量,脚本内直接引用变量名 name。
  4. engine.call(null, "add", args):调用脚本中已定义的函数,第 1 参传 null 表示在全局对象上调用。注意函数调用走 BSFEngine.call 而不是 BSFManager.apply,原因见第四章。

2.3 编译与运行

cd /root/bsf-demo && mvn -q compile && echo "EXIT=$?"
EXIT=0
R=~/.m2/repository
java -Dfile.encoding=UTF-8 -cp target/classes:$R/bsf/bsf/2.4.0/bsf-2.4.0.jar:$R/commons-logging/commons-logging/1.0.4/commons-logging-1.0.4.jar:$R/rhino/js/1.7R2/js-1.7R2.jar BsfVerify
BSF version = 240.20061006
eval '1 + 2 * 3' = 7 (java.lang.Integer)
eval toUpperCase = APACHE BSF
JS reads name    = BSF on Ubuntu 22.04
engine.call add  = 42.0
BsfVerify OK

验证点:四项功能全部符合预期——求值返回 Integer、字符串方法可用、Java 对象注入后脚本可读、脚本函数回调返回 Double 42.0。至此 BSF 集成最小闭环完成。

💡 技术拓展:除了 BSF 的 BSFManager 门面,Java 生态还有两条脚本集成路线——JSR-223(javax.script,JDK 6 起内置)与各引擎自带的直接 API(如直接 new Context() 用 Rhino)。三者对比:

路线入口语言覆盖状态
BSF(本文)BSFManager注册表内 23 个语言名,2.4.0 内置 5 个引擎实现Dormant,经典遗留系统常用
JSR-223ScriptEngineManager依 provider 而定(Nashorn/Rhino/Jython 等)JDK 标准,新项目首选
引擎直接 APIRhino Context 等单一语言灵活但与引擎绑定

本文所用为 BSF 路线,与 JSR-223 的桥接互操作见专栏第 5 篇。

三、第一个多语言程序

本节把三个引擎(JavaScript、BeanShell、Jython)放进同一个程序,验证「同一套 API、多语言运行」的 BSF 核心卖点,同时完成 Java 对象与三种脚本语言的双向操作。

3.1 补齐引擎依赖

在 2.1 节依赖基础上追加 BeanShell 与 Jython:

<dependency>
  <groupId>org.beanshell</groupId>
  <artifactId>bsh</artifactId>
  <version>2.0b4</version>
</dependency>
<dependency>
  <groupId>org.python</groupId>
  <artifactId>jython</artifactId>
  <version>2.2.1</version>
</dependency>

版本说明:org.beanshell:bsh:2.0b4 自带 BSF 引擎适配类 bsh.util.BeanShellBSFEngine(与 BSF 内置注册表对接);org.python:jython:2.2.1 是 Jython 2.2.1 的中央仓库坐标(注意不是 jython:jython,后者只到 2.2-beta1),实测在 JDK 8 与 JDK 17 上均可运行。

3.2 多语言验证代码

src/main/java/BsfMulti.java(完整内容):

import org.apache.bsf.BSFManager;

import java.util.Arrays;
import java.util.Vector;

public class BsfMulti {

    public static class Counter {
        private int n;
        public void inc(int d) { n += d; }
        public int get() { return n; }
    }

    public static void main(String[] args) throws Exception {
        BSFManager mgr = new BSFManager();

        Object r1 = mgr.eval("javascript", "<inline>", 1, 1, "1 + 2 * 3");
        System.out.println("javascript eval  = " + r1 + " (" + r1.getClass().getName() + ")");

        Object r2 = mgr.eval("beanshell", "<inline>", 1, 1, "1 + 2 * 3");
        System.out.println("beanshell eval   = " + r2 + " (" + r2.getClass().getName() + ")");

        Object r3 = mgr.eval("jython", "<inline>", 1, 1, "sum(range(1, 101))");
        System.out.println("jython eval      = " + r3 + " (" + r3.getClass().getName() + ")");

        Counter counter = new Counter();
        mgr.declareBean("counter", counter, Counter.class);
        mgr.exec("javascript", "<inline>", 1, 1, "counter.inc(10); counter.inc(32);");
        System.out.println("JS inc 10+32     = " + counter.get());

        Counter counter2 = new Counter();
        mgr.declareBean("counter", counter2, Counter.class);
        mgr.exec("beanshell", "<inline>", 1, 1, "counter.inc(40); counter.inc(2);");
        System.out.println("bsh inc 40+2     = " + counter2.get());

        Counter counter3 = new Counter();
        mgr.declareBean("counter", counter3, Counter.class);
        mgr.exec("jython", "<inline>", 1, 1, "counter.inc(50)\ncounter.inc(2)");
        System.out.println("jython inc 50+2  = " + counter3.get());

        mgr.exec("javascript", "<inline>", 1, 1, "function add(a, b) { return a + b; }");
        Object r4 = mgr.loadScriptingEngine("javascript").call(null, "add", new Object[]{3, 4});
        System.out.println("JS add(3, 4)     = " + r4 + " (" + r4.getClass().getName() + ")");

        mgr.terminate();
    }
}

代码要点:三个引擎用同一 BSFManager 调度,语言差异只在第 1 个参数;declareBean 注入的 Java 对象在三种语言里都能直接调用方法(JavaScript 用分号、BeanShell 是 Java 语法、Jython 用换行缩进);Jython 的多行脚本用 \n 传给 exec。

3.3 运行与验证

cd /root/bsf-demo && mvn -q compile
R=~/.m2/repository
java -Dfile.encoding=UTF-8 -Dpython.cachedir=/tmp/jython-cache -cp target/classes:$R/bsf/bsf/2.4.0/bsf-2.4.0.jar:$R/commons-logging/commons-logging/1.0.4/commons-logging-1.0.4.jar:$R/rhino/js/1.7R2/js-1.7R2.jar:$R/org/beanshell/bsh/2.0b4/bsh-2.0b4.jar:$R/org/python/jython/2.2.1/jython-2.2.1.jar BsfMulti
javascript eval  = 7 (java.lang.Integer)
beanshell eval   = 7 (java.lang.Integer)
jython eval      = 5050 (org.python.core.PyInteger)
JS inc 10+32     = 42
bsh inc 40+2     = 42
jython inc 50+2  = 52
JS add(3, 4)     = 7.0 (java.lang.Double)

结果解读:Rhino 与 BeanShell 的算术求值返回 Integer,Jython 返回自己的 PyInteger(BSF 统一了入口、保留了各引擎的原生返回类型);Java 对象在三种脚本里均可直接调用 inc() 修改内部状态,Java 侧 get() 读到的就是脚本改过的值,双向共享成立。-Dpython.cachedir=/tmp/jython-cache 是给 Jython 指定包缓存目录的 JVM 参数,缺省时会在 jar 同目录建缓存,打印 can't create package cache dir 警告(不影响正确性,见 FAQ)。

四、集成常见坑(实测实录)

本节收录集成过程中真实踩到的四个坑,全部附实测现象与修复方式。

坑 1:eval 第 5 参传 null 直接 NPE。 BSF 2.4.0 各引擎在求值前直接对第 5 参调用 toString(),传 null 抛 java.lang.NullPointerException(实测栈指向 JavaScriptEngine.eval 第 125 行)。脚本内容必须作为第 5 参传入;第 2 参 source 只是来源名,不要误把脚本放第 2 参。

坑 2:BSFManager.apply 对函数调用「只执行函数体、忽略参数」。 实测 mgr.apply("javascript", "<inline>", 1, 1, "function add(a, b) { return a + b; }", args, names) 返回 org.mozilla.javascript.Undefined——BSF 2.4.0 的 BSFEngineImpl.apply 默认实现只把函数体当脚本求值,参数与函数名不参与调用。Rhino 与 BeanShell 引擎均未覆写 apply。正确姿势:mgr.loadScriptingEngine("javascript") 取引擎对象后调 engine.call(null, "add", new Object[]{7, 35}),实测返回 42.0。

坑 3:BeanShell 的 apply 触发 ArrayStoreException。 即使给 bsh.util.BeanShellBSFEngine 传正确参数,mgr.apply 也在引擎内部 Vector.copyInto(String[]) 处抛 ArrayStoreException(bsh 2.0b4 引擎代码的真实缺陷,实测复现)。绕过方式同坑 2:改用 engine.call,实测 call(null, "add", new Object[]{3, 4}) 返回 Integer 7。

坑 4:JDK 17 编译的类在 JDK 8 上运行报版本错误。 实测把 JDK 17 编译产物放到 JDK 8 运行:

Error: UnsupportedClassVersionError: BsfVerify has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0

61.0 是 JDK 17、52.0 是 JDK 8。修复:给目标 JDK 8 编译时把 Maven 属性改为 <maven.compiler.release>8</maven.compiler.release>,或直接在 JDK 8 下重编。bsf.jar 自身是 major=46(Java 1.2)字节码,不参与此问题。

五、无构建环境:官方 bin 包集成

没有 Maven 的服务器上,用官方 bsf-bin-2.4.0.tar.gz 加中央仓库补齐 jar 也能完整集成,编译运行两条命令搞定(下载与 sha256 校验步骤见专栏安装篇,此处从解压后开始)。

mkdir -p /root/jar-run && cd /root/jar-run
cp /root/bsf-2.4.0/lib/bsf.jar .
cp /root/bsf-demo/src/main/java/BsfVerify.java .
curl -sO --max-time 60 https://repo1.maven.org/maven2/rhino/js/1.7R2/js-1.7R2.jar
curl -sO --max-time 60 https://repo1.maven.org/maven2/commons-logging/commons-logging/1.0.4/commons-logging-1.0.4.jar
mkdir -p out
javac -encoding UTF-8 -cp bsf.jar -d out BsfVerify.java
java -Dfile.encoding=UTF-8 -cp out:bsf.jar:js-1.7R2.jar:commons-logging-1.0.4.jar BsfVerify
BSF version = 240.20061006
eval '1 + 2 * 3' = 7 (java.lang.Integer)
eval toUpperCase = APACHE BSF
JS reads name    = BSF on Ubuntu 22.04
engine.call add  = 42.0
BsfVerify OK

验证点:与 Maven 方案输出完全一致。jar 直跑场景 classpath 的完整清单就是「bsf.jar + 语言引擎 jar + commons-logging-1.0.4.jar + 编译输出目录」,多一个不多、少一个报 NoClassDefFoundError。

六、脚本引擎依赖速查表

BSF 2.4.0 内置注册表(jar 内 org/apache/bsf/Languages.properties)共登记 23 个语言名,实际可用的引擎取决于 classpath 上有没有对应实现。实测可跑通的四个引擎与依赖坐标如下:

语言名引擎类(bsf.jar 内)引擎实现依赖(Maven 坐标)实测结论
javascriptorg.apache.bsf.engines.javascript.JavaScriptEnginerhino:js:1.7R2表达式/函数/对象共享全通,推荐
beanshellbsh.util.BeanShellBSFEngine(由 bsh jar 提供)org.beanshell:bsh:2.0b4eval/exec/call 全通;apply 有缺陷
jythonorg.apache.bsf.engines.jython.JythonEngineorg.python:jython:2.2.1eval/exec/对象共享全通;建议设 python.cachedir
xsltorg.apache.bsf.engines.xslt.XSLTEnginexalan:xalan:2.7.2输入输出可通;参数传递在 2.7.2 下失效(见第 4 篇)

注册表里其余语言名(如 jacl、netrexx、groovy、ruby 等)仅登记了历史引擎类名,对应实现依赖(Jacl、NetRexx、Groovy 1.x、JRuby 0.9 等)均已停止维护,未在本文实测范围内,生产上不建议引入。

💡 技术拓展:同为「Java 调脚本」需求,引擎选择的关键差异:

引擎语言成熟度与 BSF 2.4.0 的适配
Rhino 1.7R2JavaScript高(浏览器级引擎)内置引擎类直接适配,实测首选
BeanShell 2.0b4Java 语法脚本中(轻量解释器)引擎类在 bsh jar 内,apply 有缺陷
Jython 2.2.1Python中(Python 2.2 语法)引擎类适配完整,需配 cachedir

本文集成示例以 JavaScript(Rhino)为主线,因其在依赖体积、稳定性与功能完整度上表现最好。

七、JDK 兼容性实测结论

BSF 2.4.0 是 2006 年的产物,能否在新 JDK 上运行是选型的第一疑问。本节给出一台机器上的双 JDK 实测。

实测 1:字节码版本。 bsf-2.4.0.jar 主版本号 major=46,即 Java 1.2 字节码,属于所有现代 JDK 都兼容的最低档。

实测 2:OpenJDK 17.0.20.1。 第二章、第三章全部示例在该 JDK 上编译运行通过(回显如各节所示),包括 Rhino 1.7R2、BeanShell 2.0b4、Jython 2.2.1 三个引擎的求值、函数调用与对象共享。

实测 3:OpenJDK 1.8.0_504。 同一套源码用 JDK 8 重新编译(javac -cp "libs/*" -d classes8)后运行第三章的 BsfMulti,实测输出与 JDK 17 完全一致:

javascript eval  = 7 (java.lang.Integer)
beanshell eval   = 7 (java.lang.Integer)
jython eval      = 5050 (org.python.core.PyInteger)
JS inc 10+32     = 42
bsh inc 40+2     = 42
jython inc 50+2  = 52
JS add(3, 4)     = 7.0 (java.lang.Double)

结论: BSF 2.4.0 + Rhino 1.7R2/BeanShell 2.0b4/Jython 2.2.1 组合在 JDK 8 与 JDK 17 上均可正常工作;跨 JDK 迁移时唯一要管住的是「用哪个 JDK 编译你的业务代码」(见坑 4)。JDK 8 上另有内置的 Nashorn 引擎可走 JSR-223 路线(见专栏第 5 篇的实测对比)。

八、参考链接

  1. Apache Commons BSF 官方主页:https://commons.apache.org/bsf/
  2. BSF 2.4.0 官方二进制包:https://archive.apache.org/dist/commons/bsf/binaries/bsf-bin-2.4.0.tar.gz
  3. Maven 中央仓库 bsf:bsf:2.4.0:https://repo1.maven.org/maven2/bsf/bsf/2.4.0/
  4. Rhino 1.7R2 坐标:https://repo1.maven.org/maven2/rhino/js/1.7R2/
  5. BeanShell 坐标:https://repo1.maven.org/maven2/org/beanshell/bsh/2.0b4/
  6. Jython 2.2.1 坐标:https://repo1.maven.org/maven2/org/python/jython/2.2.1/