`
dengminhui
  • 浏览: 167933 次
  • 来自: ...
社区版块
存档分类
最新评论

动态执行Java脚本

阅读更多

1 背景
在我们的项目中,有时候在需要运行时获取一段脚本并执行其逻辑以灵活地实现业务需求。有人的第一想法就是脚本语言,我们当然可以在程序中内嵌一个Python解释器,然后在需要灵活变动的地方使用Python脚本实现我们的逻辑。但是这样做太“重量级”了,况且身为一个Java程序员,你不一定懂得Python的语法,又或者你忠于Java根本不愿把项目交托给其他语言。如果能够使用符合Java语法的脚本,而且不必嵌入任何解释器,那么这是不是一个完美的选择?

 

2 原理
Java本身具有足够的灵活性能让我们做到这一点,JVM可以在运行时动态编译Java源文件然后加载类,这是Java脚本动态获得生命力的基础。不过,动态编译的输入是一个完整的Java类的源文件,和javac工具一样,而我们要执行的仅仅是一段脚本。另外,这段Java脚本还需要有上下文环境,例如输入和输出,不然凭空写一段脚本没有任何意义,这就要费一番心思去设计了。要满足这些要求也不难,这里提供一个简单的实现方法,基本思路是生成一个临时类,将上下文变量声明为该类的成员,将脚本放在该类的excute()方法里,然后将这个临时类动态编译并执行excute()方法,即可使脚本生效。对于程序来说,动态编译的过程是透明的,其结果是准确获得了脚本的输出。

 

3 具体示例
3.1 需求描述
程序从数据库中取出了别名和姓名,而在界面上显示怎样的名字却不确定,因为这点会经常变动,有些场合显示别名和姓名的组合,有些场合仅显示姓名等等,为了适应这种灵活变化,需要将如何显示名称的逻辑作为一段脚本写在外部文件里,程序在运行时执行这段脚本得到名称然后显示。

3.2 解决过程
假设程序从数据库中取出了别名和姓名,分别存入变量

String alias = “万里独行”;
String name = “田伯光”;  

  

并从外部文件读取了脚本内容

String script = “displayName = aliasName + ':' + originalName”;  

 

从脚本里可以看到3个变量,分别是displayName、aliasName和originalName,这是我们默认提供给脚本的上下文变量环境,就像在Jsp页面里默认有request对象一样。

3.2.1 首先需要构造一个Java类的完整源代码
利用StringBuilder构造如下内容的字符串 String javaSource:

 

public class Temp
{ 
public String aliasName; 
public String originalName; 
public String displayName; 
public void excute()
{
    displayName = aliasName + ‘:’+ originalName; 
}
}

 


其中,excute()方法体的内容就是脚本内容,其余部分是固定的,将aliasName等3个变量声明为Temp类的成员变量,这样就为脚本代码构建了上下文环境(不然连编译都没法通过)。源代码内容构造完成之后,就要将它写入文件,文件名当然就是Temp.java了。

OutputStream os = new FileOutputStream("Temp.java");    
os.write(javaSource.getBytes());    
os.close();  

 

这样,就在当前目录产生了一个我们需要的Java源文件。

 

3.2.2 然后编译这个Java源文件和加载编译后的class

 

编译源文件:

 

String[] compileArgs = new String[] {"Temp.java"};    
com.sun.tools.javac.Main.compile(compileArgs);  

 

注意,使用com.sun.tools.javac.Main类必须为项目添加tools.jar包的引用,这个jar包在JDK根目录下的lib文件夹里,自Java1.4开始包含。如果你的项目用手工编译,则必须添加类路径参数 “-cp tools.jar”。

加载编译后的class:

URLClassLoader loader =    
    new URLClassLoader(new URL[]{new File(".").toURI().toURL()});   
Class<?> scriptClass = loader.loadClass("Temp"); 

 

3.2.3 然后向临时变量注入上下文变量环境
虽然刚才构造的Java源代码里面已经有了aliasName和originalName两个成员变量提供给脚本代码使用,但是这两个变量并没有实际意义的值,实际的值应该是从数据库里取出来的值,我们要想办法将这个值传递给脚本。

Object obj = scriptClass.newInstance();    
c.getDeclaredField("aliasName").set(obj, alias);    
c.getDeclaredField("originalName").set(obj, name);  

 

用类反射的方法将我们之前从数据库里取出来的别名和姓名赋值给Temp的实例obj,这样当执行脚本的时候,脚本里面所引用的aliasName和originalName就是实际中应用的值了。

3.2.4 最后是执行脚本代码并获得显示名称

c.getDeclaredMethod("excute").invoke(obj);    
String displayName = (String)c.getDeclaredField("displayName").get(obj);   

 
用类反射执行excute()方法实际上就是执行了外部脚本的逻辑,而外部脚本已将显示名称构造完成,所以取出临时变量的displayName属性即是我们需要的显示名称。这样,我们就实现了在运行时获取并执行外部Java脚本的需求,在一些场合下大大提高了应用程序的灵活性。当然,为了提高性能,不可能让程序每次都进行动态编译,可以将临时对象缓存起来,仅当脚本变化后才重新编译,不过这不属于本文的范畴,留待以后讨论。

4       改进
4.1 避免源文件冲突
我们可以通过构造唯一的路径或文件名来保证生成的Java源文件的唯一性,但是复杂的路径会增加后续编译和加载的复杂度,所以最好还是使用唯一的文件名。文件名可以用前缀+UUID的方式构造,也可以通过Java提供的临时文件来构造,临时文件能够保证唯一性。

//在当前目录构造前缀为Temp,扩展名为.java的临时文件    
File.createTempFile("Temp", ".java", new File("."));   

 

4.2 编译参数增强
Main.compile()的参数是一个String数组,它支持的参数和javac工具支持的参数一致,只要将javac命令行的每一个单词依次作为数组的元素即可,例如

String[] compileArgs =    
new String[] { "-d", “.”, “-cp”, “.;xx1.jar;xx2.jar”, "Temp.java"}; 

 

4.3 简化构造的源代码
如果脚本的上下文变量环境较多,那么构造源代码的过程就变得复杂低效易错,我们可以用继承的方式来消除这种复杂性,而且还可以用”多态”来代替一些类反射的使用,例如定义以下类:

public abstract class ScriptContext 
{ 
public Object context1; 
public Object context2; 
//……其他上下文变量 
public void excute() {}
} 

 

有了这样一个抽象基类,我们构造的源代码只需要继承ScriptContext,然后覆盖excute()方法即可,而执行脚本的过程也可以直接调用ScriptContext.excute(),而不必通过类反射定位到excute()方法然后执行。在新的思路下我们刚才构造的源代码可能变成这样:

public class Temp { 
@Override 
public void excute() { 
displayName = aliasName + ‘:’ + originalName;
}
} 

 

而执行脚本的过程也相应简化为:

Class<?> scriptClass = loader.loadClass("Temp");    
ScriptContext obj = (ScriptContext) scriptClass.newInstance();    
obj. aliasName = alias;    
obj. originalName = name; //上下文变量属于父类   
obj.excute();//利用多态调用覆盖的excute()函数   
String displayName = obj. displayName;//直接获得脚本的输出结果  

 

 

本文转载自:http://blog.csdn.net/tanjiazhang/archive/2010/01/05/5139095.aspx

 

 

附完全代码:

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;

public class Test {

    
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException, InstantiationException{

        
     
        String javaSource="public class DynaScript { public String aliasName; public String originalName; public String displayName; public void excute() { displayName=aliasName+\":\"+originalName;}}";
        
        
        OutputStream os = new FileOutputStream("DynaScript.java");    
        os.write(javaSource.getBytes());    
        os.close();  
        
        String[] compileArgs = new String[] {"DynaScript.java"};    
        com.sun.tools.javac.Main.compile(compileArgs); 
        
        
        URLClassLoader loader =    
            new URLClassLoader(new URL[]{new File(".").toURI().toURL()});   
        Class<?> scriptClass = loader.loadClass("DynaScript");  
        
         
        Object obj = scriptClass.newInstance();    
        scriptClass.getDeclaredField("aliasName").set(obj, "alias");    
        scriptClass.getDeclaredField("originalName").set(obj, "name");   
        
        scriptClass.getDeclaredMethod("excute").invoke(obj);     
        String displayName = (String)scriptClass.getDeclaredField("displayName").get(obj); 
        
        
        System.out.println(displayName);
        
    }
}

 

分享到:
评论

相关推荐

    shell,bat脚本运行java程序

    总的来说,shell和bat脚本提供了一种方便的方式来管理和运行Java程序,特别是对于需要定时执行或在后台运行的任务,它们能极大地简化操作流程。通过熟练掌握这两种脚本,你可以更高效地管理Java应用的生命周期。

    用BeanShell来运行java脚本

    标题中的“用BeanShell来运行Java脚本”指的是利用BeanShell这个开源库在Java环境中执行动态的、交互式的Java代码。BeanShell是一个轻量级的Java Scripting引擎,它允许你在运行时执行Java代码,无需编译,极大地...

    java android 执行脚本代码(java代码)

    通过Python Interpreter API,Java代码可以调用Python脚本并获取结果。 - **Kivy or PyDroid3**: 这些第三方库允许在Android设备上完整地运行Python环境,提供更强大的Python支持。 3. **Shell命令执行**: - **...

    java 执行sql脚本 例子

    java 执行sql脚本 例子java 执行sql脚本 例子java 执行sql脚本 例子java 执行sql脚本 例子java 执行sql脚本 例子java 执行sql脚本 例子java 执行sql脚本 例子java 执行sql脚本 例子java 执行sql脚本 例子java 执行...

    Java调用Groovy,实时动态加载数据库groovy脚本

    Java代码会连接到MongoDB,找到这个文档,读取Groovy脚本,然后使用GroovyClassLoader进行加载和执行。 动态加载数据库中的Groovy脚本带来的优势包括: - **热部署**:当Groovy脚本更新时,无需重新编译或重启Java...

    内存中动态编译执行java代码

    内存中动态编译执行Java代码是一种高级编程技巧,它允许我们在程序运行时根据需要创建、编译和执行新的Java代码。这种技术在某些场景下非常有用,比如在元编程、插件系统、自定义脚本执行或者代码热更新中。在Java中...

    JVM 动态执行Groovy脚本的方法

    本文将详细讲解如何使用JVM动态执行Groovy脚本的方法,主要包括利用JShell执行代码、调试模式下动态执行代码以及利用javax.script包执行Groovy脚本。以下是对各知识点的详细说明。 1. 利用JShell执行代码 Java 9 ...

    java动态代码执行

    Java动态代码执行是一种在运行时编译和执行代码的技术,它可以极大地提高程序的灵活性和可扩展性。在Java中,Groovy是一个强大的脚本语言,它与Java语法兼容,并且可以无缝集成到Java应用程序中,使得动态代码执行变...

    java 执行cmd命令及mongodb脚本

    Java执行CMD命令及MongoDB脚本是开发过程中常见的任务,特别是在集成系统或者自动化运维场景下。下面将详细讲解这两个主题。 一、Java执行CMD命令 在Java中,我们可以使用Runtime类或ProcessBuilder类来执行操作...

    java 动态脚本语言 精通 Groovy

    Groovy是一种基于Java平台的动态脚本语言,它在Java开发者中越来越受欢迎,因为它提供了简洁、灵活的语法,以及强大的动态编程能力。Groovy与Java兼容性极佳,可以直接调用Java类库,使得它在Java生态系统中具有广泛...

    linux 通过脚本执行java程序

    在Linux环境中,通过脚本执行Java程序是一种常见的实践,尤其...6. 后台运行Java程序的方法(如nohup、screen、init或Systemd服务) 了解并掌握这些知识点,将有助于你在Linux系统中更高效地管理和部署Java应用程序。

    Java脚本教程(学习资料)

    通过这个接口,你可以编写Java代码来调用和执行脚本语言的函数,或者将Java对象暴露给脚本环境。例如,`eval()`方法用于执行一个字符串形式的脚本,`put()`和`get()`方法则用于在Java和脚本环境中交换数据。 2. **...

    测试的java脚本

    这里的“测试的java脚本”可能是指一个专门用于执行自动化测试的Java程序,它可能包含了各种测试框架,如JUnit、TestNG等,用于验证特定功能或组件的行为是否符合预期。这样的脚本通常会包含单元测试、集成测试和/或...

    java脚本编程

    它与Java有着良好的兼容性,可以直接调用Java代码,并且支持元编程。Groovy的灵活性使其成为自动化测试、构建脚本等场景的理想选择。 - **语法特性**:Groovy支持闭包、动态类型、反射等。 - **应用场景**:Gradle...

    在Java中运行Perl脚本 JERL

    JERL(Java-Embedding for Perl Library)就是这样一个库,它允许Java程序直接执行和交互Perl脚本,实现Java与Perl的融合。 **JERL的原理** JERL是Java和Perl之间的桥梁,它通过JNI(Java Native Interface)将Java...

    JDK8 下 SpringBoot 应用动态编译 Java 源码并注入 Spring 容器

    基于接口、抽象类实现不停机动态调整代码的目的,将修改后的源码文件放置于指定目录下,读取文件后执行动态编译方法,即可将该类重新加载,新的类可以在Spring容器从新注册,且仅在当前窗口生效。如果重启了服务或...

    IE一键设置ActiveX控件、JAVA脚本、活动脚本

    运行bat文件,对IE一键设置ActiveX控件、JAVA脚本、活动脚本。win7及以上版本,最好采用右键点击,选择以管理员身份运行bat文件。

    java的windows启动脚本

    java的windows启动脚本

    第 1 部分: 引入 Java 脚本 API

    Java 脚本 API,也称为 Java Scripting API,是Java平台标准版(Java SE)的一部分,它允许Java应用程序在运行时执行脚本语言代码。这个API在Java 6版本中得到了显著增强,为开发者提供了在Java程序中嵌入和执行脚本...

Global site tag (gtag.js) - Google Analytics