|
The Java Specialists' Newsletter
Issue 181 2010-03-01
Category:
Performance
Java version: 6+ Generating Static Proxy Classes - 2/2by Dr. Heinz M. KabutzAbstract: In this newsletter, we show how the Generator described in our previous issue can be used to create virtual proxy classes statically, that is, by generating code instead of using dynamic proxies.

A warm welcome to the 181st edition ofThe Java(tm) Specialists' Newsletter. Another day,
another airport. This time in Athens Elefterios Venizelos en
route to Düsseldorf, Germany, for a Java Specialist Master
Course in German. This afternoon I am visiting my
grandmother who is staying with my aunt and uncle in Hennef.
My "Oma" is celebrating her ##th birthday in two week's time.
Instead of boasting about her amazing genes and longevity, I
will give you a clue of our ages and let you figure it out.
Last year, I was a prime number of years old. This year, my
grandmother turns a prime number of years. (You could say,
she is in her prime :-)) We have this in common: My
age last year and her age this year are both circular primes.
Furthermore, both prime numbers have cousins. Both number are
good primes, but mine is not happy. At least it is lucky :-)
Generating Static Proxy Classes - 2/2
This is the second part of our Generating Static Proxy Classes
newsletters. Click here to read the
first part.
Since there is a lot of code in our last two newsletters, I
have put all of the source files into a zip file. Click here to
download the Java source files.
Let us imagine a company with moral fibre. This altruism is
an expensive luxury to possess, especially when no one is
asking for it. Thus instead of constructing a "real" moral
fibre object, the company will instead contain a "virtual"
moral fibre. This "virtual" object will then create the
"real" moral fibre only when it is used for the first time,
which in our case will be when any method is called.
We start with the company class, which has three
methods: makeMoney(), damageEnvironment() and
becomeFocusOfMediaAttention(). You can easily see that the
company only uses its moral fibre object for the first time
when the media does an expo on its business practices. We
could thus create the moral fibre object lazily. This is what
the virtual proxy design pattern does for us.
package example;
public class Company {
private final String name;
private final MoralFibre moralFibre;
private double cash;
public Company(String name, double cash, MoralFibre moralFibre) {
this.name = name;
this.cash = cash;
this.moralFibre = moralFibre;
System.out.println("Company constructed: " + this);
}
public void damageEnvironment() {
cash += 4000000;
System.out.println("Company.damageEnvironment(): " + this);
}
public void makeMoney() {
cash += 1000000;
System.out.println("Company.makeMoney(): " + this);
}
public void becomeFocusOfMediaAttention() {
cash -= moralFibre.actSociallyResponsibly();
cash -= moralFibre.cleanupEnvironment();
cash -= moralFibre.empowerEmployees();
System.out.println("Look how good we are... " + this);
}
public String toString() {
return String.format("%s has $ %.2f", name, cash);
}
}
The moral fibre interface contains several methods that show
how good the company actually is:
package example;
public interface MoralFibre {
double actSociallyResponsibly();
double empowerEmployees();
double cleanupEnvironment();
}
The real moral fibre object is expensive to create, which I
show by making it contain a very large byte array. It also
prints out a debug message in its initializer block, this
will help us see when it in actual fact is constructed.
package example;
public class MoralFibreImpl implements MoralFibre {
// very expensive to create moral fibre!
private byte[] costOfMoralFibre = new byte[900 * 1000];
{
System.out.println("Moral Fibre Created!");
}
// AIDS orphans
public double actSociallyResponsibly() {
return costOfMoralFibre.length / 3;
}
// shares to employees
public double empowerEmployees() {
return costOfMoralFibre.length / 3;
}
// oiled sea birds
public double cleanupEnvironment() {
return costOfMoralFibre.length / 3;
}
}
The virtual proxies then create the MoralFibreImpl object
only when the do-good methods are called for the first time.
There are several approaches that we could use. We could
take great care to never create more than one real object,
by synchronizing the construction of the MoralFibreImpl.
Or we could increase throughput slightly by using the
AtomicReference, at the risk of occasionally making
multiple objects. If we do not need to care about
concurrency at all, then we can simply ignore this problem.
Since the three solutions differ only in the way they create
the real moral fibre, we can introduce an abstract superclass,
with the do-good functions delegating to the result of the
realSubject() method:
package example.handcoded;
import example.*;
public abstract class VirtualMoralFibre implements MoralFibre {
protected abstract MoralFibre realSubject();
public final double actSociallyResponsibly() {
return realSubject().actSociallyResponsibly();
}
public final double empowerEmployees() {
return realSubject().empowerEmployees();
}
public final double cleanupEnvironment() {
return realSubject().cleanupEnvironment();
}
}
We will start with the easiest solution, which is when we
do not cater for concurrent access in our virtual proxy:
package example.handcoded;
import example.*;
public class MoralFibreProxyByHand0 extends VirtualMoralFibre {
private MoralFibre realSubject;
protected MoralFibre realSubject() {
if (realSubject == null) {
realSubject = new MoralFibreImpl();
}
return realSubject;
}
}
The next hand-written virtual proxy uses atomic references
that may sometimes create multiple instances. If this
happens, we use the first reference that was set:
package example.handcoded;
import example.*;
import java.util.concurrent.atomic.*;
public class MoralFibreProxyByHand1 extends VirtualMoralFibre {
private final AtomicReference<MoralFibre> realSubject =
new AtomicReference<MoralFibre>();
protected MoralFibre realSubject() {
MoralFibre subject = realSubject.get();
if (subject == null) {
subject = new MoralFibreImpl();
if (!realSubject.compareAndSet(null, subject)) {
subject = realSubject.get();
}
}
return subject;
}
}
The most tricky is the last one, where we try to avoid locking
outright, but still only have one instance ever created. In
this solution, similar to what you can find in Effective Java 2nd Ed, Joshua Bloch,
we minimize the number of memory reads by writing our results
to local variables:
package example.handcoded;
import example.*;
import java.util.concurrent.locks.*;
public class MoralFibreProxyByHand2 extends VirtualMoralFibre {
private volatile MoralFibre realSubject;
private final Lock initializationLock = new ReentrantLock();
protected MoralFibre realSubject() {
MoralFibre result = realSubject;
if (result == null) {
initializationLock.lock();
try {
result = realSubject;
if (result == null) {
result = realSubject = new MoralFibreImpl();
}
} finally {
initializationLock.unlock();
}
}
return result;
}
}
These hand-written virtual proxies work, but are a lot
of effort to write and maintain. A good programmer has to be
just the right amount of lazy. If he is too lazy, he will
simply copy & paste his code and modify a few lines. In
the end, his codebase will be unmaintainable, but he might be
promoted due to the amazing lines-of-code (LOC) he has
achieved. If he is not lazy enough, he will not shirk from
doing something over and over again. Both character flaws
(too lazy and too eager) result in code that is hard to
maintain.
We could construct a company with a virtual moral fibre as
follows:
new Company("Cretesoft",20000.0, new MoralFibreProxyByHand1())
Using Dynamic Proxies
Instead of writing the code by hand, we could also use a
dynamic proxy. This only works when the proxied type is an
interface. Unfortunately there is a run-time overhead
every time we call any of the dynamic proxy's methods.
Here is how the dynamic proxy invocation handler would look:
package util.proxy;
import java.lang.reflect.*;
public class VirtualDynamicProxyNotThreadSafe
implements InvocationHandler {
private final Class realSubjectClass;
private Object realSubject;
public VirtualDynamicProxyNotThreadSafe(Class realSubjectClass) {
this.realSubjectClass = realSubjectClass;
}
private Object realSubject() throws Exception {
if (realSubject == null) {
realSubject = realSubjectClass.newInstance();
}
return realSubject;
}
public Object invoke(
Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(realSubject(), args);
}
}
The other concurrency types are left as an exercise for the
reader :-)
(As an aside, you should actually never call
Class.newInstance(). Better is to use:
Class.getConstructor((Class[])null).newInstance().
Come to my course if you want to know why :-))
Generating Proxies as Static Classes
Instead of using a dynamic proxy, we could also generate
code according to the class structure that we are trying to
wrap. We could then use our Generator to compile the class and
load it into our class loader. When we choose to generate a
proxy, we want to specify whether it is dynamic or static and
what the concurrency level is. We do that with two enums:
package util.proxy;
public enum ProxyType {
STATIC, DYNAMIC
}
package util.proxy;
public enum Concurrency {
NONE, SOME_DUPLICATES, NO_DUPLICATES;
}
When we generate class names, we want them to appear as they
would in source code. This means formatting arrays correctly
and hiding references to the package java.lang.
package util.proxy;
public class Util {
public static String prettyPrint(Class clazz) {
return prettyPrint(clazz, "");
}
public static String prettyPrint(Class c, String postfix) {
if (c.isArray()) {
return prettyPrint(c.getComponentType(), postfix + "[]");
} else {
Package pack = c.getPackage();
if (pack != null && pack.getName().equals("java.lang")) {
return c.getSimpleName() + postfix;
}
return c.getName() + postfix;
}
}
}
The ProxyGenerator builds up the CharSequence for the proxy
subject, taking into account the concurrency level and proxy
type. Classes that were already generated are kept in a
WeakHashMap, to avoid making the same class twice. The cache
is similar to what you would probably find in the
java.lang.reflect.Proxy class.
package util.proxy;
import util.gen.*;
import java.lang.reflect.*;
import java.util.*;
public class ProxyGenerator {
private static final WeakHashMap cache = new WeakHashMap();
public static <T> T make(
Class<T> subject,
Class<? extends T> realClass,
Concurrency concurrency,
ProxyType type) {
return make(subject.getClassLoader(),
subject, realClass, concurrency, type);
}
public static <T> T make(
Class<T> subject, Class<? extends T> realClass,
Concurrency concurrency) {
return make(subject, realClass, concurrency,
ProxyType.STATIC);
}
public static <T> T make(ClassLoader loader,
Class<T> subject,
Class<? extends T> realClass,
Concurrency concurrency,
ProxyType type) {
Object proxy = null;
if (type == ProxyType.STATIC) {
proxy = createStaticProxy(loader, subject,
realClass, concurrency);
} else if (type == ProxyType.DYNAMIC) {
proxy = createDynamicProxy(loader,
subject, realClass, concurrency);
}
return subject.cast(proxy);
}
private static Object createStaticProxy(
ClassLoader loader, Class subject,
Class realClass, Concurrency concurrency) {
Map clcache;
synchronized (cache) {
clcache = (Map) cache.get(loader);
if (clcache == null) {
cache.put(loader, clcache = new HashMap());
}
}
try {
Class clazz;
CacheKey key = new CacheKey(subject, concurrency);
synchronized (clcache) {
clazz = (Class) clcache.get(key);
if (clazz == null) {
VirtualProxySourceGenerator vpsg = create(subject,
realClass, concurrency);
clazz = Generator.make(loader, vpsg.getProxyName(),
vpsg.getCharSequence());
clcache.put(key, clazz);
}
}
return clazz.newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
private static VirtualProxySourceGenerator create(
Class subject, Class realClass,
Concurrency concurrency) {
switch (concurrency) {
case NONE:
return new VirtualProxySourceGeneratorNotThreadsafe(
subject, realClass
);
case SOME_DUPLICATES:
return new VirtualProxySourceGeneratorSomeDuplicates(
subject, realClass
);
case NO_DUPLICATES:
return new VirtualProxySourceGeneratorNoDuplicates(
subject, realClass
);
default:
throw new IllegalArgumentException(
"Unsupported Concurrency: " + concurrency);
}
}
private static Object createDynamicProxy(
ClassLoader loader, Class subject,
Class realClass, Concurrency concurrency) {
if (concurrency != Concurrency.NONE) {
throw new IllegalArgumentException(
"Unsupported Concurrency: " + concurrency);
}
return Proxy.newProxyInstance(
loader,
new Class<?>[]{subject},
new VirtualDynamicProxyNotThreadSafe(realClass));
}
private static class CacheKey {
private final Class subject;
private final Concurrency concurrency;
private CacheKey(Class subject, Concurrency concurrency) {
this.subject = subject;
this.concurrency = concurrency;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CacheKey that = (CacheKey) o;
if (concurrency != that.concurrency) return false;
return subject.equals(that.subject);
}
public int hashCode() {
return 31 * subject.hashCode() + concurrency.hashCode();
}
}
}
The actual VirtualProxySourceGenerator builds up the
CharSequence based on the class structure of the proxy
subject, in our case the MoralFibre interface:
package util.proxy;
import static util.proxy.Util.*;
import java.io.*;
import java.lang.reflect.*;
public abstract class VirtualProxySourceGenerator {
protected final Class subject;
protected final Class realSubject;
private final String proxy;
private CharSequence charSequence;
public VirtualProxySourceGenerator(
Class subject, Class realSubject, Concurrency type) {
this.subject = subject;
this.realSubject = realSubject;
this.proxy = makeProxyName(subject, type);
}
private static String makeProxyName(Class subject,
Concurrency type) {
return "$$_" + subject.getName().replace('.', '_') +
"Proxy_" + Integer.toHexString(System.identityHashCode(
subject.getClassLoader())) + "_" + type;
}
public String getProxyName() {
return proxy;
}
public CharSequence getCharSequence() {
if (charSequence == null) {
StringWriter sw = new StringWriter();
generateProxyClass(new PrintWriter(sw));
charSequence = sw.getBuffer();
}
return charSequence;
}
private void generateProxyClass(PrintWriter out) {
addClassDefinition(out);
addProxyBody(out);
out.close();
}
private void addProxyBody(PrintWriter out) {
addRealSubjectCreation(out, subject.getName(),
realSubject.getName());
addProxiedMethods(out);
out.println("}");
}
protected abstract void addRealSubjectCreation(
PrintWriter out, String name, String realName);
private void addClassDefinition(PrintWriter out) {
addImports(out);
out.printf("public class %s %s %s {%n",
proxy, getInheritanceType(subject), subject.getName());
}
private String getInheritanceType(Class subject) {
return subject.isInterface() ? "implements" : "extends";
}
protected void addImports(PrintWriter out) {
}
private void addToStringIfInterface(PrintWriter out) {
if (subject.isInterface()) {
out.println();
out.println(" public String toString() {");
out.println(" return realSubject().toString();");
out.println(" }");
}
}
private void addProxiedMethods(PrintWriter out) {
for (Method m : subject.getMethods()) {
addProxiedMethod(out, m);
}
addToStringIfInterface(out);
}
private void addProxiedMethod(PrintWriter out, Method m) {
if (Modifier.isFinal(m.getModifiers())) return;
addMethodSignature(out, m);
addMethodBody(out, m);
out.printf(");%n }%n");
}
private void addMethodSignature(PrintWriter out, Method m) {
out.printf("%n public %s", prettyPrint(m.getReturnType()));
out.printf(" %s(", m.getName());
addParameterList(out, m);
out.printf(") {%n ");
}
private void addParameterList(PrintWriter out, Method m) {
Class<?>[] types = m.getParameterTypes();
for (int i = 0; i < types.length; i++) {
String next = i == types.length - 1 ? "" : ", ";
out.printf("%s p%d%s", prettyPrint(types[i]), i, next);
}
}
private void addMethodBody(PrintWriter out, Method m) {
addReturnKeyword(out, m);
addMethodBodyDelegatingToRealSubject(out, m);
}
private void addReturnKeyword(PrintWriter out, Method m) {
if (m.getReturnType() != void.class) {
out.print("return ");
}
}
private void addMethodBodyDelegatingToRealSubject(
PrintWriter out, Method m) {
out.printf("realSubject().%s(", m.getName());
addMethodCall(out, m);
}
private void addMethodCall(PrintWriter out, Method m) {
Class<?>[] types = m.getParameterTypes();
for (int i = 0; i < types.length; i++) {
String next = i == types.length - 1 ? "" : ", ";
out.printf("p%d%s", i, next);
}
}
}
We then have different subclasses for each of the concurrency
levels specified. For example, if we do not want it to be
threadsafe, we can just use this class. You can easily see
how similar the generated code will be to the hand written
virtual proxy:
package util.proxy;
import java.io.*;
class VirtualProxySourceGeneratorNotThreadsafe
extends VirtualProxySourceGenerator {
public VirtualProxySourceGeneratorNotThreadsafe(
Class subject, Class realSubject) {
super(subject, realSubject, Concurrency.NONE);
}
protected void addRealSubjectCreation(PrintWriter out,
String name,
String realName) {
out.printf(" private %s realSubject;%n", name);
out.println();
out.printf(" private %s realSubject() {%n", name);
out.printf(" if (realSubject == null) {%n");
out.printf(" realSubject = new %s();%n", realName);
out.println(" }");
out.println(" return realSubject;");
out.println(" }");
}
}
If we want to have less possible duplicates, we can this
class to generate the virtual proxy. It uses the
AtomicReference, which in some cases might construct
duplicates:
package util.proxy;
import java.io.*;
class VirtualProxySourceGeneratorSomeDuplicates
extends VirtualProxySourceGenerator {
public VirtualProxySourceGeneratorSomeDuplicates(
Class subject, Class realSubject) {
super(subject, realSubject, Concurrency.SOME_DUPLICATES);
}
protected void addImports(PrintWriter out) {
out.println("import java.util.concurrent.atomic.*;");
out.println();
}
protected void addRealSubjectCreation(PrintWriter out,
String name,
String realName) {
out.printf(" private final AtomicReference<%s> " +
"ref = new AtomicReference<%1$s>();%n", name);
out.println();
out.printf(" private %s realSubject() {%n", name);
out.printf(" %s result = ref.get()%n;", name);
out.printf(" if (result == null) {%n");
out.printf(" result = new %s();%n", realName);
out.printf(" if (!ref.compareAndSet(null, result)) {%n");
out.printf(" result = ref.get();%n");
out.println(" }");
out.println(" }");
out.println(" return result;");
out.println(" }");
}
}
If we want to have absolutely no duplicates, we could use
the following class to generate our virtual proxy. In this
case, we need to also import the
java.util.concurrent.* package for the
Lock and ReentrantLock classes:
package util.proxy;
import java.io.*;
class VirtualProxySourceGeneratorNoDuplicates
extends VirtualProxySourceGenerator {
public VirtualProxySourceGeneratorNoDuplicates(
Class subject, Class realSubject) {
super(subject, realSubject, Concurrency.NO_DUPLICATES);
}
protected void addImports(PrintWriter out) {
out.println("import java.util.concurrent.locks.*;");
out.println();
}
protected void addRealSubjectCreation(PrintWriter out,
String name,
String realName) {
out.printf(" private volatile %s realSubject;%n", name);
out.println(" private final Lock initializationLock = " +
"new ReentrantLock();");
out.println();
out.printf(" private %s realSubject() {%n", name);
out.printf(" %s result = realSubject;%n", name);
out.printf(" if (result == null) {%n");
out.printf(" initializationLock.lock();%n");
out.printf(" try {%n");
out.printf(" result = realSubject;%n");
out.printf(" if (result == null) {%n");
out.printf(" result = realSubject = new %s();%n",
realName);
out.printf(" }%n");
out.printf(" } finally {%n");
out.printf(" initializationLock.unlock();%n");
out.printf(" }%n");
out.printf(" }%n");
out.printf(" return result;%n");
out.println(" }");
}
}
You probably noticed that these generator classes are all
package access. We cannot use them directly, rather, we need
to call the ProxyGenerator's factory methods.
Here is some test code that makes two companies. The first
one is constructed with a real instance of MoralFibre. The
second just contains a virtual proxy:
import example.*;
import util.proxy.*;
public class CompanyTest {
public static void main(String[] args) {
Company company = new Company("Cretesoft", 10000.0,
new MoralFibreImpl());
company.makeMoney();
company.damageEnvironment();
company.becomeFocusOfMediaAttention();
Company company2 = new Company("Cretesoft2", 20000.0,
ProxyGenerator.make(MoralFibre.class,
MoralFibreImpl.class,
Concurrency.NONE));
company2.makeMoney();
company2.makeMoney();
company2.makeMoney();
company2.damageEnvironment();
company2.becomeFocusOfMediaAttention();
}
}
When we run this, we see how the moral fibre is constructed
lazily for the second company:
Moral Fibre Created!
Company constructed: Cretesoft has $ 10000.00
Company.makeMoney(): Cretesoft has $ 1010000.00
Company.damageEnvironment(): Cretesoft has $ 5010000.00
Look how good we are... Cretesoft has $ 4110000.00
Company constructed: Cretesoft2 has $ 20000.00
Company.makeMoney(): Cretesoft2 has $ 1020000.00
Company.makeMoney(): Cretesoft2 has $ 2020000.00
Company.makeMoney(): Cretesoft2 has $ 3020000.00
Company.damageEnvironment(): Cretesoft2 has $ 7020000.00
Moral Fibre Created!
Look how good we are... Cretesoft2 has $ 6120000.00
The ProxyGenerator can create virtual proxies of both classes
and interfaces. With the dynamic proxy, you can only create
proxies of interfaces.
One more thing that is interesting to point out. When we
generate the proxy code, the ProxyGenerator makes up a name
that is likely to be unique. You can see the different class
names when you run the following test code:
import example.*;
import util.proxy.*;
import javax.swing.*;
import javax.swing.table.*;
import java.util.concurrent.*;
public class ProxyGeneratorTest {
public static void main(String[] args) {
for (Concurrency type : Concurrency.values()) {
test(type);
}
}
private static void test(Concurrency concurrency) {
System.out.println();
System.out.println("Concurrency: " + concurrency);
MoralFibre mf = ProxyGenerator.make(MoralFibre.class,
MoralFibreImpl.class, concurrency);
System.out.println("Moral Fibre: " + mf.getClass());
System.out.println(mf);
mf.actSociallyResponsibly();
System.out.println();
ConcurrentMap<String, Integer> map =
ProxyGenerator.make(ConcurrentMap.class,
ConcurrentHashMap.class, concurrency);
System.out.println(map.getClass());
map.put("Hello", 23);
map.put("Hello2", 24);
map.put("Hello3", 25);
System.out.println(map);
System.out.println();
AbstractTableModel model =
ProxyGenerator.make(AbstractTableModel.class,
DefaultTableModel.class, concurrency);
System.out.println(model.getClass());
JTable table = new JTable(model);
System.out.println();
}
}
This ProxyGenerator can make virtual proxies dynamically,
without the runtime overhead of dynamic proxies. In
addition, because this is generated code, can also extend
classes, which is not possible with dynamic proxies.
A word of caution about generated code. I prefer to hide
this from the developer, although he could of course view it
for debugging purposes. It is just too tempting to add a
little patch here and there. Before too long, your code will
be one-way code.
Kind regards from Düsseldorf
Heinz
Performance Articles
Related Java Course
Discuss at The Java Specialist Club
|