Beispiel zu leicht anpassbaren Anwendungen mit Scripting

Thomas Darimont

Erfahrenes Mitglied
Hallo,

hier mal ein kleines Beispiel wie einfach man mit Scripting Java Anwendungen leicht anpassen kann ohne diese neu kompilieren zu müssen.
Hier wollen wir die Implementierung einer Methode des BusinessService durch ein Java Script script erledigen.

IBusinessService:
Java:
/**
 * 
 */
package de.tutorials.services;

public interface IBusinessService {
    void businessOperation1();

    void businessOperation2(int a, int b, String c);
}

BusinessService:
Java:
/**
 * 
 */
package de.tutorials.services.internal;

import de.tutorials.services.IBusinessService;

public class BusinessService implements IBusinessService {
    String data ="BUBU";

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
    
    @Override
    public void businessOperation1() {
        System.out.println("businessOperation1 " + data);
    }

    @Override
    public void businessOperation2(int aa, int b, String c) {
        System.out.println(String.format("businessOperation2 %s %s %s", getData(),(aa+b),c));
        
    }
}

ScriptAwareInvocationHandler:
Java:
/**
 * 
 */
package de.tutorials;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

class ScriptAwareInvocationHandler implements InvocationHandler {

	private final Object target;

	private final static String SCRIPTS_HOME = "scripts/";

	private final static String ARGUMENT = "ARG";

	private final ScriptEngine scriptEngine = new ScriptEngineManager()
			.getEngineByName("javascript");

	public ScriptAwareInvocationHandler(Object target) {
		this.target = target;

		for (Class<?> iface : target.getClass().getInterfaces()) {
			String scriptFileName = SCRIPTS_HOME
					+ iface.getName().replace('.', '/') + ".js";
			File scriptFile = new File(scriptFileName);
			if (scriptFile.exists()) {
				try {
					scriptEngine.eval(new FileReader(scriptFile));
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (ScriptException e) {
					e.printStackTrace();
				}
			}
		}
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {

		Object scriptedTarget = scriptEngine.getContext().getAttribute(
				method.getName());

		if (null != scriptedTarget) {
			this.scriptEngine.getContext().setAttribute("$this", getTarget(),
					ScriptContext.ENGINE_SCOPE);

			StringBuilder parameterNames = new StringBuilder();
			for (int argumentIndex = 0; argumentIndex < args.length; argumentIndex++) {
				String argumentName = ARGUMENT + argumentIndex;
				this.scriptEngine.getContext().setAttribute(argumentName,
						args[argumentIndex], ScriptContext.ENGINE_SCOPE);
				parameterNames.append(argumentName);
				if (argumentIndex + 1 < args.length) {
					parameterNames.append(",");
				}
			}

			return this.scriptEngine.eval(method.getName() + "("
					+ parameterNames + ")");
		} else {
			return method.invoke(getTarget(), args);
		}

	}

	public Object getTarget() {
		return target;
	}
}

ScriptedProxyExample:
Java:
/**
 * 
 */
package de.tutorials;

import java.lang.reflect.Proxy;

import de.tutorials.services.IBusinessService;
import de.tutorials.services.internal.BusinessService;

/**
 * @author Tom
 * 
 */
public class ScriptedProxyExample {

    /**
     * @param args
     */
    public static void main(String[] args) {
        IBusinessService businessService = (IBusinessService)Proxy.newProxyInstance(ScriptedProxyExample.class.getClassLoader(),
                new Class[] { IBusinessService.class },
                new ScriptAwareInvocationHandler(new BusinessService()));
        
        businessService.businessOperation1();
        businessService.businessOperation2(1,2,"aaa");
    }

}

im Verzeichnis scripts/de/tutorials/services/IBusinessService.js:
Javascript:
function businessOperation2(intValue0, intValue1, stringValue){
    print("businessOperation2 from script!" + $this.data + " " + (intValue0 + intValue1) + " " + stringValue );
}

Ausgabe:
Code:
businessOperation1 BUBU
businessOperation2 from script!BUBU 3 aaa

Das mal so auf die schnelle. Das Ganze ist natürlich nur ein proof of concept ;-)
Man kann mit diesem Ansatz Objekte komplett oder auch nur einzelne Methoden durch Script Implementierungen ersetzen ähnlich wie in der Dynamic Language Unterstützung im Spring Framework:
http://static.springframework.org/s...anguage.html#dynamic-language-final-notes-aop

Dieser Ansatz bringt ohne Zweifel natürlich eine Menge Sicherheitsrisiken (wie Beispielsweise das Einschleusen von Schadcode, etc.) mit sich,
über die man sich im klaren sein muss.



Viel Spaß beim rumspielen :)

Gruß Tom
 
Zurück