BeanShell语法学习提要

简介

BeanShell是一个小巧免费的JAVA源码解释器,支持对象式的脚本语言特性,亦可嵌入到JAVA源代码中,能动态执行JAVA源代码并为其扩展了脚本语言的一些特性,像JavaScript和perl那样的弱类型、命令式、闭包函数等等特性都不在话下。

摘自“度娘”知道

本文一方面当作BeanShell的学习笔记使用,另一方面能够帮助更多的Java程序员快速地上手BeanShell。内容中不涉及BeanShell各种高级货,仅限语法提要,仅为语法过度之参考。语言完整精要部分请参考http://www.beanshell.org。在线文档地址:http://www.beanshell.org/docs.html。另外还有PDF版本的bshmanual.pdf可供下载阅读。

环境准备

为了快速搭建一个可以运行的实验环境,我使用了一个带有Maven插件的eclipse,主要用于依赖下载。具体操作为:File/New/Maven Project,然后在向导画面中勾选Create a simple project (skip archetype selection),点击Next,然后分别填写好Group IdArtifact Id之后,点击Finish按钮,至此你已经完成了项目创建。

项目创建之后可以仿照下面的pom.xml来设置BeanShell依赖,然后通过鼠标右键点击项目图标,依次选择Maven/Update Project...即可完成项目依赖下载。

<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.0modelVersion>
    <groupId>info.woody.beanshellgroupId>
    <artifactId>tutorialartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <dependencies>
        <dependency>
            <groupId>org.beanshellgroupId>
            <artifactId>bshartifactId>
            <version>2.0b5version>
        dependency>
    dependencies>
project>

下面是整个项目的完整目录结构:

tutorial
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── info
│   │   │       └── woody
│   │   │           └── tutorial
│   │   │               ├── Main.java
│   │   │               ├── Person.java
│   │   │               ├── convenience_syntax.bsh
│   │   │               ├── defalut_import.bsh
│   │   │               ├── enhanced_for.bsh
│   │   │               ├── integration.bsh
│   │   │               ├── loose_type.bsh
│   │   │               ├── main.bsh
│   │   │               └── script_method.bsh
│   │   └── resources
│   └── test
│       ├── java
│       └── resources
└── target
    ├── classes
    │   └── info
    │       └── woody
    │           └── tutorial
    │               ├── Main.class
    │               ├── Person.class
    │               ├── convenience_syntax.bsh
    │               ├── defalut_import.bsh
    │               ├── enhanced_for.bsh
    │               ├── integration.bsh
    │               ├── loose_type.bsh
    │               ├── main.bsh
    │               └── script_method.bsh
    └── test-classes

初识BeanShell

项目和依赖全部准备成功之后,我们可以正式地接触下BeanShell了,这里一方面会使用到下面的Person作为实验辅助类,另一方面需要使用到Java的ScriptEngine技术。

package info.woody.tutorial;

public class Person {

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void speak() {
        System.out.format("My name is %s, and I'm %s years old.".concat(System.lineSeparator()), name, age);
    }
}

为了循序渐进地学习,实验将分为三个阶段:初识、语法认知、综合运用。这三个阶段分别会可以用下面的Java代码表现。接下来我们就具体看看test()方法的作用。

public static void main(String[] args) throws ScriptException, IOException {
    test();
    testAll();
    testIntegration();
}

test()方法中,程序先是获取了一个BeanShell执行引擎对象bshEngine,然后利用它直接执行BeanShell语法编写的脚本程序:

private static void test() {
    ScriptEngine bshEngine = new ScriptEngineManager().getEngineByExtension("bsh");
    try {
        bshEngine.eval("print(\"Hello Woody\");");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

上面程序执行结束后,会输出Hello Woody字样。也就说,程序已经成功地完成了从Java平台中运行BeanShell的工作。那接下来,我们就可以深入语法细节部分了。

语法认知

这部分的学习任务将由testAll()来引导,每个语法要点都单独形成一个测试程序,然后由testAll()方法统一执行。实验过程中将有六个要执行的BeanShell文件,每次我们都从对应的程序中读出文件然后执行其中的BeanShell程序,内容读取以及程序执行的任务由方法executeBsh(String fileName)来承担。

private static void testAll() {
    String[] scriptFileNames = {
            "main.bsh              ",
            "convenience_syntax.bsh",
            "defalut_import.bsh    ",
            "enhanced_for.bsh      ",
            "loose_type.bsh        ",
            "script_method.bsh     "
        };

    for (String name : scriptFileNames) {
        System.out.println();
        System.out.format("<=== %s ===>".concat(System.lineSeparator()), name.trim());
        executeBsh(name.trim());
    }
}

private static void executeBsh(String fileName) {
    ScriptEngine bshEngine = new ScriptEngineManager().getEngineByExtension("bsh");
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(
            Main.class.getResourceAsStream("/info/woody/tutorial/".concat(fileName))))
    ) {
        StringBuilder bshBuilder = new StringBuilder();
        String s;
        while ((s = reader.readLine()) != null) {
            bshBuilder.append(s).append(System.lineSeparator());
        }
        String scriptText = bshBuilder.toString();
        if (debug) {
            System.out.println(scriptText);
            System.out.println();
        }
        bshEngine.eval(scriptText);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

main.bsh是第一个要执行的程序,它和上一节test()方法中执行的BeanShell一样,都是输出一行文本内容,与Java相比你不需要书写对应的类名,只需要直接调用print ( String )即可。而且你不需要构建任何程序结构,完全像是置身于Java的main方法之中,直接进入程序结构省去了外围的类结构和方法结构,直奔编码环节。

// Main
print ("hello world");

这里需要注意的是,BeanShell的方法print()相当于Java的System.out.println()

接下来是变量定义和属性访问部分,实验文件为convenience_syntax.bsh

变量定义的时候你只需要像下面那样var variable_name = assigned_value;即可,无需像Java那样明确指定类型,好处是以后你可以完全与类型有关的声明了,因为BeanShell会自动为你发现某个对象的类型,你需要负责的是正确地调用它的方法就可以了。

属性访问支持花括号,另一方面允许你利用字段名来替代JavaBeanget方法,算是语法糖吧,用起来较为方便。

// Convenience Syntax

import info.woody.tutorial.*;

var p = new Person("Woody", 2);
p.speak();

p{"name"} = "Buzz Lightyear";
print(p{"name"});

p.age = 3;
print(p.age);

p.speak();

默认导入 —— 这个和import java.lang.*含义一样,对于常用的一些包,BeanShell已经为你导入了,直接写code就可以啦!

// Default Imports

print ( "javax.swing.event".trim() ); 
print ( "javax.swing      ".trim() );
print ( "java.awt.event   ".trim() );
print ( "java.awt         ".trim() );
print ( "java.net         ".trim() );
print ( "java.util        ".trim() );
print ( "java.io          ".trim() );
print ( "java.lang        ".trim() );
print ( "                 ".trim() );
print ( "bsh.EvalError    ".trim() );
print ( "bsh.Interpreter  ".trim() );

var h = new Hashtable();
h{"foo"} = "bar";
print(h{"foo"}.concat(h.get("foo")));

和上面提到的JavaBean属性访问一样,Map也支持花括号式的数据访问。

Java 5引入的增强版for语句在BeanShell里也是支持的。

// Enhanced 'for' Loop
var array = new int [] { 1, 2, 3 };

for ( i : array )
    print(i);

关于类型,除了变量定义时需要指明你想创建哪个对象之外,其余时候你完全可以把它忘记。也即是说:

  1. 你再也不需要强制类型转换了

  2. 变量想跟哪个实例绑定就跟哪个实例绑定

  3. 捕获异常的时候catch (e)就能替代catch (Exception e)

  4. 方法定义时,形式参数和返回值的定义也没有类型啥事儿了

  5. 既然类型概念都被去掉了,就不要再提范型了(BeanShell不支持范型语法,因为压根儿就没必要了)

// Loosely Typed Java Syntax

import info.woody.tutorial.*;

var p = new Person("Flash", 100);

try {
    x = 1 / 0;
} catch ( e ) {
    print( "caught exception: " + e );
}

为了能够重复利用代码,BeanShell是支持方法定义的,参考如下:

// Scripted Methods
add( a, b ) {
    return a + b;
}

print( add( 1, 2 ) );

综合运用

最后再来看一个Java与BeanShell交互的例子。

@SuppressWarnings("unchecked")
private static void testIntegration() throws ScriptException, IOException {
    Random random = new Random();
    List persons = new ArrayList<>();
    persons.add( new Person("Rick    ".trim(), 12 + random.nextInt(48)) );
    persons.add( new Person("Glenn   ".trim(), 12 + random.nextInt(48)) );
    persons.add( new Person("Carl    ".trim(), 12 + random.nextInt(48)) );
    persons.add( new Person("Daryl   ".trim(), 12 + random.nextInt(48)) );
    persons.add( new Person("Carol   ".trim(), 12 + random.nextInt(48)) );
    persons.add( new Person("Maggie  ".trim(), 12 + random.nextInt(48)) );
    persons.add( new Person("Michonne".trim(), 12 + random.nextInt(48)) );

    ScriptEngine bshEngine = new ScriptEngineManager().getEngineByExtension("bsh");
    Bindings bindings = bshEngine.createBindings();
    bindings.put("persons", persons);

    String scriptName = "/info/woody/tutorial/integration.bsh";
    try (Reader reader = new BufferedReader(
            new InputStreamReader(Main.class.getResourceAsStream(scriptName)))
    ) {
        bshEngine.eval(reader, bindings);
    } catch (Exception e) {
        e.printStackTrace();
    }

    System.out.println("persons in ".concat(Main.class.getName()));
    persons = (List)bindings.get("persons");
    for (Person person : persons) {
        person.speak();
    }
}

上面的Java代码先是构建了一个Person集合,并随机指派了不同的年龄。然后调用了下面的BeanShell程序为这些Person进行排序,排序完成后再次在Java中将每个Person的年龄打印出来。

print("");
print("INTEGRATION.BSH");
print("");

dump (persons) {
    for (var p : persons) {
        //print ( person.name );
        p.speak();
    }
}

print ("before sort");

dump(persons);

Collections.sort(persons, new Comparator () {
    compare ( p1, p2) {
        new Integer(p1.age).compareTo(p2.age);
    }
});

print ("after sort");

dump(persons);

程序执行之后输出的结果如下:

INTEGRATION.BSH

before sort
My name is Rick, and I'm 12 years old.
My name is Glenn, and I'm 25 years old.
My name is Carl, and I'm 51 years old.
My name is Daryl, and I'm 37 years old.
My name is Carol, and I'm 19 years old.
My name is Maggie, and I'm 43 years old.
My name is Michonne, and I'm 30 years old.
after sort
My name is Rick, and I'm 12 years old.
My name is Carol, and I'm 19 years old.
My name is Glenn, and I'm 25 years old.
My name is Michonne, and I'm 30 years old.
My name is Daryl, and I'm 37 years old.
My name is Maggie, and I'm 43 years old.
My name is Carl, and I'm 51 years old.
persons in info.woody.tutorial.Main
My name is Rick, and I'm 12 years old.
My name is Carol, and I'm 19 years old.
My name is Glenn, and I'm 25 years old.
My name is Michonne, and I'm 30 years old.
My name is Daryl, and I'm 37 years old.
My name is Maggie, and I'm 43 years old.
My name is Carl, and I'm 51 years old.

总结

  1. 无需考虑任何结构,我们可以直接进入编码阶段
  2. System.out.println直接由print取代
  3. Bean的访问支持花括号或是字段名,不需要get方法了
  4. 常用的包都自动导入了,最重要的就是java.util.*
  5. for ( : )是支持的
  6. 强制类型的不需要,声明时类型不需要,try ... catch (e) ...的类型不需要,各种类型不需要,不需要,不需要 ……
  7. 可以把反复执行的代码包装成方法,而且还是不需要为形式参数和返回值指明类型
  8. 由宿主语言Java传入的变量可以直接用,修改后的数据从Java端可以直接读

项目下载关键字:BeanShellSyntaxTutorial。

你可能感兴趣的:(Java,杂七杂八)