Abstract: 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 :-)
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
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 onto our public GitHub repository.
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.
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:
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.
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:
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:
public class VirtualMoralFibreNotThreadSafe 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:
import java.util.concurrent.atomic.*; public class VirtualMoralFibreLockFree 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:
import java.util.concurrent.locks.*; public class VirtualMoralFibreThreadSafe 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 VirtualMoralFibreLockFree())
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:
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 :-))
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:
public enum ProxyType { STATIC, DYNAMIC } 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.
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.
import eu.javaspecialists.tjsn.util.codegen.*; 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:
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", Util.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", Util.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:
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:
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:
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 eu.javaspecialists.tjsn.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
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.