Beanshell

Zastosowania

Uogólnienie pliczków konfiguracyjnych.

Ogólnie pliki konfiguracyjne mają dwie wady:

  • Każdy jest de facto swoim własnym językiem, którego trzeba się nauczyć. (do dziś nie wiem jak działa plik log4j.properties - ten nie xmlowy)
  • Prędzej czy później trzeba zrobić coś co będzie trudne/niewykonalne w ramach danego pliku konfiguracyjnego.
  • Ciężko zwalidować składnie.

Więc czasem po prostu warto olać pliczki napisać w Eclipsie kawałek kodu który robi inicjalizacje (korzystając z wszystkich cudów tego ide), a potem po chamsku przekleić do beanshella.

Zalety:

  • Nie trzeba się uczyć jezyka.
  • Z reguły to co można łatwo ustawić w kodzie jest nadzbiorem tego co można ustawić w pliku konfiguracyjnym.
  • Łatwo zwalidować składnie jeszcze w czasie testów jednostkowych. To chwila napisać klasę jUnita która to przeleci wszystkie pliki skryptowe i spróbuje wykonać i zobaczy czy żaden wyjątek nie poleciał

Wrzucenie funkcjonalności skryptowej do aplikacji.

Zalety:

  • Nie trzeba tworzyć silnika skryptów.
  • Jest potężny,

Wady:

  • Jest za potężny (może zrobić coklowiek z kodem). Nie opracowałem jeszcze jak się ogranicza np. zbior klas do których jest dostęp z poziomu skryptu.
  • No jednak wymaga znajomości javy.

Tutorial

Sam beanshell jest tragicznie prosty w obsłudze. Nauczyć się trzeba jednej klasy [bsh.Interpreter. Główną funkcją jest eval:

Object eval(String str);

eval zinterpretuje to co zostanie jej podane w argumencie i ew. jeśli coś będzie można potraktować jako wynik zwróci go.

Na przykład:

System.out.println(interpreter.eval("2+2"));

Wypisze cztery.

Czasem nie interesuje nas wynik ale skutki uboczne np:

interpreter.eval("System.out.println(2+2)");

Też wypisze 4.

W ramach eval można podawać też funkcje z których potem można korzystać:

interpreter.eval("" +
                "public void sysout(String str){" +
                "    System.out.println(str);" +
                "}");
interpreter.eval("sysout(\"\" + (2+2));");

Zawartość zmiennych zdefiniowanych w interpreterze można pobierać:

interpreter.eval("int ii = 2+2");
System.out.println(interpreter.get("ii"));

I ustawiać:

interpreter.set("ii", 44);
interpreter.eval("sysout(\"\" + ii);");

Zarówno ustawianie i pobieranie pozwala na wyznaczanie skomplikowanych ścieżek (patrz:beans properties).

interpreter.eval("JLabel foo = new JLabel();");
interpreter.set("foo.text", "bar");
interpreter.eval("sysout(foo.getText());");

Wyciąganie interfejsów

Prawdziwym powerem Beanshella jest wyciąganie z interoretera interfejsów. Idea jest taka że definiujemy metody klasy która jest implementacją jakiegoś interfejsu i:

    Interpreter interpreter = new Interpreter();
        interpreter.eval("" +
                "public void actionPerformed(ActionEvent e) {"+
                "System.out.println(\"FOO!\");" +
                "}");
        ActionListener foo = (ActionListener) interpreter.getInterface(ActionListener.class);
        foo.actionPerformed(null);
        foo = (ActionListener) interpreter.eval("(ActionListener) this");
        foo.actionPerformed(null);

W ten sposób dostajemy z BeanShella coś co możemy wrzucić do dowlnego komponentu który nie musi umieć obsługiwać BeanShella.

Wydajność

Jeśli podamy w eval definicję funkcji albo klasy to zostanie ona podczas pierwszego wykonania sparsowana do AST (Abstract Syntax Trees) czyli obiektowej reprezentacji kodu źródłowego. Przy wykonaniach kod źródłowy nie jest wykonywany - uzywa się już samych AST. Czyli jest szybciej.

Wielowątkowość

Co więcej AST są thread-safe czyli można je bezpiecznie wołać z wielu wątków. ALE: sam interpreter nie jest thread-safe! tj. jeśli wywołasz na nim metodę set() to ustawisz pole we wszystkich wątkach.

Czyli albo rezygnujesz z przekazywania do interpretera parametrów, albo: wyciagasz z Interpretera interfejs i (o ile w samym kodzie interpretowanym nie będzie fackupów) będzie OK, a parametry podajesz jako parametry wywołania metod tego interfejsu.

TODO

O ile nie zaznaczono inaczej, treść tej strony objęta jest licencją Creative Commons Attribution-NonCommercial-NoDerivs 3.0 License