Title: Dynamic Java
1Dynamic Java
Classes Without Code
- Aaron Mulder
- Chief Technical Officer
- Chariot Solutions
2Learning Objectives
- In this presentation, we'll discuss
- Dynamic Proxies
- Dynamic Classes, or generating Java classes
without source code - Where these techniques can be useful
3About Aaron Mulder
- Chief Technical Officer at Chariot Solutions
- Co-author of Professional EJB (Wrox Press, 2001)
- Presented at JavaOne 2001 JavaOne 2002
- A member of the JSR-88 Expert Group (J2EE
Deployment API) - Contributed to the JBoss and OpenEJB projects,
including an implementation of Dynamic Proxies
for JDK 1.2
4About Chariot Solutions
- Chariot Solutions is an IT service provider,
specializing in J2EE - Chariot project teams include experienced project
managers, business analysts, technical
architects, and developers - Chariot's extensive Java tools and application
framework can give any Java project a head start - Flyers around the room have more information
5Today's Problem
6Presentation Agenda
- Reflection Review
- Dynamic Proxies
- Project 1 Tracing JDBC Driver
- Project 2 EJB 1.1 Client Stubs
- Dynamic Classes
- Project 3 EJB 2.0 CMP
- Wrapup/QA
7Reflection Review
8Reflection Review Class
- java.lang.Class provides information on the
constructors, methods, fields, interfaces
implemented, superclass, package, etc. for a class
public class Class Field getFields()
Field getField(String name) Method
getMethods() Method getMethod(String name,
Class params) Constructor
getConstructors() ... getDeclaredXXX(...)
...
9Reflection Review Method Constructor
- java.lang.reflect.Method provides information on
the parameters, return type, access modifiers,
exceptions, etc. for a method - java.lang.reflect.Constructor is very similar
public class Method String getName()
Class getReturnType() Class
getParameterTypes() Class
getExceptionTypes() int getModifiers()
...
10Reflection Review Field
- java.lang.reflect.Field provides information on
the name, type, access modifiers, etc. for a field
public class Field String getName()
Class getType() int getModifiers() ...
11Reflection Review Modifier
- java.lang.reflect.Modifier decodes the Modifiers
property (an int) on methods, fields, etc.
public class Modifier boolean isPublic(int
modifiers) boolean isPrivate(int
modifiers) boolean isAbstract(int
modifiers) boolean isStatic(int modifiers)
boolean isFinal(int modifiers) ...
12Dynamic Proxies
13Dynamic Proxies
- Introduced in JDK 1.3
- java.lang.reflect.Proxy
- java.lang.reflect.InvocationHandler
- Allow you to implement arbitrary interfaces at
runtime - All calls to Proxy methods are dispatched to an
InvocationHandler for processing - Proxies rely heavily on Reflection
14Dynamic Proxy Diagram
Client
Proxy (impl. interfaces)
Invocation Handler
15InvocationHandler
- java.lang.reflect.InvocationHandler has one
method, which takes as arguments - The proxy that was called
- The Method object representing the method which
was called - The objects which were passed as parameters to
the method - It returns an Object the value to return from
the method call, or null for void methods - It throws Throwable
16InvocationHandler API
public interface InvocationHandler public
Object invoke(Object proxy,
Method method,
Object args) throws Throwable
17Proxy
- java.lang.reflect.Proxy has methods which
- Create a new proxy class (based on the
combination of interfaces) - Create a new proxy instance (uses a proxy class
created above, for a specific InvocationHandler) - Get the InvocationHandler for a proxy
- Check whether an arbitrary object is a proxy
18Proxy API
public class Proxy public Class
getProxyClass(ClassLoader loader,
Class interfaces) public
Object newProxyInstance(
ClassLoader loader, Class
interfaces,
InvocationHandler handler) public
InvocationHandler getInvocationHandler(
Object proxy) public boolean
isProxyClass(Class class)
19Project 1 Tracing JDBC Driver
- What if you want a log of all SQL statements
executed? - But you don't want to add code everywhere you
issue a JDBC command (or the app server is doing
all the JDBC for you) - And you want to be able to turn it on or off via
config files - And you want to see all the values in place in
every PreparedStatement
20Why Dynamic Proxies Fit
- The JDBC API is made up entirely of interfaces
- Which driver is used is (typically) controlled by
config files - One driver can wrap another
- Implementing all the interfaces is a ton of code
(100s of methods), when we only want to act on a
couple of them (executeQuery), and pass the rest
directly to the wrapped instance
21JDBC Driver Class
public boolean acceptsURL(String url)
return url.startsWith("jdbctrace") public
Connection connect(String url,
Properties info) String driver
info.getProperty("driver") if(driver ! null
!loaded.contains(driver)) // Load the
driver String realUrl "jdbc"url.substring(
11) Connection con DriverManager.getConnect
ion( realUrl,
info) return (Connection)Proxy.newProxyInstan
ce( getClass().getClassLoader(),
new Classjava.sql.Connection.class,
new ConnectionHandler(con))
22Connection Proxy Diagram
Client
Proxy (impl. Connection)
Invocation Handler
Real Connection
23ConnectionHandler Class
private Connection con ... public Object
invoke(Object proxy, Method method,
Object args) throws Throwable
if(method.getName().equals("isClosed"))
return con null if(con null)
throw new SQLException("Con. closed!")
Method conMeth con.getClass().getMethod(
method.getName(),
method.getParameterTypes())
Object result conMeth.invoke(con, args)
if(method.getName().equals("close"))
con null ... // To Be Continued!
24ConnectionHandler, continued
if(method.getName().equals("createStatement"))
return (Statement)Proxy.newProxyInstance
( getClass().getClassLoader(),
new Classjava.sql.Statement.class,
new StatementHandler((Statement)result))
if(method.getName().equals("prepareStatement
")) return (PreparedStatement)
Proxy.newProxyInstance(
getClass().getClassLoader(), new
Classjava.sql.PreparedStatement.class,
new PreparedStatementHandler(
(PreparedStatement)result,
(String)args0) // this is the SQL
) return result
25StatementHandler
private Statement st ... public Object
invoke(Object proxy, Method method,
Object args) throws Throwable
if(method.getName().equals("executeQuery")
method.getName().equals("executeUpdate"))
log(args0) // args0 is the SQL!
Method meth st.getClass().getMethod(
method.getName(),
method.getParameterTypes()) Object
result meth.invoke(st, args)
if(method.getName().equals("close")) st
null return result
26PreparedStatementHandler
private PreparedStatement st private String
sql private Map values new HashMap() ... publi
c Object invoke(Object proxy, Method method,
Object args) throws Throwable
if(method.getName().startsWith("set"))
values.put(args0, args1) // args0
index, args1 value
if(method.getName().startsWith("execute"))
log(writeValues(sql, values))
values.clear() // execute the method
on the underlying PS // return the
result private static String writeValues(String,
Map)...
27Sample JDBC Driver Output
- JDBC URL jdbcdriver...
- no output
- JDBC URL jdbctracedriver...
SELECT WL0.check_date, WL0.company_no,
WL0.create_datetime, WL0.create_username,
WL0.cutoff_date, WL0.due_date_flag,
WL0.payables_selected, WL0.update_datetime,
WL0.update_username FROM dbo.ap_selection WL0
WHERE (WL0.company_no 6)
28Project 2 EJB Client Stubs
- EJB Architecture
- Client deals with remote interface
- Container must implement remote interface, handle
RMI and translate calls to run against a bean
instance - Bean instance isn't remote and doesn't implement
remote interface, so container must create some
"glue" code
Bean Instance
Client
Remote Interface
Container
What implements this?
29Solution 1 "That Other App Server"
- At deployment time, container generates a class
which is remote, implements the remote interface,
and dispatches calls back to the container - Java source code is written to disk
- Java compiler is run (in another process)
- Source code is deleted
- Class files are loaded from disk
- Deploying lots of beans takes... a long time.
30Generated Code Diagram
Client
Generated Remote Bean Stub
Remote Bean Impl
Container
31Solution 2 JBoss Dynamic Proxies
- A Proxy is generated for each bean to implement
the EJB's Remote Interface - One prewritten InvocationHandler class is used
for all beans of a given type (Entity, Stateless
Session, etc.) - The InvocationHandler has a reference to the
Container (as a remote object) - The InvocationHandler parameterizes the Method
and sends it with the arguments to the Container
for processing
32JBoss Proxy Diagram
Client
Proxy (impl EJB Remote Interface)
Invocation Handler
Container Remote Stub
Container Impl
33Sample InvocationHandler Code
public Object invoke(Object proxy, Method method,
Object args) throws
Throwable if(proxy instanceof EJBHome)
return Container.executeHome(proxy,
encode(method), args)
else return Container.executeRemote(prox
y, encode(method),
args)
- Real code lives in org.jboss.ejb.plugin.jrmp for
JBoss 2.x (but the classes named "Proxy" are
really the InvocationHandlers) - Method objects regrettably aren't Serializable
34Dynamic Proxy Review
- Dynamic Proxies can be used to implement
arbitrary interfaces at runtime - The client casts the Proxy to an instance of one
of the implemented interfaces - An InvocationHandler handles all calls to the
Proxy - Dynamic Proxies can't be used to extend existing
classes
35Dynamic Classes
36Dynamic Classes
- The hardcore solution
- Involves assembling bytecode into classes
- Requires an understanding of bytecode
instructions (essentially, assembly language for
the Java Virtual Machine) - However, existing bytecode libraries can help
manage the worst parts of it
37Typical Bytecode
public static final int fac(int n) return
(n 0)? 1 n fac(n - 1) 0 iload_0 1
ifne 8 4 iconst_1 5 goto
16 8 iload_0 9 iload_0 10 iconst_1 11
isub 12 invokestatic SomeClass.fac (I)I
(12) 15 imul 16 ireturn
38Alternatives to Dynamic Classes
- Write Java code, run a compiler, delete the code,
load the class, blah, blah, blah... - Interpret some other minimal "language" regular
expressions, for example - The alternatives are usually somewhat clearer
(i.e. not bytecode), but bigger and slower - If you do proceed... use a library
- BCEL _at_ http//jakarta.apache.org/bcel/
- gnu.bytecode _at_ http//sources.redhat.com/kawa/api/
gnu/bytecode/
39BCEL (Byte Code Engineering Library)
- Includes numerous helper classes
- ClassGen, for creating a class
- MethodGen, for creating a method
- FieldGen, for creating a field
- ConstantPoolGen, for managing the Constant Pool
(a necessary feature of Java classes) - InstructionList, for assembling instructions
- Constant, a list of helpful constants
40Project 3 EJB 2.0 CMP
- EJB 2.0 CMP entity bean instances are abstract
classes
public abstract class UserBean implements
EntityBean public abstract int
getUserID() public abstract void
setUserID(int userID) public abstract
String getUsername() public abstract void
setUsername(String name) public abstract
String getPassword() public abstract void
setPassword(String pw) ...
- Container must generate a subclass in order to
instantiate bean instances
41Solution 1 Write Code, Run Compiler, Load
Class...
- This should be familiar by now
42Solution 2 Extend Dynamic Proxies
- JBoss 3.x uses this approach
- Must re-implement Dynamic Proxies in order to
extend their capabilities - Add the ability to extend an abstract class in
addition to implementing interfaces - Abstract method calls are passed to the
InvocationHandler just like calls to interface
methods - JBoss uses BCEL to do this, but it's way too
complicated to go into here
43Solution 3 Generate a Concrete Subclass
- Generate a dynamic class which extends the
abstract bean instance class - Lets you hardcode more container-specific
behavior as compared to extending Dynamic Proxies
(i.e. a modified flag) - The subclass can look pretty much like an EJB 1.x
bean (with CMR and other new features, of course)
44Dynamic Class Procedure
- Figure out what the generated class should look
like - Write Java code for an example desired class (in
this case, create the abstract superclass too) - Write a test class to test all the features of
the output class, using Reflection - Run "javap -c" on the desired class to see its
bytecode - Write the BCEL code to produce the output class
45Desired Java Code
public class UserBeanTemplate extends UserBean
// Custom instance variables private
boolean modified false // Instance
variables for CMP fields public int userID
public String username public String
password public String fullName public
String email public Timestamp createDate
// Methods for CMP fields public int
getUserID() return userID public void
setUserID(int userID) this.userID
userID modified true public
String getUsername() return username
public void setUsername(String username) ...
46Test Class
private Class outputCls ...
testConstructor() testCMPFields()
testCMPMethods() ... private void
testConstructor() throws BadClassEx try
Constructor con outputCls.getConstructor(
new Class...)
if(!Modifier.isPublic(con.getModifiers()))
throw new BadClassEx("...")
catch (NoSuchMethodException e) throw
new BadClassEx("...") catch
(SecurityException e) throw new
BadClassEx("...")
47BCEL Preparing To Create A Class
- In order to generate a class we need
- The name of the new class
- The name of the superclass
- The name of the source file it came from (we'll
make something up) - The modifiers for the class (public, etc.)
- The names of any interfaces the class implements
48BCEL Initializing a class
private Class source private ClassGen
clsGen private ConstantPoolGen pool private
InstructionList il private Map fields private
String subclassName ... subclassName
source.getName()"Impl" clsGen new
ClassGen(subclassName,
source.getName(),
"ltgeneratedgt", Constants.ACC_PUBLIC
Constants.ACC_FINAL,
new String0) pool clsGen.getConstantPool(
) il new InstructionList() fields
new HashMap()
49BCEL Adding a Field
- In order to add a field, we need the field
modifiers (public, etc.), field type, field name,
and a reference to the Constant Pool
ClassGen clsGen ... FieldGen fg for(int i0
iltproperties.length i) fg new
FieldGen(Constants.ACC_PUBLIC,
BCELUtilities.getType(propertiesi.type),
propertiesi.name,
pool) Field f fg.getField()
fields.put(propertiesi.name, f)
clsGen.addField(f)
50BCEL Preparing to Add a Method
- In order to add a method, we need
- The method modifiers (public, etc.)
- The return type
- The parameter types
- The parameter names
- The method name
- The owning class name
- The code for the method (including Exception
handling) - A reference to the Constant Pool
51BCEL Adding a Method
ClassGen clsGen ... InstructionList il
... private void createConstructor()
MethodGen mg new MethodGen(
Constants.ACC_PUBLIC,
Type.VOID, new
Type..., new
String...,
"ltinitgt",
subclassName, il,
pool) il.append(new
ALOAD(0)) il.append(new PUTFIELD(...))
clsGen.addMethod(mg.getMethod())
il.dispose() // InstructionList is reusable
52BCEL Generating the Bytecode
- Once you've added the fields and methods, it's
easy to get the resulting bytecode
ClassGen clsGen ... byte code
clsGen.getJavaClass().getBytes()
- However, loading the class is an adventure too
- Default ClassLoaders aren't prepared to load a
class from a byte array in memory
53Loading A Dynamic Class
public class DynLdr extends ClassLoader
private Class dynClass private String
clsName public ProxyLoader(ClassLoader
parent, String clsName,
byte code) super(parent)
this.clsName clsName dynClass
defineClass(className, code,
0, code.length) protected
synchronized Class loadClass(
String name, boolean resolve)
throws ClassNotFoundException
if(name.equals(clsName)) return dynClass
return getParent().loadClass(name)
54Dynamic Class Review
- It's painful to write in bytecode
- But it's much faster to generate bytecode
directly at runtime, compared to generating
source code and then running the compiler - Dynamic classes should only be used when the
performance advantage is significant - Other potential uses include Serialization,
Regular Expressions, extensions to the Java
language (Aspects, Generics, etc.), and more
55Summary
- Dynamic Java is an excellent tool for
- avoiding generating compiling Java code
- avoiding interpreting complex languages
- avoiding ongoing Reflection at runtime
- Dynamic Proxies can easily implement interfaces
at runtime - Dynamic Classes are more challenging, but can
solve more problems as well - Use these tools wisely make sure there's a
substantive advantage
56One For The Road
Would it make sense to implement a JSP container
using Dynamic Classes?
57(No Transcript)
58Slides
- Slides from this presentation and the complete
code for all snippets will be available soon at - http//www.chariotsolutions.com/phillyjug/