Janino:轻量级且高效的Java编译器-01-入门介绍

标题:Janino:轻量级且高效的Java编译器

Janino 是一个超小型、超快速的Java编译器。

Janino 不仅能像JAVAC一样将一组源文件编译成一组类文件,而且还能编译一个Java表达式、一个代码块、一个类体、一个.java文件或一组.java文件在内存中,加载字节码并在运行中的JVM中直接执行。

 

xml

代码解读

复制代码

<integration> <commons-jci>Apache Commons JCI ("Java Compiler Interface")</commons-jci> <jboss-rules>JBoss Rules / Drools</jboss-rules> </integration>

Janino 还可以用于静态代码分析或代码操作。

快速入门指南

简介:

这是关于什么的?查看这个PDF演示文稿以快速开始。(请注意,一些信息已经过时,例如对CODEHAUS的引用,它在2015年的某个时候已经不存在了。)

属性:

主要设计目标是在保持编译器小巧简单的前提下,提供一个(几乎)完整的Java编译器。

以下是Java编程语言实现的元素(或注明部分实现的):

Java 1.4语言特性:

  • 包声明,导入声明
  • 类声明
  • 接口声明
  • 继承(扩展和实现)
  • 静态成员类型声明
  • 内部类声明(非静态成员、局部、匿名)
  • 类初始化器,实例初始化器
  • 字段声明,方法声明
  • 局部变量声明
  • 类变量初始化器,实例变量初始化器
  • 块语句({...})
  • if ... else 语句
  • 基本的for循环
  • while循环
  • do ... while循环
  • try ... catch ... finally 语句
  • 抛出语句
  • 返回语句
  • 断点语句
  • 继续语句
  • switch语句
  • 同步语句
  • 所有基本类型(布尔,字符,字节,短整型,整型,长整型,浮点型,双精度浮点型)
  • 赋值运算符 =
  • 复合赋值运算符 +=, -=, *=, /=, &=, |=, ^=, %=, <<=, >>=, >>>=
  • 条件运算符 ? ... :, &&, ||
  • 布尔逻辑运算符 &, ^, |
  • 整型位运算符 &, ^, |
  • 数值运算符 *, /, %, +, -, <<, >>, >>
  • 字符串连接运算符 +
  • 运算符 ++ 和 --
  • 类型比较运算符 instanceof
  • 一元运算符 +, -, ~, !
  • 括号表达式
  • 字段访问(如 System.out)
  • 超类成员访问(super.meth(), super.field)
  • this(当前实例的引用)
  • 调用其他构造器(this(a, b, c);)
  • 超类构造器调用(super(a, b, c);)
  • 方法调用(System.out.println("Hello"))
  • 类实例创建(new Foo())
  • 原始数组创建(new int[10][5][])
  • 类或接口数组创建(new Foo[10][5][])
  • 数组访问(args[0])
  • 局部变量访问
  • 整数字面量(十进制,十六进制和八进制)
  • 浮点字面量(十进制)
  • 布尔,字符,字符串字面量
  • null字面量
  • 数值转换:一元,二元,扩展,缩小
  • 引用转换:扩展,缩小
  • 赋值转换
  • 字符串转换(用于字符串连接)
  • 强制类型转换
  • 常量表达式
  • 块作用域,方法作用域,类作用域,全局作用域
  • 抛出子句
  • 数组初始化器(String[] a = { "x", "y", "z" })
  • 原始类字面量(int.class)
  • 非原始类字面量(String.class)
  • 未编译编译单元之间的引用
  • 行号表("-g:lines")
  • 源文件信息("-g:source")
  • 局部变量信息("-g:vars")
  • 处理@deprecated文档注释标签
  • 可访问性检查(public, protected, private, 默认)
  • "确定赋值"检查
  • 编译成超过32KB的方法
  • assert(部分实现 - 断言始终启用,就像JVM以"-ea"命令行选项启动一样)
  • 用于字符串连接的StringBuilder类

    整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

     需要全套面试笔记的【点击此处即可】即可免费获取

Java 5语言特性:

  • 参数化类型声明
  • 类型参数(例如 List):已解析,但其他方面被忽略。最显著的限制是,您必须从方法调用中强制转换返回值,例如 "(String) myMap.get(key)"

Java 7语言特性:

  • 二进制整数字面量(JLS7 3.10.1)
  • 数值字面量中的下划线(JLS7 3.10.1)
  • 字符串switch语句(JLS7 14.11)
  • try-with-resources语句(JLS7 14.20.3)
  • 捕获和重新抛出多个异常类型:部分实现;已解析和未解析,但不可编译
  • 泛型实例创建的类型推断(即“菱形运算符”)(JLS11 15.9.1)

Java 8语言特性:

  • Lambda表达式:部分实现;已解析和未解析,但不可编译
  • 方法引用:部分实现;已解析和未解析,但不可编译
  • 默认方法
  • 静态接口方法

Java 9语言特性:

  • 私有接口方法
  • 增强的try-with-resources语句,允许VariableAccesses作为资源(JLS9 14.20.3)

Java 10语言特性:

  • 局部变量类型推断:部分实现;已解析和未解析,但不可编译

Java 11语言特性:

  • Lambda参数类型推断:部分实现;已解析和未解析,但不可编译

Java 15语言特性:

  • 文本块

即使JANINO在旧版本的JRE上运行,它也支持所有这些语言特性!

Java编程语言未实现或部分实现的特性

以下是Java编程语言未实现(或注明部分实现)的元素:

Java 1.4语言特性:

  • assert:部分实现 - 断言始终启用,就像JVM是以"-ea"命令行选项启动的一样。

Java 5语言特性:

  • 类型参数:已解析,但其他方面被忽略。最显著的限制是,您必须从方法调用中强制转换返回值,例如 "(String) myMap.get(key)"。

Java 7语言特性:

  • 捕获并重新抛出多个异常类型:部分实现;已解析和未解析,但不可编译。
  • 泛型实例创建的类型推断(例如 Map<String, Integer> map = new HashMap<>();)。

Java 8语言特性:

  • Lambda表达式:部分实现;已解析和未解析,但不可编译。
  • 方法引用:部分实现;已解析和未解析,但不可编译。
  • 重复注解。
  • 类型注解。

Java 9语言特性:

  • 内部类使用的菱形运算符。
  • 模块:部分实现;模块化编译单元已解析和未解析,但不可编译。

Java 10语言特性:

  • 局部变量类型推断:部分实现;已解析和未解析,但不可编译。

Java 11语言特性:

  • Lambda参数类型推断:部分实现;已解析和未解析,但不可编译。

许可:

  • JANINO在新BSD许可证下可用。

下载

  • 包 如果您使用MAVEN,请在您的POM文件中添加此条目:
 

xml

代码解读

复制代码

<!-- MAVEN依赖配置示例 --> <dependency> <groupId>org.codehaus.janino</groupId> <artifactId>janino</artifactId> <version>版本号</version> </dependency>

请注意,上面的版本号应替换为实际使用的Janino库的版本号。

入门例子

 

java

代码解读

复制代码

ExpressionEvaluator ee = new ExpressionEvaluator(); ee.cook("3 + 4"); System.out.println(ee.evaluate()); // Prints "7".

基础示例:Janino 作为表达式求值器

Janino作为一个表达式求值器:

假设你正在构建一个电子商务系统,该系统计算用户放入购物车中的商品的运费。由于你在实现时不知道商家的运费计算模型,你可以实现一组你能想到的运费计算模型(固定收费、按重量、按商品数量等),并在运行时选择其中之一。

实际上,你会发现你实现的运费计算模型很少会符合商家的要求,所以你必须添加自定义模型,这些模型是特定于商家的。如果商家的模型稍后有变化,你必须更改你的代码,重新编译并重新分发你的软件。

因为这种方式非常不灵活,运费表达式应在运行时指定,而不是在编译时。这意味着表达式必须在运行时扫描、解析和评估,这就是为什么你需要一个表达式求值器。

一个简单的表达式求值器会解析一个表达式并创建一个“语法树”。例如,表达式 "a + b * c" 将编译成一个“求和”对象,其第一个操作数是参数 "a",第二个操作数是一个“乘积”对象,其操作数是参数 "b" 和 "c"。这样的语法树可以相对快速地评估。然而,运行时性能大约比直接由JVM执行的“本地”Java代码低100倍。这限制了这种表达式求值器在简单应用程序中的使用。

此外,你可能不仅想要进行简单的算术运算,如 "a + b * c % d",而且可能想要进一步发展这个概念,拥有一个真正的“脚本”语言,这增加了你的应用程序的灵活性。由于你已经知道Java编程语言,你可能想要一个与Java编程语言语法类似的语言。

所有这些考虑都导致了在运行时编译Java代码,就像一些引擎(例如JSP引擎)已经做的那样。然而,使用ORACLE的JDK编译Java程序是一个相对资源密集型的过程(磁盘访问,CPU时间等)。这就是Janino发挥作用的地方……一个轻量级,“嵌入式”Java编译器,它在内存中编译简单程序到JVM字节码,这些字节码在运行程序的JVM中执行。

好的,现在你好奇了……这就是你如何使用ExpressionEvaluator的方式:(接下来的内容应该是具体的使用示例,但原文中并未给出具体的代码或步骤,所以这里无法继续翻译。如果需要进一步的示例代码或步骤,请提供详细信息。)

 

java

代码解读

复制代码

package foo; import java.lang.reflect.InvocationTargetException; import org.codehaus.commons.compiler.CompileException; import org.codehaus.janino.ExpressionEvaluator; public class Main { public static void main(String[] args) throws CompileException, InvocationTargetException { // Now here's where the story begins... ExpressionEvaluator ee = new ExpressionEvaluator(); // The expression will have two "int" parameters: "a" and "b". ee.setParameters(new String[] { "a", "b" }, new Class[] { int.class, int.class }); // And the expression (i.e. "result") type is also "int". ee.setExpressionType(int.class); // And now we "cook" (scan, parse, compile and load) the fabulous expression. ee.cook("a + b"); // Eventually we evaluate the expression - and that goes super-fast. int result = (Integer) ee.evaluate(new Object[] { 19, 23 }); System.out.println(result); } }

注意:如果你传递一个字符串字面量作为表达式,一定要转义所有的 Java 特殊字符,特别是反斜杠。

在我的机器上(2 GHz P4),编译表达式需要 670 微秒,而评估表达式仅需 0.35 微秒(大约比编译快 2000 倍)。

有一个示例程序 "ExpressionDemo" 你可以用来试验 ExpressionEvaluator,或者你可以研究 ExpressionDemo 的源代码来了解 ExpressionEvaluator 的 API。

 

sql

代码解读

复制代码

$ java -cp janino.jar:commons-compiler.jar \ > org.codehaus.commons.compiler.samples.ExpressionDemo \ > -help Usage: ExpressionDemo { <option> } <expression> { <parameter-value> } Compiles and evaluates the given expression and prints its value. Valid options are -et <expression-type> (default: any) -pn <comma-separated-parameter-names> (default: none) -pt <comma-separated-parameter-types> (default: none) -te <comma-separated-thrown-exception-types> (default: none) -di <comma-separated-default-imports> (default: none) -help The number of parameter names, types and values must be identical. $ java -cp janino.jar:commons-compiler.jar \ > org.codehaus.commons.compiler.samples.ExpressionDemo \ > -et double \ > -pn x \ > -pt double \ > "Math.sqrt(x)" \ > 99 Result = 9.9498743710662 $

Janino 作为脚本评估器

类似于表达式评估器,ScriptEvaluator API 存在于编译和处理 Java "块"(即方法体)中。

如果定义了一个非 "void" 的返回值,那么该块必须返回该类型的值。

作为一个特殊功能,它允许声明方法。方法声明的位置和顺序无关紧要。

示例:

 

java

代码解读

复制代码

package foo; import java.lang.reflect.InvocationTargetException; import org.codehaus.commons.compiler.CompileException; import org.codehaus.janino.ScriptEvaluator; public class Main { public static void main(String[] args) throws CompileException, NumberFormatException, InvocationTargetException { ScriptEvaluator se = new ScriptEvaluator(); se.cook( "" + "static void method1() {\n" + " System.out.println(1);\n" + "}\n" + "\n" + "method1();\n" + "method2();\n" + "\n" + "static void method2() {\n" + " System.out.println(2);\n" + "}\n" ); se.evaluate(); } }

Janino 作为类体评估器

类似于表达式评估器和脚本评估器,ClassBodyEvaluator 用于编译和处理 Java 类体,即一系列方法和变量声明。

如果你规定类体应该定义一个名为 "main()" 的方法,那么你的脚本将几乎像一个 "C" 程序一样:

 

java

代码解读

复制代码

public static void main(String[] args) { System.out.println(java.util.Arrays.asList(args)); }

“ClassBodyDemo” 程序(源代码)演示了这一点:

 

vbnet

代码解读

复制代码

$ java -cp janino.jar:commons-compiler.jar \ > org.codehaus.commons.compiler.samples.ClassBodyDemo -help Usage: ClassBodyDemo <class-body> { <argument> } ClassBodyDemo -help If <class-body> starts with a '@', then the class body is read from the named file. The <class-body> must declare a method "public static void main(String[])" to which the <argument>s are passed. If the return type of that method is not VOID, then the returned value is printed to STDOUT. $ java -cp janino.jar:commons-compiler.jar \ > org.codehaus.commons.compiler.samples.ClassBodyDemo ' > public static void > main(String[] args) { > System.out.println(java.util.Arrays.asList(args)); > }' \ > a b c [a, b, c] $

Janino 作为简单编译器

SimpleCompiler 编译单个 .java 文件(“编译单元”)。

与常规的 Java 编译不同,该编译单元可以声明多个公共类型。

示例:

 

java

代码解读

复制代码

// This is file "Hello.java", but it could have any name. public class Foo { public static void main(String[] args) { new Bar().meth(); } } public class Bar { public void meth() { System.out.println("HELLO!"); } }

它返回一个 ClassLoader,你可以从中获取已编译的类。

要运行此程序,请输入:

 

ruby

代码解读

复制代码

$ java -cp janino.jar:commons-compiler.jar \ > org.codehaus.janino.SimpleCompiler -help Usage: org.codehaus.janino.SimpleCompiler <source-file> <class-name> { <argument> } Reads a compilation unit from the given <source-file> and invokes method "public static void main(String[])" of class <class-name>, passing the given <argument>s. $ java -cp janino.jar:commons-compiler.jar \ > org.codehaus.janino.SimpleCompiler \ > Hello.java Foo HELLO! $

Janino 作为编译器

Compiler 编译一组 .java 文件(“编译单元”),并生成 .class 文件。

每个编译单元可以声明不同的包,且这些编译单元可以相互引用,甚至可以循环引用。

示例:

 

java

代码解读

复制代码

ICompiler compiler = compilerFactory.newCompiler(); compiler.compile(new File("pkg1/A.java"), new File("pkg2/B.java"));

然而,Compiler 可以重新配置,以从不同的源读取编译单元,和/或将类存储在不同的位置,例如存储到一个 Map 中,然后通过 ClassLoader 将其加载到虚拟机中:

 

java

代码解读

复制代码

ICompiler compiler = compilerFactory.newCompiler(); // Store generated .class files in a Map: Map<String, byte[]> classes = new HashMap<String, byte[]>(); compiler.setClassFileCreator(new MapResourceCreator(classes)); // Now compile two units from strings: compiler.compile(new Resource[] { new StringResource( "pkg1/A.java", "package pkg1; public class A { public static int meth() { return pkg2.B.meth(); } }" ), new StringResource( "pkg2/B.java", "package pkg2; public class B { public static int meth() { return 77; } }" ), }); // Set up a class loader that uses the generated classes. ClassLoader cl = new ResourceFinderClassLoader( new MapResourceFinder(classes), // resourceFinder ClassLoader.getSystemClassLoader() // parent ); Assert.assertEquals(77, cl.loadClass("pkg1.A").getDeclaredMethod("meth").invoke(null));

高级示例

Janino 作为源代码类加载器

JavaSourceClassLoader 扩展了 Java 的 java.lang.ClassLoader 类,具有直接从源代码加载类的功能。

具体来说,如果通过这个类加载器加载一个类,它会在给定的“源路径”中的任何目录中搜索匹配的 ".java" 文件,读取、扫描、解析并编译它,并在 JVM 中定义生成的类。必要时,更多的类将通过父类加载器和/或源路径加载。

文件系统中不会创建任何中间文件。

示例:

 

java

代码解读

复制代码

// srcdir/pkg1/A.java package pkg1; import pkg2.*; public class A extends B { }

 

java

代码解读

复制代码

// srcdir/pkg2/B.java package pkg2; public class B implements Runnable { public void run() { System.out.println("HELLO"); } }

 

java

代码解读

复制代码

// Sample code that reads, scans, parses, compiles and loads // "A.java" and "B.java", then instantiates an object of class // "A" and invokes its "run()" method. ClassLoader cl = new JavaSourceClassLoader( this.getClass().getClassLoader(), // parentClassLoader new File[] { new File("srcdir") }, // optionalSourcePath (String) null, // optionalCharacterEncoding DebuggingInformation.NONE // debuggingInformation ); // Load class A from "srcdir/pkg1/A.java", and also its superclass // B from "srcdir/pkg2/B.java": Object o = cl.loadClass("pkg1.A").newInstance(); // Class "B" implements "Runnable", so we can cast "o" to // "Runnable". ((Runnable) o).run(); // Prints "HELLO" to "System.out".

如果 Java 源代码不在文件中,而是在其他存储(如数据库、主内存等)中,你可以指定一个自定义的 ResourceFinder,而不是基于目录的源路径。

如果你有许多源文件,并且想要减少编译时间,可以使用 CachingJavaSourceClassLoader,它利用应用程序提供的缓存来存储类文件以便重复使用。

jsh - Java shell

JANINO 有一个姊妹项目 "jsh",它实现了一个类似于 "bash"、"ksh"、"csh" 等的 "shell" 程序,但使用 Java 语法。

Janino 作为命令行 Java 编译器

Compiler 类模仿了 ORACLE 的 javac 工具的行为。它将一组“编译单元”(即 Java 源文件)编译成一组类文件。

使用 "-warn" 选项,Janino 会发出一些可能非常有趣的警告,这些警告可能帮助你“清理”源代码。

BASH 脚本 "bin/janinoc" 实现了一个 ORACLE 的 JAVAC 工具的替代品:

 

vbnet

代码解读

复制代码

$ janinoc -sourcepath src -d classes src/com/acme/MyClass.java $ janinoc -help A drop-in replacement for the JAVAC compiler, see the documentation for JAVAC Usage: java java.lang.Compiler [ <option> ] ... <source-file> ... Supported <option>s are: -d <output-dir> Where to save class files -sourcepath <dirlist> Where to look for other source files -classpath <dirlist> Where to look for other class files -extdirs <dirlist> Where to look for other class files -bootclasspath <dirlist> Where to look for other class files -encoding <encoding> Encoding of source files, e.g. "UTF-8" or "ISO-8859-1" -verbose -g Generate all debugging info -g:none Generate no debugging info (the default) -g:{source,lines,vars} Generate only some debugging info -rebuild Compile all source files, even if the class files seem up-to-date -help The default encoding in this environment is "UTF-8". $

Janino 作为 ANT 编译器

你可以通过 AntCompilerAdapter 类将 Janino 插入到 ANT 工具中。

只需确保 janino.jar 在类路径中,然后使用以下命令行选项运行 ANT:

 

bash

代码解读

复制代码

-Dbuild.compiler=org.codehaus.janino.AntCompilerAdapter

Janino 作为 TOMCAT 编译器

如果你想在 TOMCAT 中使用 Janino,只需将 janino.jar 文件复制到 TOMCAT 的 common/lib 目录中,然后在 TOMCAT 的 conf/web.xml 文件中的 JSP servlet 定义部分添加以下初始化参数段:

 

xml

代码解读

复制代码

<init-param> <param-name>compiler</param-name> <param-value>org.codehaus.janino.AntCompilerAdapter</param-value> </init-param>

Janino 作为代码分析器

除了编译 Java 代码之外,Janino 还可以用于静态代码分析:

基于解析器生成的 AST(“抽象语法树”),遍历器会遍历 AST 的所有节点,派生类可以对它们进行各种分析,例如计算声明数量:

 

bash

代码解读

复制代码

$ java org.codehaus.janino.samples.DeclarationCounter DeclarationCounter.java Class declarations: 1 Interface declarations: 0 Fields: 4 Local variables: 4 $

这就是所有这些精巧的代码度量和样式检查的基础。

Janino 作为代码操控器

如果你想将一个 Java 编译单元读入内存,对其进行操控,然后将其写回文件以进行编译,只需执行以下步骤:

 

java

代码解读

复制代码

// 从 Reader "r" 中读取编译单元到内存中。 Java.CompilationUnit cu = new Parser(new Scanner(fileName, r)).parseCompilationUnit(); // 在内存中操控 AST。 // ... // 将 AST 转换回文本。 UnparseVisitor.unparse(cu, new OutputStreamWriter(System.out));

AstTest.testMoveLocalVariablesToFields() 测试用例演示了如何操控 AST。

替代编译器实现

Janino 可以配置为使用其他 Java 编译器实现,而不是其自身的编译器。这些替代实现基本上需要实现 ICompilerFactory 接口。其中一个替代实现基于 javax.tools API,并作为 Janino 发行版的一部分提供:commons-compiler-jdk.jar

基本上有两种方式可以切换实现:

  1. 使用 org.codehaus.commons.compiler.jdk.ExpressionEvaluator 及其相关类,而不是 org.codehaus.janino.ExpressionEvaluator;在编译时和运行时的类路径中使用 commons-compiler-jdk.jar 代替 janino.jar。(commons-compiler.jar 必须始终在类路径中,因为它包含所有实现所需的基本类。)

  2. 使用 org.codehaus.commons.compiler.CompilerFactoryFactory.getDefaultFactory().newExpressionEvaluator(),并且只对 commons-compiler.jar 进行编译(不需要具体实现)。在运行时,添加一个实现(janino.jar 或 commons-compiler-jdk.jar)到类路径中,getDefaultFactory() 会在运行时找到它。

基于 JDK 的实现生成的代码性能与 Janino 实现的性能相同;然而,编译速度则慢了 22 倍(通过编译表达式“a + b”两个整数来测量,见下文)。主要原因是 javax.tools 编译器通过类路径加载所需的 JRE 类,而 Janino 则使用已经加载到运行中的 JVM 中的类。因此,CompilerDemo(JAVAC 的直接替代品)并不比 JAVAC 更快(通过编译 Janino 源代码来测量,见下文)。

活动JaninoJDK
编译表达式“a + b”0.44 ms9.84 ms
编译 Janino 源代码6.417 s5.864 s

(使用 JDK 17 和 JRE 17 测量。)

安全性

警告: JRE 17 报告“System::setSecurityManager 将在未来版本中移除”,因此你不应该在 JRE 17 及更高版本中使用 Janino 沙箱。

由于 Janino 生成的字节码可以完全访问 JRE,因此如果正在编译和执行的表达式、脚本、类体或编译单元包含用户输入,可能会产生安全问题。

如果用户是一个有经验的系统管理员,可以预期他或她会负责任地使用 Janino,并遵循你提供的文档和注意事项;然而,如果用户是来自内部网或互联网的用户,无法假定他们不会表现得笨拙、轻率、富有创造性、一根筋,甚至是恶意的。

Janino 包含一个非常易于使用的安全 API,可以用来将表达式、脚本、类体和编译单元锁定在一个“沙箱”中,该沙箱由 Java 安全管理器保护。

 

java

代码解读

复制代码

import java.security.Permissions; import java.security.PrivilegedAction; import java.util.PropertyPermission; import org.codehaus.janino.ScriptEvaluator; import org.codehaus.commons.compiler.Sandbox; public class SandboxDemo { public static void main(String[] args) throws Exception { // Create a JANINO script evaluator. The example, however, will work as fine with // ExpressionEvaluators, ClassBodyEvaluators and SimpleCompilers. ScriptEvaluator se = new ScriptEvaluator(); se.setDebuggingInformation(true, true, false); // Now create a "Permissions" object which allows to read the system variable // "foo", and forbids everything else. Permissions permissions = new Permissions(); permissions.add(new PropertyPermission("foo", "read")); // Compile a simple script which reads two system variables - "foo" and "bar". PrivilegedAction<?> pa = se.createFastEvaluator(( "System.getProperty(\"foo\");\n" + "System.getProperty(\"bar\");\n" + "return null;\n" ), PrivilegedAction.class, new String[0]); // Finally execute the script in the sandbox. Getting system property "foo" will // succeed, and getting "bar" will throw a // java.security.AccessControlException: access denied (java.util.PropertyPermission bar read) // in line 2 of the script. Et voila! Sandbox sandbox = new Sandbox(permissions); sandbox.confine(pa); } }

Java 安全管理器的官方文档由 ORACLE 提供,标题为《Java Essentials: The Security Manager》。由权限保护的操作包括:

  • 接受来自指定主机和端口号的套接字连接
  • 修改线程(更改其优先级、停止它等)
  • 打开到指定主机和端口号的套接字连接
  • 创建新的类加载器
  • 删除指定文件
  • 创建新进程
  • 使应用程序退出
  • 加载包含本地方法的动态库
  • 在指定的本地端口号上等待连接
  • 从指定包中加载类(由类加载器使用)
  • 将新类添加到指定包中(由类加载器使用)
  • 访问或修改系统属性
  • 访问指定的系统属性
  • 从指定文件读取
  • 向指定文件写入

这些操作是攻击者可能进行的“真正恶意的行为”。然而,未受到保护的操作包括:

  • 分配内存
  • 创建新线程
  • 过度使用 CPU 时间

幸运的是,Thread 构造函数会进行一些反射操作,因此可以通过不允许新的 RuntimePermission("accessDeclaredMembers") 来防止脚本创建新线程。

内存分配无法直接受到保护,不过,com.sun.management.ThreadMXBean.getThreadAllocatedBytes(long threadId) 似乎可以统计线程所分配的字节数,因此它可能是检测运行中脚本过度内存分配的一个有效措施。

debug 调试

即使生成的类是动态创建的,也可以进行交互式调试。

只需在启动 JVM 时设置两个系统属性,例如:

 

bash

代码解读

复制代码

$ java \ > ... \ > -Dorg.codehaus.janino.source_debugging.enable=true \ > -Dorg.codehaus.janino.source_debugging.dir=C:\tmp \ > ...

(第二个属性是可选的;如果未设置,则临时文件将创建在默认的临时文件目录中。)

当 Janino 扫描表达式、脚本、类体或编译单元时,它会将源代码的副本存储在一个临时文件中,调试器通过其源路径访问该文件。(JVM 终止时,临时文件将被删除。)

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值