Upload
clement-blair
View
219
Download
0
Embed Size (px)
Citation preview
Philly JUG: May 21, 2002
Dynamic Java
Aaron MulderChief Technical OfficerChariot Solutions
Classes Without Code
Philly JUG: May 21, 20021
Learning Objectives
● In this presentation, we'll discuss
➔ Dynamic Proxies
➔ Dynamic Classes, or generating Java classes
without source code
➔ Where these techniques can be useful
Beginning
Philly JUG: May 21, 20021
About 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
Beginning
Philly JUG: May 21, 20021
About 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
Beginning
Philly JUG: May 21, 20021
Today's Problem
Beginning
Java code generation, compilation, and loading is slow and resource-intensive. However attempting to avoid it can be even worse!
Philly JUG: May 21, 20021
Presentation 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/Q&A
Beginning
Philly JUG: May 21, 2002
Reflection Review
Philly JUG: May 21, 20021Middle
● java.lang.Class provides information on the constructors, methods, fields, interfaces implemented, superclass, package, etc. for a class
Reflection Review: Class
public class Class { Field[] getFields(); Field getField(String name); Method[] getMethods(); Method getMethod(String name, Class[] params); Constructor[] getConstructors(); ... getDeclaredXXX(...); ...}
Philly JUG: May 21, 20021Middle
● 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
Reflection Review: Method & Constructor
public class Method { String getName(); Class getReturnType(); Class[] getParameterTypes(); Class[] getExceptionTypes(); int getModifiers(); ...}
Philly JUG: May 21, 20021Middle
● java.lang.reflect.Field provides information on the name, type, access modifiers, etc. for a field
Reflection Review: Field
public class Field { String getName(); Class getType(); int getModifiers(); ...}
Philly JUG: May 21, 20021Middle
● java.lang.reflect.Modifier decodes the Modifiers property (an int) on methods, fields, etc.
Reflection Review: Modifier
public class Modifier { boolean isPublic(int modifiers); boolean isPrivate(int modifiers); boolean isAbstract(int modifiers); boolean isStatic(int modifiers); boolean isFinal(int modifiers); ...}
Philly JUG: May 21, 2002
Dynamic Proxies
Philly JUG: May 21, 20021Middle
● 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
Dynamic Proxies
Philly JUG: May 21, 20021Middle
Dynamic Proxy Diagram
ClientProxy (impl.interfaces)
InvocationHandler
Philly JUG: May 21, 20021Middle
● 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
InvocationHandler
Philly JUG: May 21, 20021Middle
InvocationHandler API
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}
Philly JUG: May 21, 20021Middle
● 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
Proxy
Philly JUG: May 21, 20021Middle
Proxy 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);}
Philly JUG: May 21, 20021Middle
● 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
Project 1: Tracing JDBC Driver
Philly JUG: May 21, 20021Middle
Why 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
Philly JUG: May 21, 20021Middle
JDBC Driver Class
public boolean acceptsURL(String url) { return url.startsWith("jdbc:trace:");}
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.getConnection( realUrl, info); return (Connection)Proxy.newProxyInstance( getClass().getClassLoader(), new Class[]{java.sql.Connection.class}, new ConnectionHandler(con));}
Philly JUG: May 21, 20021Middle
Connection Proxy Diagram
ClientProxy (impl.Connection)
InvocationHandler
RealConnection
Philly JUG: May 21, 20021Middle
ConnectionHandler 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!
Philly JUG: May 21, 20021Middle
ConnectionHandler, continued
if(method.getName().equals("createStatement")){ return (Statement)Proxy.newProxyInstance( getClass().getClassLoader(), new Class[]{java.sql.Statement.class}, new StatementHandler((Statement)result)); } if(method.getName().equals("prepareStatement")){ return (PreparedStatement) Proxy.newProxyInstance( getClass().getClassLoader(), new Class[]{java.sql.PreparedStatement.class}, new PreparedStatementHandler( (PreparedStatement)result, (String)args[0]) // this is the SQL ); } return result;}
Philly JUG: May 21, 20021Middle
StatementHandler
private Statement st;...public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("executeQuery") || method.getName().equals("executeUpdate")) { log(args[0]); // args[0] 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;}
Philly JUG: May 21, 20021Middle
PreparedStatementHandler
private PreparedStatement st;private String sql;private Map values = new HashMap();...public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().startsWith("set")) { values.put(args[0], args[1]); // args[0] = index, args[1] = 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)...
Philly JUG: May 21, 20021Middle
Sample JDBC Driver Output
● JDBC URL = jdbc:driver:...➔ no output
● JDBC URL = jdbc:trace:driver:...
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])
Philly JUG: May 21, 20021Middle
Project 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
ClientRemoteInterface
ContainerBeanInstance
What implements this?
Philly JUG: May 21, 20021Middle
Solution 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.
Philly JUG: May 21, 20021Middle
Generated Code Diagram
ClientGeneratedRemote BeanStub
RemoteBean Impl
Container
Philly JUG: May 21, 20021Middle
Solution 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
Philly JUG: May 21, 20021Middle
JBoss Proxy Diagram
Client Proxy (implEJB RemoteInterface)
InvocationHandler
ContainerRemote Stub
ContainerImpl
Philly JUG: May 21, 20021Middle
Sample 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(proxy, 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
Philly JUG: May 21, 20021Middle
Dynamic 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
Philly JUG: May 21, 2002
Dynamic Classes
Philly JUG: May 21, 20021Middle
Dynamic 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
Philly JUG: May 21, 20021Middle
Typical Bytecode
public static final int fac(int n) { return (n == 0)? 1 : n * fac(n - 1);}
0: iload_01: ifne #84: iconst_15: goto #168: iload_09: iload_010: iconst_111: isub12: invokestatic SomeClass.fac (I)I (12)15: imul16: ireturn
Philly JUG: May 21, 20021Middle
Alternatives 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 @ http://jakarta.apache.org/bcel/➔ gnu.bytecode @
http://sources.redhat.com/kawa/api/gnu/bytecode/
Philly JUG: May 21, 20021Middle
BCEL (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
Philly JUG: May 21, 20021Middle
Project 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
Philly JUG: May 21, 20021Middle
Solution 1: Write Code, Run Compiler, Load Class...
● This should be familiar by now
Philly JUG: May 21, 20021Middle
Solution 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
Philly JUG: May 21, 20021Middle
Solution 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)
Philly JUG: May 21, 20021Middle
Dynamic 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
Philly JUG: May 21, 20021Middle
Desired 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) { ...
Philly JUG: May 21, 20021Middle
Test 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("..."); }}
Philly JUG: May 21, 20021Middle
BCEL: 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
Philly JUG: May 21, 20021Middle
BCEL: 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(), "<generated>", Constants.ACC_PUBLIC | Constants.ACC_FINAL, new String[0]); pool = clsGen.getConstantPool(); il = new InstructionList(); fields = new HashMap();
Philly JUG: May 21, 20021Middle
BCEL: 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 i=0; i<properties.length; i++) { fg = new FieldGen(Constants.ACC_PUBLIC, BCELUtilities.getType(properties[i].type), properties[i].name, pool); Field f = fg.getField(); fields.put(properties[i].name, f); clsGen.addField(f);}
Philly JUG: May 21, 20021Middle
BCEL: 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
Philly JUG: May 21, 20021Middle
BCEL: Adding a Method
ClassGen clsGen = ...;InstructionList il = ...;
private void createConstructor() { MethodGen mg = new MethodGen( Constants.ACC_PUBLIC, Type.VOID, new Type[]{...}, new String[]{...}, "<init>", subclassName, il, pool); il.append(new ALOAD(0)); il.append(new PUTFIELD(...)); clsGen.addMethod(mg.getMethod()); il.dispose(); // InstructionList is reusable}
Philly JUG: May 21, 20021Middle
BCEL: 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
Philly JUG: May 21, 20021Middle
Loading 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); }}
Philly JUG: May 21, 20021Middle
Dynamic 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
Philly JUG: May 21, 20021
Summary
● 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
End
Philly JUG: May 21, 20021
One For The Road
End
Would it make sense to implement a JSP container using Dynamic Classes?
Philly JUG: May 21, 2002
Philly JUG: May 21, 20021
Slides
● Slides from this presentation and the complete code for all snippets will be available soon at:
http://www.chariotsolutions.com/phillyjug/
End