Presentation is loading. Please wait.

Presentation is loading. Please wait.

Philly JUG: May 21, 2002 Dynamic Java Aaron Mulder Chief Technical Officer Chariot Solutions Classes Without Code.

Similar presentations


Presentation on theme: "Philly JUG: May 21, 2002 Dynamic Java Aaron Mulder Chief Technical Officer Chariot Solutions Classes Without Code."— Presentation transcript:

1 Philly JUG: May 21, 2002 Dynamic Java Aaron Mulder Chief Technical Officer Chariot Solutions Classes Without Code

2 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

3 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

4 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

5 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!

6 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

7 Philly JUG: May 21, 2002 Reflection Review

8 Philly JUG: May 21, 20021 Middle ● 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(...);... }

9 Philly JUG: May 21, 20021 Middle ● 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();... }

10 Philly JUG: May 21, 20021 Middle ● 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();... }

11 Philly JUG: May 21, 20021 Middle ● 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);... }

12 Philly JUG: May 21, 2002 Dynamic Proxies

13 Philly JUG: May 21, 20021 Middle ● 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

14 Philly JUG: May 21, 20021 Middle Dynamic Proxy Diagram Client Proxy (impl. interfaces) Invocation Handler

15 Philly JUG: May 21, 20021 Middle ● 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

16 Philly JUG: May 21, 20021 Middle InvocationHandler API public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }

17 Philly JUG: May 21, 20021 Middle ● 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

18 Philly JUG: May 21, 20021 Middle 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); }

19 Philly JUG: May 21, 20021 Middle ● 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

20 Philly JUG: May 21, 20021 Middle 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

21 Philly JUG: May 21, 20021 Middle 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)); }

22 Philly JUG: May 21, 20021 Middle Connection Proxy Diagram Client Proxy (impl. Connection) Invocation Handler Real Connection

23 Philly JUG: May 21, 20021 Middle 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!

24 Philly JUG: May 21, 20021 Middle 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; }

25 Philly JUG: May 21, 20021 Middle 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; }

26 Philly JUG: May 21, 20021 Middle 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)...

27 Philly JUG: May 21, 20021 Middle 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])

28 Philly JUG: May 21, 20021 Middle 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 Client Remote Interface Container Bean Instance What implements this?

29 Philly JUG: May 21, 20021 Middle 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.

30 Philly JUG: May 21, 20021 Middle Generated Code Diagram Client Generated Remote Bean Stub Remote Bean Impl Container

31 Philly JUG: May 21, 20021 Middle 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

32 Philly JUG: May 21, 20021 Middle JBoss Proxy Diagram Client Proxy (impl EJB Remote Interface) Invocation Handler Container Remote Stub Container Impl

33 Philly JUG: May 21, 20021 Middle 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

34 Philly JUG: May 21, 20021 Middle 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

35 Philly JUG: May 21, 2002 Dynamic Classes

36 Philly JUG: May 21, 20021 Middle 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

37 Philly JUG: May 21, 20021 Middle Typical 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

38 Philly JUG: May 21, 20021 Middle 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/

39 Philly JUG: May 21, 20021 Middle 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

40 Philly JUG: May 21, 20021 Middle 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

41 Philly JUG: May 21, 20021 Middle Solution 1: Write Code, Run Compiler, Load Class... ● This should be familiar by now

42 Philly JUG: May 21, 20021 Middle 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

43 Philly JUG: May 21, 20021 Middle 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)

44 Philly JUG: May 21, 20021 Middle 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

45 Philly JUG: May 21, 20021 Middle 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) {...

46 Philly JUG: May 21, 20021 Middle 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("..."); }

47 Philly JUG: May 21, 20021 Middle 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

48 Philly JUG: May 21, 20021 Middle 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(), " ", Constants.ACC_PUBLIC | Constants.ACC_FINAL, new String[0]); pool = clsGen.getConstantPool(); il = new InstructionList(); fields = new HashMap();

49 Philly JUG: May 21, 20021 Middle 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); }

50 Philly JUG: May 21, 20021 Middle 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

51 Philly JUG: May 21, 20021 Middle BCEL: Adding a Method ClassGen clsGen =...; InstructionList il =...; private void createConstructor() { MethodGen mg = new MethodGen( Constants.ACC_PUBLIC, Type.VOID, new Type[]{...}, new String[]{...}, " ", subclassName, il, pool); il.append(new ALOAD(0)); il.append(new PUTFIELD(...)); clsGen.addMethod(mg.getMethod()); il.dispose(); // InstructionList is reusable }

52 Philly JUG: May 21, 20021 Middle 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

53 Philly JUG: May 21, 20021 Middle 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); }

54 Philly JUG: May 21, 20021 Middle 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

55 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

56 Philly JUG: May 21, 20021 One For The Road End Would it make sense to implement a JSP container using Dynamic Classes?

57 Philly JUG: May 21, 2002

58 1 Slides ● Slides from this presentation and the complete code for all snippets will be available soon at: http://www.chariotsolutions.com/phillyjug/ End


Download ppt "Philly JUG: May 21, 2002 Dynamic Java Aaron Mulder Chief Technical Officer Chariot Solutions Classes Without Code."

Similar presentations


Ads by Google