Java Specialists' Java Training Europehome of the java specialists' newsletter

The Java Specialists' Newsletter
Issue 1802010-02-19 Category: Performance Java version: 6+

GitHub Subscribe Free RSS Feed

Generating Static Proxy Classes - 1/2

by Dr. Heinz M. Kabutz
Abstract:
In this newsletter, we have a look at how we can create new classes in memory and then inject them into any class loader. This will form the basis of a system to generate virtual proxies statically.

A hearty welcome to the 180th edition of The Java(tm) Specialists' Newsletter, sent from my balcony with a stunning view of the snow-capped "Lefka Ori" mountains. On my right is a view down to the Cretan sea. In front I see my neighbour's vineyard and lots of olive trees. The birds think it is spring already and are twittering to their hearts' content...

But please do not be jealous! As from May 2010, you will have an excuse to visit this historical island and do so as a tax write-off! We are now offering courses on Crete at our new conference facility with even better views than from my balcony. Here is what our typical day will look like: We will cover about 250 slides of advanced Java material per day, together with lots of practical exercises to apply your new knowledge. To obtain your JavaSpecialist.EU Certificate of Training (view a sample student report here), you have to complete the entire course, so no slacking will be tolerated! (Tell your boss that :-)

For lunch we will pop up to our favourite restaurant "Taverna Irene", where you will be served delicious traditional Cretan home-cooked food. Coffee time in the afternoons will be held at our pool with the opportunity for a quick dip to cool down. Included in the price is a dinner at the Kalathas Beach Restaurant and a spit roast at my house on the last evening. On other evenings we can go for a stroll through the olive groves and past the watermelon fields down to Tersanas beach for a swim and perhaps a drink or two.

As always, if Crete does not suit you, we also have excellent training partners in Canada, USA, France, Germany and Norway through whom we offer the exact same training as on the island of Crete. Plus, for larger groups we can do in-house courses at your company.

NEW: We have revised our "Advanced Topics" course, covering Reflection, Java NIO, Data Structures, Memory Management and several other useful topics for Java experts to master. 2 days of extreme fun and learning. Extreme Java - Advanced Topics.

Generating Static Proxy Classes - 1/2

Since the code for building the proxy class generator is quite long, I have split the newsletter into several parts. The next issue will probably be sent in ten days from Düsseldorf.

A few weeks ago, we were talking about dynamic proxies at my Java Specialist Master Course in Baltimore, MD. I told my students that generated code would be much faster and that you could do this from within Java. I had known about the javax.tools.JavaCompiler class for a number of years, but had not managed to use it to my satisfaction. I figured out how to compile classes from strings, but the resulting class files were dumped on the disk instead of being loaded into the current class loader.

After much searching and head scratching, I found a website that described how to do this in Groovy, using the JavaCompiler. The key was the ForwardingJavaFileManager class. This led to another excellent article called Dynamic In-memory Compilation. Both articles showed how to convert a String into a byte[] representing the Java class.

Once we have obtained the byte[], we need to turn this into a class. One easy solution is to make a ClassLoader that inherits from our current one. One of the risks is that we then enter ClassLoader hell. I wanted to rather take the dynamic proxy approach, which lets the user specify into which ClassLoader we want our class to be injected. In my solution I use the same mechanism by calling the private static Proxy.defineClass0() method. We could probably also have used the public Unsafe.defineClass() method, but both "solutions" bind us to an implementation of the JDK and are thus not ideal.

In this newsletter, we look at how the Generator works. It uses a GeneratedJavaSourceFile to store the String, in this case actually a CharSequence. The CharSequence interface is implemented by String, StringBuffer and StringBuilder, thus we do not need to create an unnecessary String. We can simply pass in our existing StringBuilder. I wish more classes used the CharSequence interface!

According to the JavaDocs, the recommended URI for a Java source String object is "string:///NameOfClass.java", but "NameOfClass.java" also works, so that is what we will use.

import javax.tools.*;
import java.io.*;
import java.net.*;

class GeneratedJavaSourceFile extends SimpleJavaFileObject {
  private CharSequence javaSource;

  public GeneratedJavaSourceFile(String className,
                                 CharSequence javaSource) {
    super(URI.create(className + ".java"),
        Kind.SOURCE);
    this.javaSource = javaSource;
  }

  public CharSequence getCharContent(boolean ignoreEncodeErrors)
      throws IOException {
    return javaSource;
  }
}
  

The next class is used to hold the generated class file. It presents a ByteArrayOutputStream to the JavaFileManager in the openOutputStream() method. The URI here is not used, so I just specify "generated.class". Once the Java source is compiled, we extract the class with getClassAsBytes().

import javax.tools.*;
import java.io.*;
import java.net.*;

class GeneratedClassFile extends SimpleJavaFileObject {
  private final ByteArrayOutputStream outputStream =
      new ByteArrayOutputStream();

  public GeneratedClassFile() {
    super(URI.create("generated.class"), Kind.CLASS);
  }

  public OutputStream openOutputStream() {
    return outputStream;
  }

  public byte[] getClassAsBytes() {
    return outputStream.toByteArray();
  }
}
  

The GeneratingJavaFileManager forces the JavaCompiler to use the GeneratedClassFile's output stream for writing the class:

import javax.tools.*;
import java.io.*;

class GeneratingJavaFileManager extends
    ForwardingJavaFileManager<JavaFileManager> {
  private final GeneratedClassFile gcf;

  public GeneratingJavaFileManager(
      StandardJavaFileManager sjfm,
      GeneratedClassFile gcf) {
    super(sjfm);
    this.gcf = gcf;
  }

  public JavaFileObject getJavaFileForOutput(
      Location location, String className,
      JavaFileObject.Kind kind, FileObject sibling)
      throws IOException {
    return gcf;
  }
}
  

The Generator class uses the private static "defineClass0" method found in Proxy to add the class into the ClassLoader. This will cause an exception if the class already exists in that class loader. Another approach is to use a new ClassLoader. See the Dynamic In-memory Compilation article for an example of how to do that.

Compiling syntax errors will be printed to System.err. You should replace that code with calls to your favourite logging system.

import javax.tools.*;
import java.lang.reflect.*;
import java.util.*;

public class Generator {
  private static final Method defineClassMethod;
  private static final JavaCompiler jc;

  static {
    try {
      defineClassMethod = Proxy.class.getDeclaredMethod(
          "defineClass0", ClassLoader.class,
          String.class, byte[].class, int.class, int.class);
      defineClassMethod.setAccessible(true);
    } catch (NoSuchMethodException e) {
      throw new ExceptionInInitializerError(e);
    }
    jc = ToolProvider.getSystemJavaCompiler();
    if (jc == null) {
      throw new UnsupportedOperationException(
          "Cannot find java compiler!  " +
              "Probably only JRE installed.");
    }
  }

  public static Class make(ClassLoader loader,
                           String className,
                           CharSequence javaSource) {
    GeneratedClassFile gcf = new GeneratedClassFile();

    DiagnosticCollector<JavaFileObject> dc =
        new DiagnosticCollector<JavaFileObject>();
 
    boolean result = compile(className, javaSource, gcf, dc);
    return processResults(loader, javaSource, gcf, dc, result);
  }

  private static boolean compile(
      String className, CharSequence javaSource,
      GeneratedClassFile gcf,
      DiagnosticCollector<JavaFileObject> dc) {
    GeneratedJavaSourceFile gjsf = new GeneratedJavaSourceFile(
        className, javaSource
    );
    GeneratingJavaFileManager fileManager =
        new GeneratingJavaFileManager(
            jc.getStandardFileManager(dc, null, null), gcf);
    JavaCompiler.CompilationTask task = jc.getTask(
        null, fileManager, dc, null, null, Arrays.asList(gjsf));
    return task.call();
  }

  private static Class processResults(
      ClassLoader loader, CharSequence javaSource,
      GeneratedClassFile gcf, DiagnosticCollector<?> dc,
      boolean result) {
    if (result) {
      return createClass(loader, gcf);
    } else {
      // use your logging system of choice here
      System.err.println("Compile failed:");
      System.err.println(javaSource);
      for (Diagnostic<?> d : dc.getDiagnostics()) {
        System.err.println(d);
      }
      throw new IllegalArgumentException(
          "Could not create proxy - compile failed");
    }
  }

  private static Class createClass(
      ClassLoader loader, GeneratedClassFile gcf) {
    try {
      byte[] data = gcf.getClassAsBytes();
      return (Class) defineClassMethod.invoke(
          null, loader, null, data, 0, data.length);
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new IllegalArgumentException("Proxy problem", e);
    }
  }
}
  

We can try this out by passing a String to the Generator. Here we produce a class that implements Runnable, called WatchThis. We then make an instance of the class and pass it to a thread to run.

public class GeneratorTest {
  public static void main(String[] args) throws Exception {
    Class testClass = Generator.make(
        null, "WatchThis",
        "" +
            "package coolthings;\n" +
            "\n" +
            "public class WatchThis implements Runnable {\n" +
            "  public WatchThis() {\n" +
            "    System.out.println(\"Hey this works!\");\n" +
            "  }\n" +
            "\n" +
            "  public void run() {\n" +
            "    System.out.println(Thread.currentThread());\n" +
            "    while(Math.random() < 0.95) {\n" +
            "      System.out.println(\"Cool stuff!\");\n" +
            "    }\n" +
            "  }\n" +
            "}\n"
    );
    Runnable r = (Runnable) testClass.newInstance();
    Class<? extends Runnable> clazz = r.getClass();
    System.out.println("Our class: " + clazz.getName());
    System.out.println("Classloader: " + clazz.getClassLoader());
    Thread t = new Thread(r, "Cool Thread");
    t.start();
  }
}
  

Distributing Code

The JavaCompiler depends on the tools.jar file that is distributed with the JDK, but not with the JRE. It searches for it in all the usual install places. Thus your users either have to install the JDK or you need to distribute the tools.jar together with your application. See the README file in the JDK install directory for more information of what you may redistribute.

Additional Articles

After I completed my code, I found two more articles that would be of interest: Using built-in JavaCompiler with a custom classloader and Create dynamic applications with javax.tools

Kind regards

Heinz

Performance Articles Related Java Course

Extreme Java - Concurrency and Performance for Java 8
Extreme Java - Advanced Topics for Java 8
Design Patterns
In-House Courses

© 2010-2016 Heinz Kabutz - All Rights Reserved Sitemap
Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners. JavaSpecialists.eu is not connected to Oracle, Inc. and is not sponsored by Oracle, Inc.