`

javax.tool package was added to jdk 1.6

阅读更多

Introduction

The javax.tools package, added to Java SE 6 as a standard API for compiling Java source, lets you add dynamic capabilities to extend static applications. This article provides an overview of the major classes in the package and demonstrates how to use them to create a façade for compiling Java source from Java Strings, StringBuffers, or other CharSequences, instead of files. It then uses this façade to build an interactive plotting application that lets the user express a numeric function y = f(x) using any valid numeric Java expression. Finally, it discusses the possible security risks associated with dynamic source compilation and ways to deal with those risks.

The idea of extending applications via compiling and loading Java extensions isn't new, and several existing frameworks support this capability. JavaServer Pages (JSP) technology in Java Platform, Enterprise Edition (Java EE) is a widely known example of a dynamic framework that generates and compiles Java classes. The JSP translator transforms .jsp files into Java servlets, using intermediate source-code files that the JSP engine then compiles and loads into the Java EE servlet container. The compilation is often performed by directly invoking the javac compiler, which requires an installed Java Development Kit (JDK) or by calling com.sun.tools.javac.Main, which can be found in Sun's tools.jar. Sun's licensing allows tools.jar to be redistributed with the full Java Runtime Environment (JRE). Other ways to implement such dynamic capabilities include using an existing dynamic scripting language (such as JavaScript or Groovy) that integrates with the application's implementation language (see Resources) or writing a domain-specific language and associated language interpreter or compiler.

Other frameworks (such as NetBeans and Eclipse) allow extensions that developers code directly in the Java language, but such systems require external static compilation and source and binary management of the Java code and its artifacts. Apache Commons JCI provides a mechanism to compile and load Java classes into a running application. Janino and Javassist also provide similar dynamic capabilities, although Janino is limited to pre-Java 1.4 language constructs, and Javassist works not at the source-code level but at a Java class abstraction level. (See Resources for links to these projects.) However, because Java developers are already adept at writing in the Java language, a system that lets you simply generate Java source code on the fly and then compile and load it promises the shortest learning curve and the most flexibility.

Benefits of using javax.tools

Using javax.tools has the following advantages:



Back to top


Java compilation: Concepts and implementation

To understand the javax.tools package, it's helpful to review Java compilation concepts and how the package implements them. The javax.tools package provides abstractions for all of these concepts in a general way that lets you provide the source code from alternate source objects rather than requiring the source to be located in the file system.

Compiling Java source requires the following components:

Figure 1 maps the javac concepts to their implementations in javax.tools:


Figure 1. How javac concepts map to javax.tools interfaces
How javac concepts map into javax.tools interfaces.

With these concepts in mind, you'll now see how to implement a façade for compiling CharSequences.



Back to top


Compiling Java source in CharSequence instances

In this section, I'll construct a façade for javax.tools.JavaCompiler. The javaxtools.compiler.CharSequenceCompiler class (see Download) can compile Java source stored in arbitrary java.lang.CharSequence objects (such as String, StringBuffer, and StringBuilder), returning a Class. CharSequenceCompiler has the following API:

  • public CharSequenceCompiler(ClassLoader loader, Iterable<String> options) : This constructor accepts a ClassLoader that is passed to the Java compiler, allowing it to resolve dependent classes. The Iterable options allow the client to pass additional compiler options that correspond to the javac options.

  • public Map<String, Class<T>> compile(Map<String, CharSequence> classes, final DiagnosticCollector<JavaFileObject> diagnostics) throws CharSequenceCompilerException, ClassCastException : This is the general compilation method that supports compiling multiple sources together. Note that the Java compiler must handle cyclic graphs of classes, such as A.java depending on B.java, B.java depending on C.java, and C.java depending on A.java. The first argument to this method is a Map whose keys are fully qualified class names and whose corresponding values are CharSequences containing the source for that class. For example:
    • "mypackage.A""package mypackage; public class A { ... }";
    • "mypackage.B""package mypackage; class B extends A implements C { ... }";
    • "mypackage.C""package mypackage; interface C { ... }"
    The compiler adds Diagnostics to the DiagnosticCollector. The generic type parameter T is the primary type that you wish to cast the class to. compile() is overloaded with another method that takes a single class name and CharSequence to compile.

  • public ClassLoader getClassLoader() : This method returns the class loader that the compiler assembles when generating .class files, so that you can load other classes or resources from it.

  • public Class<T> loadClass(final String qualifiedClassName) throws ClassNotFoundException : Because the compile() method can define multiple classes (including public nested classes), this method allows these auxiliary classes to be loaded.

To support this CharSequenceCompiler API, I implement the javax.tools interfaces with the classes JavaFileObjectImpl (for storing the CharSequence sources and CLASS output emitted by the compiler) and JavaFileManagerImpl (which maps names to JavaFileObjectImpl instances to manage both the source sequences and the bytecode emitted from the compiler).

JavaFileObjectImpl

JavaFileObjectImpl, shown in Listing 1, implements JavaFileObject and holds a CharSequence source (for SOURCE) or a ByteArrayOutputStream byteCode (for CLASS files). The key method is CharSequence getCharContent(final boolean ignoreEncodingErrors), through which the compiler obtains the source text. See Download for the complete source for all the code examples.


Listing 1. JavaFileObjectImpl (partial source listing)
                
final class JavaFileObjectImpl extends SimpleJavaFileObject {
   private final CharSequence source;

   JavaFileObjectImpl(final String baseName, final CharSequence source) {
      super(CharSequenceCompiler.toURI(baseName + ".java"), Kind.SOURCE);
      this.source = source;
   }
   @Override
   public CharSequence getCharContent(final boolean ignoreEncodingErrors)
         throws UnsupportedOperationException {
      if (source == null)
         throw new UnsupportedOperationException("getCharContent()");
      return source;
   }
}

FileManagerImpl

FileManagerImpl (see Listing 2) extends ForwardingJavaFileManager to map qualified class names to JavaFileObjectImpl instances:


Listing 2. FileManagerImpl (partial source listing)
                
final class FileManagerImpl extends ForwardingJavaFileManager<JavaFileManager> {
   private final ClassLoaderImpl classLoader;
   private final Map<URI, JavaFileObject> fileObjects 
           = new HashMap<URI, JavaFileObject>();

   public FileManagerImpl(JavaFileManager fileManager, ClassLoaderImpl classLoader) {
      super(fileManager);
      this.classLoader = classLoader;
   }

   @Override
   public FileObject getFileForInput(Location location, String packageName,
         String relativeName) throws IOException {
      FileObject o = fileObjects.get(uri(location, packageName, relativeName));
      if (o != null)
         return o;
      return super.getFileForInput(location, packageName, relativeName);
   }

   public void putFileForInput(StandardLocation location, String packageName,
         String relativeName, JavaFileObject file) {
      fileObjects.put(uri(location, packageName, relativeName), file);
   }
}

CharSequenceCompiler

If ToolProvider.getSystemJavaCompiler() can't create a JavaCompiler

The ToolProvider.getSystemJavaCompiler() method can return null if tools.jar is not in the application's classpath. The CharStringCompiler class detects this possible configuration problem and throws an exception with a recommendation for fixing the problem. Note that Sun's licensing allows tools.jar to be redistributed with the JRE.

With these support classes, I can now define the CharSequenceCompiler. It's constructed with a runtime ClassLoader and compiler options. It uses ToolProvider.getSystemJavaCompiler() to get the JavaCompiler instance, then instantiates a JavaFileManagerImpl that forwards to the compiler's standard file manager.

The compile() method iterates over the input map, constructing a JavaFileObjectImpl from each name/CharSequence and adding it to the JavaFileManager so the JavaCompiler finds them when calling the file manager's getFileForInput() method. The compile() method then creates a JavaCompiler.Task instance and runs it. Failures are thrown as a CharSequenceCompilerException. Then, for each source passed to the compile() method, the resulting Class is loaded and placed in the result Map.

The class loader associated with the CharSequenceCompiler (see Listing 3) is a ClassLoaderImpl instance that looks up the bytecode for a class in the JavaFileManagerImpl instance, returning the .class files created by the compiler:


Listing 3. CharSequenceCompiler (partial source listing)
                
public class CharSequenceCompiler<T> {
   private final ClassLoaderImpl classLoader;
   private final JavaCompiler compiler;
   private final List<String> options;
   private DiagnosticCollector<JavaFileObject> diagnostics;
   private final FileManagerImpl javaFileManager;

   public CharSequenceCompiler(ClassLoader loader, Iterable<String> options) {
      compiler = ToolProvider.getSystemJavaCompiler();
      if (compiler == null) {
         throw new IllegalStateException(
               "Cannot find the system Java compiler. "
               + "Check that your class path includes tools.jar");
      }
      classLoader = new ClassLoaderImpl(loader);
      diagnostics = new DiagnosticCollector<JavaFileObject>();
      final JavaFileManager fileManager = compiler.getStandardFileManager(diagnostics,
            null, null);
      javaFileManager = new FileManagerImpl(fileManager, classLoader);
      this.options = new ArrayList<String>();
      if (options != null) {
         for (String option : options) {
            this.options.add(option);
         }
      }
   }

   public synchronized Map<String, Class<T>> 
	      compile(final Map<String, CharSequence> classes,
                  final DiagnosticCollector<JavaFileObject> diagnosticsList)
          throws CharSequenceCompilerException, ClassCastException {
      List<JavaFileObject> sources = new ArrayList<JavaFileObject>();
      for (Entry<String, CharSequence> entry : classes.entrySet()) {
         String qualifiedClassName = entry.getKey();
         CharSequence javaSource = entry.getValue();
         if (javaSource != null) {
            final int dotPos = qualifiedClassName.lastIndexOf('.');
            final String className = dotPos == -1 
	              ? qualifiedClassName
                  : qualifiedClassName.substring(dotPos + 1);
            final String packageName = dotPos == -1 
	              ? "" 
                  : qualifiedClassName .substring(0, dotPos);
            final JavaFileObjectImpl source = 
	              new JavaFileObjectImpl(className, javaSource);
            sources.add(source);
            javaFileManager.putFileForInput(StandardLocation.SOURCE_PATH, packageName,
                  className + ".java", source);
         }
      }
      final CompilationTask task = compiler.getTask(null, javaFileManager, diagnostics,
                                                    options, null, sources);
      final Boolean result = task.call();
      if (result == null || !result.booleanValue()) {
         throw new CharSequenceCompilerException("Compilation failed.", 
                                                 classes.keySet(), diagnostics);
      }
      try {
         Map<String, Class<T>> compiled = 
	                    new HashMap<String, Class<T>>();
         for (Entry<String, CharSequence> entry : classes.entrySet()) {
            String qualifiedClassName = entry.getKey();
            final Class<T> newClass = loadClass(qualifiedClassName);
            compiled.put(qualifiedClassName, newClass);
         }
         return compiled;
      } catch (ClassNotFoundException e) {
         throw new CharSequenceCompilerException(classes.keySet(), e, diagnostics);
      } catch (IllegalArgumentException e) {
         throw new CharSequenceCompilerException(classes.keySet(), e, diagnostics);
      } catch (SecurityException e) {
         throw new CharSequenceCompilerException(classes.keySet(), e, diagnostics);
      }
   }
}



Back to top


The Plotter application

Now that I have a simple API for compiling source, I'll put it in action by creating a function-plotting application, written in Swing. Figure 2 shows the application plotting the x * sin(x) * cos(x) function:


Figure 2. A dynamic application using the javaxtools.compiler package
Plotter Swing application screenshot

The application uses the Function interface defined in Listing 4:


Listing 4. Function interface
                
package javaxtools.compiler.examples.plotter;
public interface Function {
   double f(double x);
}

The application provides a text field in which the user can enter a Java expression that returns a double value based on an implicitly declared double x input parameter. The application inserts that expression text into the code template shown in Listing 5, at the location marked $expression. It also generates a unique class name each time, replacing $className in the template. The package name is also a template variable.


Listing 5. Function template
                
package $packageName;
import static java.lang.Math.*;
public class $className
             implements javaxtools.compiler.examples.plotter.Function {
  public double f(double x) { 
    return ($expression) ; 
  }
} 

The application fills in the template via fillTemplate(packageName, className, expr), which returns a String object it then compiles using the CharSequenceCompiler. Exceptions or compiler diagnostics are passed to the log() method or written directly into the scrollable errors component in the application.

The newFunction() method shown in Listing 6 returns an object that implements the Function interface (see the source template in Listing 5):


Listing 6. Plotter's Function newFunction(String expr) method
                
Function newFunction(final String expr) {
   errors.setText("");
   try {
      // generate semi-secure unique package and class names
      final String packageName = PACKAGE_NAME + digits();
      final String className = "Fx_" + (classNameSuffix++) + digits();
      final String qName = packageName + '.' + className;
      // generate the source class as String
      final String source = fillTemplate(packageName, className, expr);
      // compile the generated Java source
      final DiagnosticCollector<JavaFileObject> errs =
            new DiagnosticCollector<JavaFileObject>();
      Class<Function> compiledFunction = stringCompiler.compile(qName, source, errs,
            new Class<?>[] { Function.class });
      log(errs);
      return compiledFunction.newInstance();
   } catch (CharSequenceCompilerException e) {
      log(e.getDiagnostics());
   } catch (InstantiationException e) {
      errors.setText(e.getMessage());
   } catch (IllegalAccessException e) {
      errors.setText(e.getMessage());
   } catch (IOException e) {
      errors.setText(e.getMessage());
   }
   return NULL_FUNCTION;
}

You'll typically generate source classes that extend a known base class or implement a specific interface, so that you can cast the instances to a known type and invoke its methods through a type-safe API. Note that the Function class is used as the generic type parameter T when instantiating the CharSequenceCompiler<T>. This allows the compiledFunction likewise to be typed as Class<Function> and compiledFunction.newInstance() to return a Function instance without requiring casts.

Once it has dynamically generated a Function instance, the application uses it to generate y values for a range of x values and then plot the (x,y) values using the open source JFreeChart API (see Resources). The full source of the Swing application is available in the downloadable source in the javaxtools.compiler.examples.plotter package.

This application's source code generation needs are quite modest. Other applications will benefit from a more sophisticated source template facility, such as Apache Velocity (see Resources).



Back to top


Security risks and strategies

An application that allows arbitrary Java source code to be entered by the user has some inherent security risks. Analogous to SQL injection (see Resources), a system that allows a user or other agent to supply raw Java source for code generation can be exploited maliciously. For example, in the Plotter application presented here, a valid Java expression can contain anonymous nested classes that access system resources, spawn threads for denial-of-service attacks, or perform other exploits. This exploit can be termed Java injection. Such applications should not be deployed in an insecure location in which an untrusted user can access it (such as on a Java EE server as a servlet, or as an applet). Instead, most clients of javax.tools should restrict the user input and translate user requests into secure source code.

Strategies for preserving security when using this package include:

  • Use a custom SecurityManager or ClassLoader that prevents loading of anonymous classes or other classes not under your direct control.

  • Use a source-code scanner or other preprocessor that discards input that uses questionable code constructs. For example, the Plotter can use a java.io.StreamTokenizer and discard input that includes a { (left brace) character, effectively preventing the declaration of anonymous or nested classes.

  • Using the javax.tools API, the JavaFileManager can discard writing of any CLASS file that's unexpected. For example, when compiling a specific class, the JavaFileManager can throw a SecurityExeception for any other calls to store unexpected class files and allow only generated package and class names that the user can't guess or spoof. This is the strategy used by the Plotter's newFunction method.
分享到:
评论

相关推荐

    javax.mail-1.6.0.jar包及移入maven仓库语句

    在Java开发中,`javax.mail`库是一个非常重要的组件,主要用于处理电子邮件的发送与接收。这个库提供了丰富的API,使得开发者能够轻松地实现邮件服务的功能。`javax.mail-1.6.0.jar`是该库的一个特定版本,包含了...

    javax.jms.jar

    Classes contained in javax.jms.jar: javax.transaction.xa.XAResource.class javax.jms.BytesMessage.class javax.jms.Message.class javax.jms.JMSException.class javax.jms.Destination.class javax.jms....

    javax.jms.jar下载

    javax.jms.BytesMessage.class javax.jms.Connection.class javax.jms.ConnectionConsumer.class javax.jms.ConnectionFactory.class javax.jms.ConnectionMetaData.class javax.jms.DeliveryMode.class javax.jms....

    javax.persistence.jar

    Files contained in javax.persistence.jar: META-INF/MANIFEST.MF javax.persistence.Access.class javax.persistence.AccessType.class javax.persistence.AssociationOverride.class javax.persistence....

    javax.ejb.jar下载

    Files contained in javax.ejb.jar: META-INF/MANIFEST.MF javax.ejb.AccessLocalException.class javax.ejb.AccessTimeout.class javax.ejb.ActivationConfigProperty.class javax.ejb.AfterBegin.class javax....

    javax.jar 包免费下载

    javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar javax.jar ...

    javax.servlet jar包---解决找不到javax.servlet.*等问题

    当你遇到“找不到javax.servlet.*”这样的错误时,通常是因为你的项目缺少了这个库,所以需要引入`javax.servlet.jar`来解决问题。 1. **Java Servlet简介** Java Servlet是Java平台上的一个标准,用于扩展服务器...

    javax.servlet.jar下载

    Files contained in javax.servlet.jar: META-INF/MANIFEST.MF javax/servlet/http/LocalStrings.properties javax.servlet.http.HttpSessionBindingListener.class javax.servlet....

    java-jdk1.6-jdk-6u45-windows-x64.zip

    6. XML处理:增强了XML解析和转换功能,例如`javax.xml.bind`包提供了数据绑定框架,使Java对象与XML文档之间的转换更方便。 安装JDK 1.6 on Windows x64平台的步骤非常简单: 1. 解压缩"java-jdk1.6-jdk-6u45-...

    javax.ejb.rar

    META-INF / maven / org.glassfish.main.ejb / javax.ejb / pom.properties META-INF / maven / org.glassfish.main.ejb / javax.ejb / pom.xml javax.ejb.AccessLocalException.class javax.ejb.AccessTimeout....

    javax.mail-1.4.4.jar

    通过java扩展包javax.mail-1.4.4.jar实现邮件发送功能。 import javax.mail.Address; import javax.mail.BodyPart; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Multipart...

    javax.xml.rpc

    "javax.xml.rpc"是Java平台中的一个关键组件,主要用于实现基于XML的远程过程调用(XML-RPC)服务。这个框架允许Java应用程序通过HTTP协议来调用远程服务器上的方法,从而实现分布式计算。在Java EE环境中,它常与...

    javax.ws.rs-api-2.0-API文档-中文版.zip

    赠送jar包:javax.ws.rs-api-2.0.jar; 赠送原API文档:javax.ws.rs-api-2.0-javadoc.jar; 赠送源代码:javax.ws.rs-api-2.0-sources.jar; 赠送Maven依赖信息文件:javax.ws.rs-api-2.0.pom; 包含翻译后的API文档...

    Unable to load class ‘javax.xml.bind.JAXBException‘(csdn).pdf

    在Android Studio中遇到"Unable to load class ‘javax.xml.bind.JAXBException‘"的错误,通常是由于Java运行时环境(JRE)或开发工具包(JDK)与项目的兼容性问题导致的。`javax.xml.bind.JAXBException`是Java的...

    javax.mail-1.6.2-API文档-中英对照版.zip

    赠送jar包:javax.mail-1.6.2.jar; 赠送原API文档:javax.mail-1.6.2-javadoc.jar; 赠送源代码:javax.mail-1.6.2-sources.jar; 赠送Maven依赖信息文件:javax.mail-1.6.2.pom; 包含翻译后的API文档:javax.mail...

    javax.xml.bind.jar

    【标题】"javax.xml.bind.jar" 是一个Java开发中常用的工具包,主要包含了Java对象到XML数据绑定的API,这是Java标准版(Java SE)的一部分,用于帮助开发者将Java类和XML文档之间的转换自动化,从而简化了XML处理。...

    javax.ws.rs-api-2.0.1-API文档-中文版.zip

    赠送jar包:javax.ws.rs-api-2.0.1.jar; 赠送原API文档:javax.ws.rs-api-2.0.1-javadoc.jar; 赠送源代码:javax.ws.rs-api-2.0.1-sources.jar; 赠送Maven依赖信息文件:javax.ws.rs-api-2.0.1.pom; 包含翻译后...

    JDK1.6百度云下载

    5. **脚本引擎支持**:通过`javax.script`包支持脚本语言的嵌入式执行,方便开发者编写混合Java和其他脚本语言的应用程序。 ### 三、JDK1.6的下载与安装 #### 下载方式 - **官方渠道**:通常推荐从Oracle官方网站...

    jdk1.6 chm文档

    7. **Swing与AWT**:在GUI编程方面,JDK 1.6提供了`javax.swing`和`java.awt`包,它们包含了大量的组件和布局管理器,使得开发者能够构建功能丰富的图形用户界面。 总之,JDK 1.6的CHM文档是开发者深入理解JDK 1.6...

    javax.xml.rpc相关jar包

    描述中提到的“解决javax.xml jar包缺失的问题,引进javax.xml.rpc-api-1.1.1.jar”,意味着在开发或运行某个Java项目时,可能会遇到由于缺少`javax.xml.rpc`相关的jar包导致的编译或运行错误。为了解决这个问题,...

Global site tag (gtag.js) - Google Analytics