Build your own interface - dynamic code generation
Posted by emcmanus on October 18, 2006 at 6:26 AM PDT
Sometimes static code isn't enough and you need to build code
dynamically, at run time. That's usually a hefty proposition,
but if the code you need to build is just an interface, it's
actually relatively simple. Here are some of the reasons you
might want to build interfaces at run time and how you might go
about it.
dynamically, at run time. That's usually a hefty proposition,
but if the code you need to build is just an interface, it's
actually relatively simple. Here are some of the reasons you
might want to build interfaces at run time and how you might go
about it.
Static and dynamic code
The first thing to remember before launching into dynamic code
generation is that it is nearly always a much better idea to
use static code generation if you can. Static code
generation just means writing code as
and compiling them with the Java compiler in the usual way.
Dynamic code generation means creating code at run time that
didn't exist when you compiled your program.
Dynamically-created code can often only be run using
reflection.
generation is that it is nearly always a much better idea to
use static code generation if you can. Static code
generation just means writing code as
.java
filesand compiling them with the Java compiler in the usual way.
Dynamic code generation means creating code at run time that
didn't exist when you compiled your program.
Dynamically-created code can often only be run using
reflection.
To make the distinction clearer, suppose you have an interface
that looks like this:
that looks like this:
public interface SomeInterface {
public void doSomething();
}
If
you can invoke its
code:
SomeInterface
is known at compile time, thenyou can invoke its
doSomething()
just by writing Javacode:
SomeInterface object = ...<i>whateveri>...;
object.doSomething();
Suppose, though, that your application creates the
by writing the code above to a file, invoking the Java compiler
on that file, then loading the resultant class. Then your
application couldn't contain the code above because
was compiled. So you would have to use reflection instead:
SomeInterface
class at run time. It might do thisby writing the code above to a file, invoking the Java compiler
on that file, then loading the resultant class. Then your
application couldn't contain the code above because
SomeInterface
didn't exist when your applicationwas compiled. So you would have to use reflection instead:
Class<?> someInterfaceClass =
Class.forName("SomeInterface", false, someClassLoader);
...<i>get an instance of someInterfaceClass somehowi>...
Method doSomethingMethod = someInterfaceClass.getMethod("doSomething");
doSomethingMethod.invoke(instance);
I'm deliberately omitting a lot of detail for the moment. The
point is just that the code to invoke the method looks
completely different, and a lot more complicated. Hence my
advice to avoid dynamic code generation if you can.
point is just that the code to invoke the method looks
completely different, and a lot more complicated. Hence my
advice to avoid dynamic code generation if you can.
The class java.lang.reflect.Proxy
The Java SE platform already contains some support for dynamic
code generation, notably in the class href="http://download.java.net/jdk6/doc/api/java/lang/reflect/Proxy.html">java.lang.reflect.Proxy.
This class allows you to take any interface and produce a class
that implements that interface, where calling any method results in
a call to anhref="http://download.java.net/jdk6/doc/api/java/lang/reflect/InvocationHandler.html#invoke(java.lang.Object,%20java.lang.reflect.Method,%20java.lang.Object[])">
method that you supply. So for example you could use this to
produce a dummy implementation of any interface, where every
method just returns null, or where every method throws an
exception. Or you could perform some action in every method
before calling the same method in a "real" implementation of the
same interface, such as logging the method call, performing
security checks, or setting a href="http://download.java.net/jdk6/doc/api/java/lang/ThreadLocal.html">ThreadLocal.
code generation, notably in the class href="http://download.java.net/jdk6/doc/api/java/lang/reflect/Proxy.html">java.lang.reflect.Proxy.
This class allows you to take any interface and produce a class
that implements that interface, where calling any method results in
a call to anhref="http://download.java.net/jdk6/doc/api/java/lang/reflect/InvocationHandler.html#invoke(java.lang.Object,%20java.lang.reflect.Method,%20java.lang.Object[])">
invoke
method that you supply. So for example you could use this to
produce a dummy implementation of any interface, where every
method just returns null, or where every method throws an
exception. Or you could perform some action in every method
before calling the same method in a "real" implementation of the
same interface, such as logging the method call, performing
security checks, or setting a href="http://download.java.net/jdk6/doc/api/java/lang/ThreadLocal.html">ThreadLocal.
The way
example. Suppose you do something like this:
Proxy
works might be clearer with anexample. Suppose you do something like this:
href="http://download.java.net/jdk6/doc/api/javax/management/MBeanServer.html">MBeanServer proxy =
(MBeanServer) Proxy.newProxyInstance(...details omitted..., myHandler);
proxy.registerMBean(someObject, someObjectName);
Then the call to
in a call like this:
proxy.registerMBean
will resultin a call like this:
myHandler.invoke(proxy, method, new Object[] {someObject, someObjectName});
where
representinghref="http://download.java.net/jdk6/doc/api/javax/management/MBeanServer.html#registerMBean(java.lang.Object,%20javax.management.ObjectName)">MBeanServer.registerMBean.
This is where you could execute arbitrary code that throws an
exception or logs the call or whatever.
method
is a href="http://download.java.net/jdk6/doc/api/java/lang/reflect/Method.html">java.lang.reflect.Methodrepresentinghref="http://download.java.net/jdk6/doc/api/javax/management/MBeanServer.html#registerMBean(java.lang.Object,%20javax.management.ObjectName)">MBeanServer.registerMBean.
This is where you could execute arbitrary code that throws an
exception or logs the call or whatever.
Dynamically generating an interface suddenly becomes more
interesting in conjunction with this
Once we have the interface, we can use
an implementation of it.
interesting in conjunction with this
Proxy
class.Once we have the interface, we can use
Proxy
to getan implementation of it.
Dynamic code generation and the JMX API
The JMX API uses reflection heavily. If we can dynamically
generate an interface, then we can give that interface to a JMX
method that will use reflection on it. Here are a couple of
ways we can exploit this.
generate an interface, then we can give that interface to a JMX
method that will use reflection on it. Here are a couple of
ways we can exploit this.
Recall that a Standard MBean is a Java class that implements an
interface called the MBean interface. That interface
determines which methods in the class are management
methods. When you register an instance of the class with the
JMX Agent (MBean Server), these methods can be called by a
management client such as href="http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html">JConsole.
interface called the MBean interface. That interface
determines which methods in the class are management
methods. When you register an instance of the class with the
JMX Agent (MBean Server), these methods can be called by a
management client such as href="http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html">JConsole.
Here's an example of an MBean class and its accompanying
interface:
interface:
public class Cache implements CacheMBean {
public int getSize</b>() {
return size;
}
public void setSizeb>(int size) {
this.size = size;
}
public int getUsed</b>() {
return used;
}
...
}
public interface CacheMBean {
public int getSizeb>();
public void setSize</b>(int size);
public int getUsedb>();
}
It can be troublesome to maintain two different source files,
for the public class
to specify the managed methods with an annotation instead. In
other words, to get rid of
write
for the public class
Cache
and the public interfaceCacheMBean
. We often hear people ask for the abilityto specify the managed methods with an annotation instead. In
other words, to get rid of
CacheMBean
and insteadwrite
Cache
like this:public class Cache {
@Managed</b>
public int getSize() {
return size;
}
@Managedb>
public void setSize(int size) {
this.size = size;
}
@Managedb>
public int getUsed() {
return used;
}
...
}
How might we implement that?
Model MBeans
One possibility is to use Model MBeans. Model MBeans
allow you to link MBean attributes and operations to arbitrary
public methods of any class. We could use reflection to pick
out the methods with the
give these methods to thehref="http://download.java.net/jdk6/doc/api/javax/management/modelmbean/RequiredModelMBean.html">RequiredModelMBean
class to make an MBean out of them.
allow you to link MBean attributes and operations to arbitrary
public methods of any class. We could use reflection to pick
out the methods with the
@Managed
annotation, andgive these methods to thehref="http://download.java.net/jdk6/doc/api/javax/management/modelmbean/RequiredModelMBean.html">RequiredModelMBean
class to make an MBean out of them.
That's fine as far as it goes, but suppose we want to add a
further feature where you can add
field and have that field be exposed as a read-only MBean
attribute. In other words, we'd replace the
further feature where you can add
@Managed
to afield and have that field be exposed as a read-only MBean
attribute. In other words, we'd replace the
getUsed
method above with this:public class Cache {
@Managed</b>
private int used;
@Managedb>
public int getSize() {
return size;
}
@Managedb>
public void setSize(int size) {
this.size = size;
}
...
}
RequiredModelMBean allows you to expose public methods of a
class, but not fields (public or otherwise). So it is not
powerful enough to handle this extension.
class, but not fields (public or otherwise). So it is not
powerful enough to handle this extension.
Annotation processors
A second possibility is to use an
annotation processor to generate an MBean wrapper from the
like this:
annotation processor to generate an MBean wrapper from the
Cache
class. This wrapper might look somethinglike this:
public interface Cache$WrapperMBean {
public int getSize();
public void setSize(int size);
public int getUsed();
}
public class Cache$Wrapper implements Cache$WrapperMBean {
private final Cache wrapped;
private final Field usedField;
public Cache$Wrapper(Cache wrapped) {
this.wrapped = wrapped;
try {
usedField = Cache.class.getDeclaredField("used");
usedField.setAccessible(true);
} catch (Exception e) {
throw new IllegalArgumentException("Field 'used' inaccessible", e);
}
}
public int getSize() {
return wrapped.getSize();
}
public void setSize(int size) {
wrapped.setSize(size);
}
public int getUsed() {
try {
return usedField.getInt(wrapped);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Field 'used' inaccessible", e);
}
}
}
We use reflection to bypass the usual Java language checks that
would prevent the generated
from accessing the private
general but is harmless in this particular case because we have
explicitly annotated the field to make it happen, and we are
only reading the field.
would prevent the generated
Cache$Wrapper
classfrom accessing the private
used
field of theCache
class. This is not very good practice ingeneral but is harmless in this particular case because we have
explicitly annotated the field to make it happen, and we are
only reading the field.
We're still talking about static code generation here, because
annotation processors run at compile time.
compiler via the annotation processor, and immediately compiled.
annotation processors run at compile time.
Cache$Wrapper.java
andCache$WrapperMBean.java
would be generated by the Javacompiler via the annotation processor, and immediately compiled.
Dynamically generating the MBean interface
A third possibility, then, is to generate the equivalent of the
classes at run time. That is, when somebody asks to register
the
methods and fields that have the
annotation, and we generate the
interface that we saw above. Once we have this interface, we
can use java.lang.reflect.Proxy to produce the equivalent of the
Cache$Wrapper
and Cache$WrapperMBean
classes at run time. That is, when somebody asks to register
the
Cache
class, we use reflection to discover themethods and fields that have the
@Managed
annotation, and we generate the
Cache$WrapperMBean
interface that we saw above. Once we have this interface, we
can use java.lang.reflect.Proxy to produce the equivalent of the
Cache$Wrapper
class.
So how do we go about generating the interface dynamically?
There are at least three ways.
There are at least three ways.
- Write the Java source code to a file and invoke the Java
compiler on that file. You might do this usinghref="http://download.java.net/jdk6/doc/api/java/lang/Runtime.html#exec(java.lang.String)">Runtime.exec,
for example. As of Java SE 6, you can use the href="http://download.java.net/jdk6/doc/api/javax/tools/package-summary.html">javax.tools
package to invoke the compiler. Strictly speaking though,
you're not absolutely guaranteed to find a Java compiler in
either case. - Use a standard byte-code generation library, such as href="http://jakarta.apache.org/bcel/">Apache BCEL. Pulling
in the whole of BCEL just to generate an interface seems a bit
like overkill, though. - Generate the byte code yourself. This is usually a major
undertaking. But in fact most of the difficulty in generating
byte code is involved in generating executable code. Interfaces
don't contain any executable code, so it is not unreasonably
difficult to generate the byte code for an interface. Plus you
get lots of extra Geek Cred, so that's what I'll show here.
Generating the byte code for an interface
(If you're not interested in the details of byte code
generation, you'll still need to know how to load the generated
class, which is the subject of the next
section.)
generation, you'll still need to know how to load the generated
class, which is the subject of the next
section.)
Before generating byte code, you do need to have an
understanding of the class file format. The reference here is
href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html">Chapter
4 of the Java VM Spec. (There's an href="http://java.sun.com/docs/books/vmspec/2nd-edition/ClassFileFormat-Java5.pdf">update
containing the changes needed for Generics, but that doesn't
concern us here.)
understanding of the class file format. The reference here is
href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html">Chapter
4 of the Java VM Spec. (There's an href="http://java.sun.com/docs/books/vmspec/2nd-edition/ClassFileFormat-Java5.pdf">update
containing the changes needed for Generics, but that doesn't
concern us here.)
The main difficulty is that the class file is in two main
parts, the constant pool, and "everything else".
Everything else here is the class properties (such as its name and
superclass) and the contained fields and methods. Every time
there is a class name, string, or other constant in this second
part, it is actually a reference to the constant pool, as
illustrated below.
parts, the constant pool, and "everything else".
Everything else here is the class properties (such as its name and
superclass) and the contained fields and methods. Every time
there is a class name, string, or other constant in this second
part, it is actually a reference to the constant pool, as
illustrated below.
So this means that when generating a class file, you are
actually generating two blocks of data in parallel, the constant
pool and the rest. Typically this means generating everything
in a buffer while recording the constant pool entries, then
writing the constant pool followed by the buffer.
actually generating two blocks of data in parallel, the constant
pool and the rest. Typically this means generating everything
in a buffer while recording the constant pool entries, then
writing the constant pool followed by the buffer.
Without further ado, let's look at the outline of the
InterfaceBuilder
class.public class InterfaceBuilder {
public static byte[] buildInterface(String name, XMethod[] methods) {...}
}
The idea here is that you call
the interface you want to fabricate and the list of methods you
want it to contain. We're using our own class
to fabricate a
class that contains it. In the case of
applied to the
generate a
contains that method.
InterfaceBuilder.buildInterface
with the name ofthe interface you want to fabricate and the list of methods you
want it to contain. We're using our own class
XMethod
here rather thanjava.lang.reflect.Method
, because there is no wayto fabricate a
Method
other than to reflect on aclass that contains it. In the case of
@Managed
applied to the
used
field above, we want togenerate a
getUsed()
method even though no classcontains that method.
The
bunch of values. Here's the outline.
XMethod
class is just a tedious wrapper for abunch of values. Here's the outline.
/**
* Object encapsulating the same information as a Method but that we can
* instantiate explicitly.
*/
public class XMethod {
public XMethod(Method m) {
this(m.getName(), m.getParameterTypes(), m.getReturnType());
}
public XMethod(String name, Class<?>[] paramTypes, Class<?> returnType) {
this.name = name;
this.paramTypes = paramTypes;
this.returnType = returnType;
}
public String getName() {
return name;
}
public Class<?>[] getParameterTypes() {
return paramTypes.clone();
}
public Class<?> getReturnType() {
return returnType;
}
// ...define equals, hashCode, toString here...
private final String name;
private final Class<?>[] paramTypes;
private final Class<?> returnType;
}
Now let's look at
When you call its
construct a private instance of
it will use to store the constant pool. We can expand the outline
as follows.
InterfaceBuilder
in more detail.When you call its
buildInterface
method, it willconstruct a private instance of
InterfaceBuilder
thatit will use to store the constant pool. We can expand the outline
as follows.
public class InterfaceBuilder {
private static final int CONSTANT_Utf8 = 1, CONSTANT_Class = 7;
/**
* Return the byte code for an interface called {@code name} that
* contains the given {@code methods}. Every method in the generated
* interface will be declared to throw {@link Exception}.
*/
public static byte[] buildInterface(String name, XMethod[] methods) {
try {
return new InterfaceBuilder().build(name, methods);
} catch (IOException e) {
// we're only writing arrays, so this "can't happen"
throw new RuntimeException(e);
}
}
private InterfaceBuilder() {
}
private byte[] build(String name, XMethod[] methods) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
dout.writeInt(0xcafebabe); // u4 magic
dout.writeShort(0); // u2 minor_version
dout.writeShort(45); // u2 major_version (Java 1.0.2)
byte[] afterConstantPool = buildAfterConstantPool(name, methods);
writeConstantPool(dout);
dout.write(afterConstantPool);
return bout.toByteArray();
}
// ...
private final Map<List<?>, Integer> poolMap =
new LinkedHashMap<List<?>, Integer>();
private int poolIndex = 1;
}
This corresponds to the approach described above, where we
generate everything into a buffer (
while recording the constants, then write the constant pool, then
write the buffer.
generate everything into a buffer (
afterConstantPool
)while recording the constants, then write the constant pool, then
write the buffer.
I've somewhat capriciously chosen to write a class file in the
format from Java 1.0.2, way back when the Java platform didn't
even have reflection. In fact nothing we are going to use has
changed in the class file format since then. But you might prefer
to use the format from Java 1.n here, in which case you
should use 44+n instead of 45 here, for example 48 for Java
1.4.
format from Java 1.0.2, way back when the Java platform didn't
even have reflection. In fact nothing we are going to use has
changed in the class file format since then. But you might prefer
to use the format from Java 1.n here, in which case you
should use 44+n instead of 45 here, for example 48 for Java
1.4.
Now let's take a look at
which will generate the meat of the class file, and in particular
the methods. You might want to have a look at that href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html">chapter
from the JVM spec in another window if you really want to
understand what's going on here.
buildAfterConstantPool
,which will generate the meat of the class file, and in particular
the methods. You might want to have a look at that href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html">chapter
from the JVM spec in another window if you really want to
understand what's going on here.
private byte[] buildAfterConstantPool(String name, XMethod[] methods)
throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
dout.writeShort(Modifier.PUBLIC|Modifier.INTERFACE|Modifier.ABSTRACT);
// u2 access_flags
dout.writeShort(classConstant(name));
// u2 this_class
dout.writeShort(classConstant(Object.class.getName()));
// u2 super_class
dout.writeShort(0); // u2 interfaces_count
dout.writeShort(0); // u2 fields_count
dout.writeShort(methods.length); // u2 methods_count
for (int i = 0; i < methods.length; i++) {
dout.writeShort(Modifier.PUBLIC|Modifier.ABSTRACT);
// u2 access_flags
dout.writeShort(stringConstant(methods[i].getName()));
// u2 name_index
dout.writeShort(stringConstant(methodDescriptor(methods[i])));
// u2 descriptor_index
dout.writeShort(1); // u2 attributes_count
dout.writeShort(stringConstant("Exceptions"));
// u2 attribute_name_index
dout.writeInt(4); // u4 attribute_length:
dout.writeShort(1); // (u2 number_of_exceptions
dout.writeShort(classConstant(Exception.class.getName()));
// + u2 exception_index)
}
dout.writeShort(0); // u2 attributes_count (for class)
return bout.toByteArray();
}
We generate the equivalent of a
is because if a
exception that is not declared in the
clause, the exception will be wrapped in anhref="http://download.java.net/jdk6/doc/api/java/lang/reflect/UndeclaredThrowableException.html">UndeclaredThrowableException.
With a bit more effort we could add the list of exceptions to
to be worth that effort.
throws Exception
clause in each method. Thisis because if a
Proxy
handler throws a checkedexception that is not declared in the
throws
clause, the exception will be wrapped in anhref="http://download.java.net/jdk6/doc/api/java/lang/reflect/UndeclaredThrowableException.html">UndeclaredThrowableException.
With a bit more effort we could add the list of exceptions to
XMethod
and generate it here, but it doesn't seemto be worth that effort.
This method requires a couple of other methods to generate the
cryptic codes that are used for method signatures in the class
file format.
cryptic codes that are used for method signatures in the class
file format.
private String methodDescriptor(XMethod method) {
StringBuilder sb = new StringBuilder("(");
for (Class<?> param : method.getParameterTypes())
sb.append(classCode(param));
sb.append(")").append(classCode(method.getReturnType()));
return sb.toString();
}
private String classCode(Class<?> c) {
if (c == void.class)
return "V";
Class<?> arrayClass = Array.newInstance(c, 0).getClass();
return arrayClass.getName().substring(1).replace('.', '/');
}
The
extravagant way to get the right href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#14152">type
code for every Java type, primitive or otherwise. Due
presumably to a historical oversight, the JVM's internal codes
leaked out through href="http://download.java.net/jdk6/doc/api/java/lang/Class.html#getName()">Class.getName() for array classes.
This is usually a pain, but here it proves useful!
Array.newInstance
business is a simple ifextravagant way to get the right href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#14152">type
code for every Java type, primitive or otherwise. Due
presumably to a historical oversight, the JVM's internal codes
leaked out through href="http://download.java.net/jdk6/doc/api/java/lang/Class.html#getName()">Class.getName() for array classes.
This is usually a pain, but here it proves useful!
Finally, we need to manage the constant pool. There are href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#20080">11
different types of constant and the obvious approach would be
to define an interface or abstract class
with 11 subclasses. However, it turns out that nearly all these
types of constant are only needed when generating code. Since
we're just generating an interface, we only need two of the types,
for href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#7963">strings
and href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#1221">class
names. So I've used a Lisp-like approach where each constant
is represented by a List. The first element of the List is the
constant tag, and the rest of the List is the data specific to
that tag. A switch statement does the right thing for each tag.
(This is href="http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html">not
very Objectly Correct. Sue me.)
different types of constant and the obvious approach would be
to define an interface or abstract class
Constant
with 11 subclasses. However, it turns out that nearly all these
types of constant are only needed when generating code. Since
we're just generating an interface, we only need two of the types,
for href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#7963">strings
and href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#1221">class
names. So I've used a Lisp-like approach where each constant
is represented by a List. The first element of the List is the
constant tag, and the rest of the List is the data specific to
that tag. A switch statement does the right thing for each tag.
(This is href="http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html">not
very Objectly Correct. Sue me.)
The constants are stored in a Map so that if the same constant
is needed more than once we can reuse it. The use of List for
each constant gives us the right behaviour for lookups. A href="http://download.java.net/jdk6/doc/api/java/util/LinkedHashMap.html">LinkedHashMap
ensures that the constants come out in the right order when we
iterate through the Map to write the constant pool.
is needed more than once we can reuse it. The use of List for
each constant gives us the right behaviour for lookups. A href="http://download.java.net/jdk6/doc/api/java/util/LinkedHashMap.html">LinkedHashMap
ensures that the constants come out in the right order when we
iterate through the Map to write the constant pool.
private int stringConstant(String s) {
return constant(CONSTANT_Utf8, s);
}
private int classConstant(String s) {
int classNameIndex = stringConstant(s.replace('.', '/'));
return constant(CONSTANT_Class, classNameIndex);
}
private int constant(Object... data) {
List<?> dataList = Arrays.asList(data);
if (poolMap.containsKey(dataList))
return poolMap.get(dataList);
poolMap.put(dataList, poolIndex);
return poolIndex++;
}
private void writeConstantPool(DataOutputStream dout) throws IOException {
dout.writeShort(poolIndex);
int i = 1;
for (List<?> data : poolMap.keySet()) {
assert(poolMap.get(data).equals(i++));
int tag = (Integer) data.get(0);
dout.writeByte(tag); // u1 tag
switch (tag) {
case CONSTANT_Utf8:
dout.writeUTF((String) data.get(1));
break; // u2 length + u1 bytes[length]
case CONSTANT_Class:
dout.writeShort((Integer) data.get(1));
break; // u2 name_index
default:
throw new AssertionError();
}
}
}
private final Map<List<?>, Integer> poolMap =
new LinkedHashMap<List<?>, Integer>();
private int poolIndex = 1;
Loading the generated interface
We now have a byte array containing a class file. How do we go
from that to an actual href="http://download.java.net/jdk6/doc/api/java/lang/Class.html">
This problem is the same whether the byte array came from the
code above, or was generated by invoking the compiler, or was
created by Apache BCEL.
from that to an actual href="http://download.java.net/jdk6/doc/api/java/lang/Class.html">
java.lang.Class
?This problem is the same whether the byte array came from the
code above, or was generated by invoking the compiler, or was
created by Apache BCEL.
We're going to need a href="http://download.java.net/jdk6/doc/api/java/lang/ClassLoader.html">ClassLoader.
The job of a ClassLoader is to take a class name (such as
corresponding
the request to another ClassLoader or by constructing the Class
itself using thehref="http://download.java.net/jdk6/doc/api/java/lang/ClassLoader.html#defineClass(java.lang.String,%20byte[],%20int,%20int)">
method.
The job of a ClassLoader is to take a class name (such as
com.example.Cache$WrapperMBean
) and produce thecorresponding
Class
. It can do this by forwardingthe request to another ClassLoader or by constructing the Class
itself using thehref="http://download.java.net/jdk6/doc/api/java/lang/ClassLoader.html#defineClass(java.lang.String,%20byte[],%20int,%20int)">
defineClass
method.
The simplest way to manage this is to write the byte array to a
file with the appropriate name, say
use a href="http://download.java.net/jdk6/doc/api/java/net/URLClassLoader.html">
that can find the file. Something like this:
file with the appropriate name, say
/tmp/code/com/example/Cache$WrapperMBean.class
, anduse a href="http://download.java.net/jdk6/doc/api/java/net/URLClassLoader.html">
URLCLassLoader
that can find the file. Something like this:
OutputStream out =
new FileOutputStream("/tmp/code/com/example/Cache$WrapperMBean");
out.write(bytes);
out.close();
URL codeDir = new File("/tmp/code").toURI().toURL();
ClassLoader loader = new URLClassLoader(new URL[] {codeDir});
Class<?> c =
Class.forName("com.example.Cache$WrapperMBean", false, loader);
But in fact it is not much harder to create your own
ClassLoader that directly fabricates and loads the byte code
without requiring any files:
ClassLoader that directly fabricates and loads the byte code
without requiring any files:
public class InterfaceClassLoader extends ClassLoader {
public InterfaceClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> findOrBuildInterface(String name, XMethod[] methods) {
Class<?> c;
c = findLoadedClass(name);
if (c != null)
return c;
byte[] classBytes = InterfaceBuilder.buildInterface(name, methods);
return defineClass(name, classBytes, 0, classBytes.length);
}
}
MBeans from annotations
We now have nearly everything we need in order to be able to
build MBeans from
that given a class like this...
build MBeans from
@Managed
annotations. A reminderthat given a class like this...
public class Cache {
@Managed</b>
public int getSize() {
return size;
}
@Managedb>
public void setSize(int size) {
this.size = size;
}
@Managedb>
public int getUsed() {
return used;
}
...
}
...we want to build a Standard MBean interface like this...
public interface Cache$WrapperMBean {
public int getSize();
public void setSize(int size);
public int getUsed();
}
...and implement that interface to make a Standard MBean that
we can register in the MBean Server. Rather than generating a
class
conventions, we're going to create a java.lang.reflect.Proxy.
We can't make the class name of the proxy be
exists for when the class name and the interface name don't
match.
we can register in the MBean Server. Rather than generating a
class
Cache$Wrapper
that implementsCache$WrapperMBean
, following the Standard MBeanconventions, we're going to create a java.lang.reflect.Proxy.
We can't make the class name of the proxy be
Cache$Wrapper
, but the class href="http://download.java.net/jdk6/doc/api/javax/management/StandardMBean.html">javax.management.StandardMBeanexists for when the class name and the interface name don't
match.
The class
in this way. The usage is similar to this:
MBeanBuilder
can be used to make MBeansin this way. The usage is similar to this:
ClassLoader thisLoader = this.getClass().getClassLoader();
MBeanBuilder builder = new MBeanBuilder(thisLoader);
...
Object cacheMBean = builder.buildMBeanb>(cache);
ObjectName cacheMBeanName = ...;
mbeanServer.registerMBean(cacheMBean, cacheMBeanName);
For best results, when a class constructs an
MBeanBuilder
it should supply its ownClassLoader
as argument, as shown here.
Here's what
MBeanBuilder
looks like:public class MBeanBuilder {
private final InterfaceClassLoader loader;
public MBeanBuilder(ClassLoader parentLoader) {
loader = new InterfaceClassLoader(parentLoader);
}
public StandardMBean buildMBean(Object x) {
Class<?> c = x.getClass();
Class<?> mbeanInterface = makeInterface(c);
InvocationHandler handler = new MBeanInvocationHandler(x);
return makeStandardMBean(mbeanInterface, handler);
}
private static <T> StandardMBean makeStandardMBean(Class<T> intf,
InvocationHandler handler) {
Object proxy =
Proxy.newProxyInstance(intf.getClassLoader(),
new Class<?>[] {intf},
handler);
T impl = intf.cast(proxy);
try {
return new StandardMBean(impl, intf);
} catch (NotCompliantMBeanException e) {
throw new IllegalArgumentException(e);
}
}
private Class makeInterface(Class implClass) {
String interfaceName = implClass.getName() + "$WrapperMBean";
try {
return Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
// OK, we'll build it
}
Set<XMethod> methodSet = new LinkedHashSet<XMethod>();
for (Method m : implClass.getMethods()) {
if (m.isAnnotationPresent(Managed.class))
methodSet.add(new XMethod(m));
}
if (methodSet.isEmpty()) {
throw new IllegalArgumentException("Class has no @Managed methods: "
+ implClass);
}
XMethod[] methods = methodSet.toArray(new XMethod[0]);
return loader.findOrBuildInterface(interfaceName, methods);
}
}
The first time it sees a given class, such as
will reuse the previously-generated interface.
com.example.Cache
, it will generate an interface,com.example.Cache$WrapperMBean
. Every other time, itwill reuse the previously-generated interface.
The
@Managed
annotation is defined like this: href="http://download.java.net/jdk6/doc/api/java/lang/annotation/Documented.html">@Documented href="http://download.java.net/jdk6/doc/api/java/lang/annotation/Retention.html">@Retention(RetentionPolicy.RUNTIME) href="http://download.java.net/jdk6/doc/api/java/lang/annotation/Target.html">@Target(ElementType.METHOD)
public @interface Managed {
}
In this version, we don't allow
fields. That's certainly possible, but would complicate the code
considerably.
@Managed
onfields. That's certainly possible, but would complicate the code
considerably.
The
Proxy looks like this:
MBeanInvocationHandler
used in the constructedProxy looks like this:
public class MBeanInvocationHandler implements InvocationHandler {
public MBeanInvocationHandler(Object wrapped) {
this.wrapped = wrapped;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Class<?> wrappedClass = wrapped.getClass();
Method methodInWrapped =
wrappedClass.getMethod(method.getName(), method.getParameterTypes());
try {
return methodInWrapped.invoke(wrapped, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
private final Object wrapped;
}
MXBean mappings
Another possible application of dynamic interface generation
appears in Java SE 6 with the addition of href="http://java.sun.com/developer/technicalArticles/J2SE/mxbeans/">MXBeans
to the platform. MXBeans map arbitrary Java types to a fixed
set of types, the so-called Open Types defined in href="http://download.java.net/jdk6/doc/api/javax/management/openmbean/package-summary.html">javax.management.openmbean.
But the MXBean framework operates on MXBean interfaces,
not on individual types. It sometimes happens that you need to
find out the mapping for a certain type.
appears in Java SE 6 with the addition of href="http://java.sun.com/developer/technicalArticles/J2SE/mxbeans/">MXBeans
to the platform. MXBeans map arbitrary Java types to a fixed
set of types, the so-called Open Types defined in href="http://download.java.net/jdk6/doc/api/javax/management/openmbean/package-summary.html">javax.management.openmbean.
But the MXBean framework operates on MXBean interfaces,
not on individual types. It sometimes happens that you need to
find out the mapping for a certain type.
If you know the type at compile time, you can just put it in an
interface, create an MXBean that implements that interface.
Suppose you want to know the mapping forhref="http://download.java.net/jdk6/doc/api/java/lang/management/MemoryUsage.html">java.lang.management.MemoryUsage,
for example. Then you can create an interface like this...
interface, create an MXBean that implements that interface.
Suppose you want to know the mapping forhref="http://download.java.net/jdk6/doc/api/java/lang/management/MemoryUsage.html">java.lang.management.MemoryUsage,
for example. Then you can create an interface like this...
public interface MemoryUsageTestMXBean {
public MemoryUsage getMemoryUsage();
public void setMemoryUsage(MemoryUsage x);
}
...and implement it like this...
public class MemoryUsageTest implements MemoryUsageTestMXBean {
volatile MemoryUsage memoryUsage;
public MemoryUsage getMemoryUsage() {
return memoryUsage;
}
public void setMemoryUsage(MemoryUsage x) {
memoryUsage = x;
}
}
Then you can make an MXBean like this...
MemoryUsageTest memoryUsageTest = new MemoryUsageTest();
StandardMBean mxbean =
new StandardMBean(memoryUsageTest, MemoryUsageTestMXBean.class, true);
...and use it like this...
// Discover the OpenType that MemoryUsage is mapped to
MBeanAttributeInfo ai = mxbean.getMBeanInfo().getAttributes()[0];
assert(ai.getName().equals("MemoryUsage");
OpenType<?> memoryUsageOpenType = (OpenType<?>)
ai.getDescriptor().getFieldValue("openType");
// Convert a MemoryUsage into a value of this Open Type
MemoryUsage mu = ...something...;
memoryUsageTest.memoryUsage = mu;
Object openValue = mxbean.getAttribute("MemoryUsage");
// Convert a value of the Open Type back into a MemoryUsage
mxbean.setAttribute(new Attribute("MemoryUsage", openValue));
mu = memoryUsageTest.memoryUsage;
We can make this work for an arbitrary type discovered at run
time, by generating the equivalent of the
code below shows this.
time, by generating the equivalent of the
MemoryUsageTestMXBean
for the type in question. Thecode below shows this.
public class MXBeanMapper {
private final StandardMBean mxbean;
private final MXBeanInvocationHandler handler;
public MXBeanMapper</b>(Class<?> originalType) {
InterfaceClassLoader loader =
new InterfaceClassLoader(originalType.getClassLoader());
XMethod getter = new XMethod("getX", new Class<?>[0], originalType);
XMethod setter = new XMethod("setX", new Class<?>[] {originalType},
void.class);
Class<?> mxbeanInterface =
loader.findOrBuildInterface("X", new XMethod[] {getter, setter});
handler = new MXBeanInvocationHandler();
mxbean = makeMXBean(mxbeanInterface, handler);
}
private static <T> StandardMBean makeMXBean(Class<T> intf,
InvocationHandler handler) {
Object proxy =
Proxy.newProxyInstance(intf.getClassLoader(),
new Class<?>[] {intf},
handler);
T impl = intf.cast(proxy);
return new StandardMBean(impl, intf, true);
}
public OpenType<?> getOpenTypeb>() {
MBeanAttributeInfo ai = mxbean.getMBeanInfo().getAttributes()[0];
assert(ai.getName().equals("X"));
return (OpenType<?>) ai.getDescriptor().getFieldValue("openType");
}
public synchronized Object toOpenValue</b>(Object javaValue) {
handler.javaValue = javaValue;
try {
return mxbean.getAttribute("X");
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
public synchronized Object fromOpenValueb>(Object openValue) {
try {
mxbean.setAttribute(new Attribute("X", openValue));
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
return handler.javaValue;
}
private static class MXBeanInvocationHandler implements InvocationHandler {
volatile Object javaValue;
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getName().equals("getX"))
return javaValue;
else if (method.getName().equals("setX")) {
javaValue = args[0];
return null;
} else
throw new AssertionError("Bad method name " + method.getName());
}
}
}
Conclusions
Generating interfaces is possible and allows us to solve some
interesting problems. This is especially true for APIs like the
JMX API that use reflection heavily.
interesting problems. This is especially true for APIs like the
JMX API that use reflection heavily.
The source code for the classes above is in this zip file, with the exception of
MXBeanMapper.java, which I separated because it
requires the Java SE 6 platform.
MXBeanMapper.java, which I separated because it
requires the Java SE 6 platform.
No comments:
Post a Comment