1. Playing with Java Classes
and Bytecode
What is a Class?
How it is loaded and used?
How to write a Java program that writes itself at runtime?
Yoav Abrahami
Wix.com
2. Java Classloading
• Why do we care?
– Because if we’re gonna write code at runtime, we’d better know
how to load and use it…
– Because we don’t really understand classes
• So… what identifies a class?
– Its name
– Its package
– Its classloader
• It means that
– We can have multiple instances of a class loaded at the same time
– Two instances of the same class from different classloaders are not compatible and not
assignable
– Static variables are static only in the context of a classloader, not globally as we’re
always told
3. Java Classloading
• So what is this classloader?
– A Java class (subclass of java.lang.ClassLoader), responsible for loading other classes
used by the JVM
– Classloaders are arranged as a tree
• Bootstrap classloader
– Loads the Java system
• jre/lib/resources.jar – series of resource files
• jre/lib/rt.jar – the java.*, javax.*, etc packages
• jre/lib/sunrsasign.jar
• jre/lib/jsse.jar – secure socket extension
• jre/lib/jce.jar – Java cryptography extension
• jre/lib/charsets.jar
• jre/classes
– The important stuff is in rt.jar – the base Java classes
Bootstrap
classloader
Ext
classloader
Application
/ System
classloader
4. Java Classloading
Commandline Java App Tomcat (6)
Bootstrap
classloader
Ext
classloader
Application
/ System
classloader
Application
/ System
classloader
Common
classloader
WAR 1
classloader
WAR 2
classloader
5. Java Classloading
Commandline Java App Tomcat (6)
Bootstrap
classloader
Ext
classloader
Application
/ System
classloader
Application
/ System
classloader
Common
classloader
WAR 1
classloader
WAR 2
classloader
Loads the Application Jars
from the classpath
Loads only
bootstrap.jar and
tomcat-juli.jar
Loads Tomcat
commons library jars
Loads jars in the
webapp lib directory
7. What we’re gonna talk about
• Aspect Oriented Programming
– Java Proxy
– Spring Aspects
– AspectJ Aspects
• Doing the really cool stuff
– The bootstrap classloader
– The javaagent and class instrumentation
– Writing bytecode at runtime
• All in context of when I’ve had to use them
8. The Java Proxy
• What does it do?
– Allows implementing one or more interfaces dynamically
• When do we use it?
– Generic implementation of an interface – such as in the case of a client calling a service.
The client uses an interface with a generic implementation that marshals the method
calls to whatever “on-the-wire” format. For instance, creating a SOAP client on the fly
using an interface and WSDL
– Simplistic AOP – to catch method calls, perform some pre/post/around logic and
delegate the call to the real implementation. Can be used for transaction handling,
logging, etc.
• Limitations
– Only supports Java interfaces
– Intercepts only calls to the proxy instance. Direct calls to the
delegate will not be intercepted (for instance, a call from one
delegate method to another)
9. The Java Proxy
• Example code
public interface SomeInterface {
public void doSomething();
public void doAnotherThing(String name);
}
Object p = Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class<?>[]{SomeInterface.class}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoking method:" + method.getName());
if (args != null)
for (Object arg: args)
System.out.println(" arg: " + arg);
return null;
}
});
SomeInterface s = (SomeInterface)p;
s.doSomething();
s.doAnotherThing("hello");
10. Spring AOP
• What does it do?
– Allows intercepting method calls to Spring beans, with or without an interface
– Simpler code compared to the Java Proxy
– Based on the proxy model – Spring BeanFactory returns a proxy to the real bean
– Supports choosing the methods to intercept using AspectJ selectors
– Aspects are written using the AspectJ Java syntax
• When do we use it?
– AOP on Spring beans
– Transaction handling, logging, security handling – anything AOP is good for
• Limitations
– Only supports Spring beans
– Intercepts only calls to the proxy instance. Direct calls to the
delegate will not be intercepted (for instance, a call from one
delegate method to another)
– Supports only a subset of AspectJ selectors
11. Spring AOP
• Configuring Spring AOP
• Example Aspect
• Activating the Aspect
– Using Spring component scan, by adding the @Component annotation on the aspect
– Using Spring Beans XML
@Aspect
public class ExampleAspect {
@Around("@annotation(com.experiments.RequireLogin)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable
{
...
}
}
<bean class="com.experiments.ExampleAspect"/>
<aop:aspectj-autoproxy/>
12. AspectJ
• What does it do?
– Allows intercepting any JoinPoint such as method calls, exceptions, etc.
– Supports writing aspects using Java or AspectJ syntax
– Modifies the actual class bytecode
– Supports choosing the methods to intercept using AspectJ selectors
– Modify existing classes, adding methods, members and super-interfaces to them
– Weaves aspects on load time or compile time
• When do we use it?
– AOP on any Java class
– Transaction handling, logging, security – anything AOP is good for
– Introduce compiler-like coding rules
• Limitations
– For compile-time weaving – replaces the standard Java Compiler
– For load-time weaving – requires elaborate JVM configuration
13. AspectJ
• Configuration options
– Compile-time weaving
– Load-time weaving
– Load-time weaving with Spring
• Dependencies
– aspectjrt.jar must be in the classpath
• Configuring AspectJ load-time weaving with Spring
– Simple, isn’t it?
• Well, not so simple…
<aop:aspectj-autoproxy/>
<context:load-time-weaver/>
14. AspectJ
• Need to include META-INF/aop.xml
– declares the aspects for load-time weaving and the target packages to weave into
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only any class from the com.experiments package -->
<include within="com.experiments..*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="com.experiments.ExampleAspect"/>
</aspects>
</aspectj>
15. AspectJ
• Per-platform configuration for class instrumentation
– Command line & JUnit applications – using the -javaagent to load instrumentation jars
• -javaagent:spring-instrument-<version>.jar -javaagent:aspectjweaver-<version>.jar
– Maven running JUnit – using the maven-surefire-plugin, passing it the javaagent args
– Note the two javaagent params must be at the same line
– Tomcat 6 –
• spring-instrument-tomcat-<version>.jar has to be copied to the Tomcat lib directory
• The webapp must have a context.xml file in WEB-INFcontent.xml, with the minimum content
– There are alternative locations for the context file. This is the location I find easiest to use.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.6</version>
<configuration>
<forkMode>once</forkMode>
<argLine> -javaagent:"${settings.localRepository}/org/springframework/spring-
instrument/${org.springframework.version}/spring-instrument-
${org.springframework.version}.jar"
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/
${org.aspectj.version}/aspectjweaver-${org.aspectj.version}.jar" </argLine>
<useSystemClassLoader>true</useSystemClassLoader>
</configuration>
</plugin>
<Context path="/">
<Loader loaderClass="org.springframework.instrument.classloading.tomcat.
TomcatInstrumentableClassLoader"/>
</Context>
17. AOP 101 – AspectJ Style
• Aspect – a concern that cuts across multiple classes. Examples are logging,
transaction handling, security, etc.
• Join Point – a point during the execution of a program. Examples are when
– A method is executed
– A method is called
– A constructor is executed
– A constructor is called
– An exception handler is executed
– An advice is executed
– Static initialization is executed
– Initialization and pre-initialization of an object
• Advice – an action taken by the aspect at a particular pointcut. Types of advice:
– Before, after returning, after throwing, after finally, around
• Pointcut – a selector of Join Points using predicates. An advice is associated with a
pointcut expression and runs on any point matched by the pointcut
18. AOP 101 – AspectJ Style
• More on pointcuts
– Pointcuts are query expressions on the code
– May be joined using the and (‘&&’), or (‘||’) or not (‘!’) operators
• Some pointcut predicates
– execution(…) – when a particular method body executes
– call(…) – when a method is called
– handler(….) – when a particular exception handler executes
– this(…) – when the currently executing object is of a certain type
– target(…) – when the target object is of type
– args(…) – when the method arguments match a certain type
– within(…) – when the executing code belongs to a certain class
– cflow(…) – when the program flow is in a certain method call (the method is a parent in
the stack)
– @annotation(…) – methods annotated with a certain annotation
– @target(…) – when the target executing object has a certain annotation
– @args(…) – when the runtime type of an argument has a certain annotation
– @within(…) – limits to pointcuts within a class that has a certain annotation
– bean(…) – a spring bean name
19. AOP 101 – AspectJ Style
• More on pointcuts
– Pointcuts are query expressions on the code
– May be joined using the and (‘&&’), or (‘||’) or not (‘!’) operators
• Some pointcut predicates
– execution(…) – when a particular method body executes
– call(…) – when a method is called
– handler(….) – when a particular exception handler executes
– this(…) – when the currently executing object is of a certain type
– target(…) – when the target object is of type
– args(…) – when the method arguments match a certain type
– within(…) – when the executing code belongs to a certain class
– cflow(…) – when the program flow is in a certain method call (the method is a parent in
the stack)
– @annotation(…) – methods annotated with a certain annotation
– @target(…) – when the target executing object has a certain annotation
– @args(…) – when the runtime type of an argument has a certain annotation
– @within(…) – limits to pointcuts within a class that has a certain annotation
– bean(…) – a spring bean name
Not
Supported by
Spring AOP
Only Spring
AOP
20. AOP 101 – AspectJ Style
• Some examples
– execution (int *())
• JoinPoints that are integer-returning method executions that do not take parameters
– @annotation(com.example.Log) && execution(* *(..))
• JoinPoints that are executions of methods annotated with the @Log annotation, regardless of
the method return type, class, name or parameters
– call(public * *(..))
• Call to any public method
– !this(Point) && call(int *(..))
• Any call to a method returning an integer when the executing object is of any type other
than Point
– cflow(P) && cflow(Q)
• All JoinPoint that are both in the control flow of P and in the control flow of Q
21. AOP 101 – AspectJ Style
• Coding Aspects using AspectJ Java Syntax
• The AspectJ syntax
@Aspect
public class MyAspect {
@Pointcut("within(com.springsource..*)")
public void inSpring() {}
@Pointcut("@Annotation(java.lang.Deprecated)")
public void inDepracated() {}
@Pointcut("inSpring() && inDepracated()")
public void deprecatedInSpring() {}
@Before("deprecatedInSpring()")
public void pointcutRef() {
...
}
@Before("within(com.springsource..*) && @Annotation(java.lang.Deprecated)")
public void pointcutInplace() {
...
}
}
aspect A {
pointcut fooPC(): execution(void Test.foo());
pointcut gooPC(): execution(void Test.goo());
pointcut printPC(): call(void java.io.PrintStream.println(String));
before(): cflow(fooPC()) && cflow(gooPC()) && printPC() && !within(A) {
System.out.println("should occur");
}
}
22. AOP 101 – AspectJ Style
• Adding code to existing classes using aspects (Inter-type declarations)
– Adding implemented interfaces
– Changing the superclass of a class
– Adding methods to a class
– Adding members to a class
– Implement Mix-ins in Java
• Using Join Points to raise custom compiler errors or warnings
• Privileged aspects
– Can access private, package and protected members of classes, bypassing the java
member visibility constraints
23. AOP 101 – AspectJ Style
• Example of an aspect modifying a class
aspect PointAssertions {
private boolean Point.assertX(int x) { return (x <= 100 && x >= 0); }
private boolean Point.assertY(int y) { return (y <= 100 && y >= 0); }
before(Point p, int x): target(p) && args(x) && call(void setX(int)) {
if (!p.assertX(x)) {
System.out.println("Illegal value for x");
return;
}
}
before(Point p, int y): target(p) && args(y) && call(void setY(int)) {
if (!p.assertY(y)) {
System.out.println("Illegal value for y");
return;
}
}
}
public class Point {
int x, y;
public void setX(int x) { this.x = x; }
public void setY(int y) { this.y = y; }
public static void main(String[] args) {
Point p = new Point();
p.setX(3);
p.setY(333);
}
}
25. The bootstrap classloader
• What does it do?
– Allows replacing Java system classes
– Bypasses all Java security policies set by SecurityManager
• When do we use it?
– Meddling with the core Java APIs
– Terracotta uses it to replace the Java HashMap class with a distributed cache
implementation
• Limitations
– Classes loaded by the bootstrap classloader can’t load classes that are not included in
the bootstrap classpath
• Usage
– With the Java command line options
• –bootclasspath: - list of jars to use instead of the standard list
• –bootclasspath/a: - list of jars to append to the standard list
• –bootclasspath/p: - list of jars to prepend to the standard list
26. Javaagent and Instrument
• What does it do?
– Allows instrumenting / transforming / changing a class as it is loaded
• When do we use it?
– AspectJ uses it for load-time weaving
– To modify classes as we load them
• Limitations
– Works on the class file bytes – requires intimate knowledge of the class file structure
– Not very useful without a framework like AspectJ, BCEL, cglib, etc.
• Usage
– Using the –javaagent command line option to introduce an Agent jar
– The Agent jar has to have a premain method
• public static void premain(String agentArguments, Instrumentation
instrumentation)
– The Instrumentation class allows to redefine a class,
add & remove transformers, and re-transform a class.
– ClassFileTransformer has one method to transform a class
file bytes (as byte[]).
27. Writing Bytecode
• What does it do?
– Write new classes or modify existing classes at runtime
– Load the new/modified classes
– Instantiate them
– Use them as any other Java class
• When do we use it?
– Implementing Proxies on any class – Spring AOP and Hibernate do just that
– Hibernate uses bytecode manipulations of classes to introduce mechanisms for lazy-
loading of members
• My own experience
– Performance – when a mapping library (in my case XML to objects) was too slow due to
excessive use of reflection, coding simple mapping classes proved much faster
– Adaptor for Beans – a reporting framework used reflection on beans to read the field
definitions from a JavaObject datasource. When adapting a
Java query interface that returns map-like objects, we had to
generate wrapper classes adapting the map to the beans the
framework expected.
28. Writing Bytecode
• Limitations
– Hard to use
– Generated bytecode cannot be debugged normally – there’s no source code
• BCEL – I’ve found using BCEL to be the easiest
• BCEL is included with JRE 1.6
– In package com.sun.org.apache.bcel
– However, the JRE version didn’t work for me
– Eventually, I’ve used the Apache version – BCEL 5.2
• Guidelines
– Keep the generated code small. Use helper methods / superclasses whenever you can.
Remember, you can debug helper classes or superclasses, but you can’t debug
generated bytecode
– Classes can be generated at build-time or runtime.
I’ve found runtime generation simpler to use
– Use class generation-by-example
29. Writing Bytecode
• Code generation or bytecode generation?
• Code generation
– Performed at build-time - inputs to the generation process must be available at
build time
– Easier to debug
– Tends to clutter the project source code
– Code-generation code tends to be longer than bytecode-generation code
– Hard to get the build right – separation of generated code from actual code
• Bytecode generation
– Performed at runtime – inputs to the generation process can be resolved at runtime
– Requires a higher skill level
– More complex to debug
• At the philosophical level
– If it can be generated, it’s not code!
– We’re just configuring the JVM, not coding
30. Writing Bytecode
• Class generation-by-example
– Write an example class of what you want to generate
– Compile it
– Run org.apache.bcel.util.BCELifier. It generates a factory class that generates your
example class bytecode
– Modify the generated factory class so that it generates the bytecode for the
classes you want
– Use a bytecode verifier to verify the generated classes
– Decompile generated files and verify method logic. I recommend that you set the
"annotate" option so you can see the bytecode instruction as comments.
– BCEL also provides a tool to generate HTML documentation for your example class
bytecode
• Class loading
– Code a new subclass of ClassLoader
– Use the defineClass (protected) method to define the new
generated class
– Remember to link your classloader to a parent classloader
31. Writing Bytecode
• Example – mapping objects from type A to type B
Using a superclass helper
public abstract class BaseMapper {
protected Object a;
protected Object b;
public BaseMapper(Object a, Object b) {
this.a = a;
this.b = b;
}
public abstract Object mapToB(Object a);
public abstract Object mapToA(Object b);
}
public class ExampleMappedClass extends BaseMapper{
public ExampleMappedClass(Person person, PersonDTO personDTO) {
super(person, personDTO);
}
@Override
public PersonDTO mapToB(Object a) {
Person person = (Person)a;
PersonDTO personDTO = new PersonDTO();
personDTO.name = person.name;
personDTO.lastName = person.lastName;
personDTO.id = Integer.toString(person.id);
return personDTO;
}
@Override public Person mapToA(Object b) {
PersonDTO personDTO = (PersonDTO)b;
Person person = new Person();
person.name = personDTO.name;
person.lastName = personDTO.lastName;
person.id = Integer.parseInt(personDTO.id);
return person;
}
}
The example class
32. Writing Bytecode
• Running BCELifier on the class
– BCELifier has a main method, accepting one parameter – the class name
• The created factory class
– Defining the class and the class members
public class ExampleMappedClassCreator implements Constants {
private InstructionFactory _factory;
private ConstantPoolGen _cp;
private ClassGen _cg;
public ExampleMappedClassCreator() {
_cg = new ClassGen("com.experiments.ExampleMappedClass", "com.experiments.BaseMapper",
"ExampleMappedClass.java", ACC_PUBLIC | ACC_SUPER, new String[] { });
_cp = _cg.getConstantPool();
_factory = new InstructionFactory(_cg, _cp);
}
public void create(OutputStream out) throws IOException {
createMethod_0();
createMethod_1();
createMethod_2();
createMethod_3();
createMethod_4();
_cg.getJavaClass().dump(out);
}
...
33. Writing Bytecode
• Creating the Factory class (continued)
– Creating the constructor
– Which generates bytecode for the constructor
private void createMethod_0() {
InstructionList il = new InstructionList();
MethodGen method = new MethodGen(ACC_PUBLIC, Type.VOID, new Type[] {
new ObjectType("com.experiments.Person"),
new ObjectType("com.experiments.PersonDTO") },
new String[] { "arg0", "arg1" },
"<init>", "com.experiments.ExampleMappedClass", il, _cp);
InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 0));
il.append(_factory.createLoad(Type.OBJECT, 1));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createInvoke("com.experiments.BaseMapper", "<init>",
Type.VOID, new Type[] { Type.OBJECT, Type.OBJECT }, Constants.INVOKESPECIAL));
InstructionHandle ih_6 = il.append(_factory.createReturn(Type.VOID));
method.setMaxStack();
method.setMaxLocals();
_cg.addMethod(method.getMethod());
il.dispose();
}
public ExampleMappedClass(Person person, PersonDTO personDTO) {
super(person, personDTO);
}
34. public PersonDTO mapToB(Object a) {
Person person = (Person)a;
PersonDTO personDTO = new PersonDTO();
personDTO.name = person.name;
personDTO.lastName = person.lastName;
personDTO.id = Integer.toString(person.id);
return personDTO;
}private void createMethod_1() {
InstructionList il = new InstructionList();
MethodGen method = new MethodGen(ACC_1PUBLIC, new ObjectType("com.experiments.PersonDTO"),
new Type[] { Type.OBJECT }, new String[] { "arg0" }, "mapToB", "com.experiments.ExampleMappedClass", il, _cp);
InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 1));
il.append(_factory.createCheckCast(new ObjectType("com.experiments.Person")));
il.append(_factory.createStore(Type.OBJECT, 2));
InstructionHandle ih_5 = il.append(_factory.createNew("com.experiments.PersonDTO"));
il.append(InstructionConstants.DUP);
il.append(_factory.createInvoke("com.experiments.PersonDTO", "<init>", Type.VOID, Type.NO_ARGS,
Constants.INVOKESPECIAL));
il.append(_factory.createStore(Type.OBJECT, 3));
InstructionHandle ih_13 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "name", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "name", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_21 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "lastName", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "lastName", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_29 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "id", Type.INT, Constants.GETFIELD));
il.append(_factory.createInvoke("java.lang.Integer", "toString", Type.STRING, new Type[] { Type.INT },
Constants.INVOKESTATIC));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "id", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_40 = il.append(_factory.createLoad(Type.OBJECT, 3));
InstructionHandle ih_41 = il.append(_factory.createReturn(Type.OBJECT));
method.setMaxStack();
method.setMaxLocals();
_cg.addMethod(method.getMethod());
il.dispose();
}
Writing Bytecode
• Creating the Factory class (continued)
– Creating the mapToB method
35. public PersonDTO mapToB(Object a) {
Person person = (Person)a;
PersonDTO personDTO = new PersonDTO();
personDTO.name = person.name;
personDTO.lastName = person.lastName;
personDTO.id = Integer.toString(person.id);
return personDTO;
}private void createMethod_1() {
InstructionList il = new InstructionList();
MethodGen method = new MethodGen(ACC_1PUBLIC, new ObjectType("com.experiments.PersonDTO"),
new Type[] { Type.OBJECT }, new String[] { "arg0" }, "mapToB", "com.experiments.ExampleMappedClass", il, _cp);
InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 1));
il.append(_factory.createCheckCast(new ObjectType("com.experiments.Person")));
il.append(_factory.createStore(Type.OBJECT, 2));
InstructionHandle ih_5 = il.append(_factory.createNew("com.experiments.PersonDTO"));
il.append(InstructionConstants.DUP);
il.append(_factory.createInvoke("com.experiments.PersonDTO", "<init>", Type.VOID, Type.NO_ARGS,
Constants.INVOKESPECIAL));
il.append(_factory.createStore(Type.OBJECT, 3));
InstructionHandle ih_13 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "name", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "name", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_21 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "lastName", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "lastName", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_29 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "id", Type.INT, Constants.GETFIELD));
il.append(_factory.createInvoke("java.lang.Integer", "toString", Type.STRING, new Type[] { Type.INT },
Constants.INVOKESTATIC));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "id", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_40 = il.append(_factory.createLoad(Type.OBJECT, 3));
InstructionHandle ih_41 = il.append(_factory.createReturn(Type.OBJECT));
method.setMaxStack();
method.setMaxLocals();
_cg.addMethod(method.getMethod());
il.dispose();
}
Writing Bytecode
• Creating the Factory class (continued)
– Creating the mapToB method
public
Return type
Input arguments
Class name
Method name
Input argument names
36. public PersonDTO mapToB(Object a) {
Person person = (Person)a;
PersonDTO personDTO = new PersonDTO();
personDTO.name = person.name;
personDTO.lastName = person.lastName;
personDTO.id = Integer.toString(person.id);
return personDTO;
}private void createMethod_1() {
InstructionList il = new InstructionList();
MethodGen method = new MethodGen(ACC_1PUBLIC, new ObjectType("com.experiments.PersonDTO"),
new Type[] { Type.OBJECT }, new String[] { "arg0" }, "mapToB", "com.experiments.ExampleMappedClass", il, _cp);
InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 1));
il.append(_factory.createCheckCast(new ObjectType("com.experiments.Person")));
il.append(_factory.createStore(Type.OBJECT, 2));
InstructionHandle ih_5 = il.append(_factory.createNew("com.experiments.PersonDTO"));
il.append(InstructionConstants.DUP);
il.append(_factory.createInvoke("com.experiments.PersonDTO", "<init>", Type.VOID, Type.NO_ARGS,
Constants.INVOKESPECIAL));
il.append(_factory.createStore(Type.OBJECT, 3));
InstructionHandle ih_13 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "name", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "name", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_21 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "lastName", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "lastName", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_29 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "id", Type.INT, Constants.GETFIELD));
il.append(_factory.createInvoke("java.lang.Integer", "toString", Type.STRING, new Type[] { Type.INT },
Constants.INVOKESTATIC));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "id", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_40 = il.append(_factory.createLoad(Type.OBJECT, 3));
InstructionHandle ih_41 = il.append(_factory.createReturn(Type.OBJECT));
method.setMaxStack();
method.setMaxLocals();
_cg.addMethod(method.getMethod());
il.dispose();
}
Writing Bytecode
• Creating the Factory class (continued)
– Creating the mapToB method
Store the result as Object 2
Take Object 1 – the a parameter
(Object 0 is this)
Cast it to Person
37. public PersonDTO mapToB(Object a) {
Person person = (Person)a;
PersonDTO personDTO = new PersonDTO();
personDTO.name = person.name;
personDTO.lastName = person.lastName;
personDTO.id = Integer.toString(person.id);
return personDTO;
}private void createMethod_1() {
InstructionList il = new InstructionList();
MethodGen method = new MethodGen(ACC_1PUBLIC, new ObjectType("com.experiments.PersonDTO"),
new Type[] { Type.OBJECT }, new String[] { "arg0" }, "mapToB", "com.experiments.ExampleMappedClass", il, _cp);
InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 1));
il.append(_factory.createCheckCast(new ObjectType("com.experiments.Person")));
il.append(_factory.createStore(Type.OBJECT, 2));
InstructionHandle ih_5 = il.append(_factory.createNew("com.experiments.PersonDTO"));
il.append(InstructionConstants.DUP);
il.append(_factory.createInvoke("com.experiments.PersonDTO", "<init>", Type.VOID, Type.NO_ARGS,
Constants.INVOKESPECIAL));
il.append(_factory.createStore(Type.OBJECT, 3));
InstructionHandle ih_13 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "name", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "name", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_21 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "lastName", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "lastName", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_29 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "id", Type.INT, Constants.GETFIELD));
il.append(_factory.createInvoke("java.lang.Integer", "toString", Type.STRING, new Type[] { Type.INT },
Constants.INVOKESTATIC));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "id", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_40 = il.append(_factory.createLoad(Type.OBJECT, 3));
InstructionHandle ih_41 = il.append(_factory.createReturn(Type.OBJECT));
method.setMaxStack();
method.setMaxLocals();
_cg.addMethod(method.getMethod());
il.dispose();
}
Writing Bytecode
• Creating the Factory class (continued)
– Creating the mapToB method
Store the result as Object 3
Create new PersonDTO
Invoke the personDTO constructor
38. public PersonDTO mapToB(Object a) {
Person person = (Person)a;
PersonDTO personDTO = new PersonDTO();
personDTO.name = person.name;
personDTO.lastName = person.lastName;
personDTO.id = Integer.toString(person.id);
return personDTO;
}private void createMethod_1() {
InstructionList il = new InstructionList();
MethodGen method = new MethodGen(ACC_1PUBLIC, new ObjectType("com.experiments.PersonDTO"),
new Type[] { Type.OBJECT }, new String[] { "arg0" }, "mapToB", "com.experiments.ExampleMappedClass", il, _cp);
InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 1));
il.append(_factory.createCheckCast(new ObjectType("com.experiments.Person")));
il.append(_factory.createStore(Type.OBJECT, 2));
InstructionHandle ih_5 = il.append(_factory.createNew("com.experiments.PersonDTO"));
il.append(InstructionConstants.DUP);
il.append(_factory.createInvoke("com.experiments.PersonDTO", "<init>", Type.VOID, Type.NO_ARGS,
Constants.INVOKESPECIAL));
il.append(_factory.createStore(Type.OBJECT, 3));
InstructionHandle ih_13 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "name", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "name", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_21 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "lastName", Type.STRING, Constants.GETFIELD));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "lastName", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_29 = il.append(_factory.createLoad(Type.OBJECT, 3));
il.append(_factory.createLoad(Type.OBJECT, 2));
il.append(_factory.createFieldAccess("com.experiments.Person", "id", Type.INT, Constants.GETFIELD));
il.append(_factory.createInvoke("java.lang.Integer", "toString", Type.STRING, new Type[] { Type.INT },
Constants.INVOKESTATIC));
il.append(_factory.createFieldAccess("com.experiments.PersonDTO", "id", Type.STRING, Constants.PUTFIELD));
InstructionHandle ih_40 = il.append(_factory.createLoad(Type.OBJECT, 3));
InstructionHandle ih_41 = il.append(_factory.createReturn(Type.OBJECT));
method.setMaxStack();
method.setMaxLocals();
_cg.addMethod(method.getMethod());
il.dispose();
}
Writing Bytecode
• Creating the Factory class (continued)
– Creating the mapToB method
Pop the stack, writing the value to the name property of object 3
Load object 3 to the stack - Person
Read the name field from object 2 to the stack
Load object 2 to the stack - PersonDTO
39. Writing Bytecode
• Creating the Factory class (continued)
– Creating the mapToB method – we got a second mapToB method
– The first has signature PersonDTO mapToB(Object)
– The second has signature Object mapToB(Object)
– The second overrides the superclass mapToB method and calls the first
– Narrowing the return type of mapToB was apparently not a good idea, given that
generated classes loaded at runtime do not benefit from the narrowed type constraint
private void createMethod_4() {
InstructionList il = new InstructionList();
MethodGen method = new MethodGen(ACC_PUBLIC | ACC_BRIDGE | ACC_SYNTHETIC, Type.OBJECT,
new Type[] { Type.OBJECT }, new String[] { "arg0" }, "mapToB",
"com.experiments.ExampleMappedClass", il, _cp);
InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 0));
il.append(_factory.createLoad(Type.OBJECT, 1));
il.append(_factory.createInvoke("com.experiments.ExampleMappedClass", "mapToB",
new ObjectType("com.experiments.PersonDTO"), new Type[] { Type.OBJECT }, Constants.INVOKEVIRTUAL));
InstructionHandle ih_5 = il.append(_factory.createReturn(Type.OBJECT));
method.setMaxStack();
method.setMaxLocals();
_cg.addMethod(method.getMethod());
il.dispose();
}
40. class MyClassLoader extends ClassLoader {
MyClassLoader(ClassLoader parent) {
super(parent);
}
public void defineClass(JavaClass javaClass) {
byte[] classBytes = javaClass.getBytes();
defineClass(javaClass.getClassName(), classBytes, 0, classBytes.length);
}
}
public JavaClass create() {
createMethod_0();
createMethod_1();
createMethod_2();
createMethod_3();
createMethod_4();
return _cg.getJavaClass();
}
MyClassLoader myClassLoader = new MyClassLoader(this.getClass().getClassLoader());
ExampleMappedClassCreator creator = new ExampleMappedClassCreator();
JavaClass javaClass = creator.create();
myClassLoader.defineClass(javaClass);
Class<?> clazz = myClassLoader.loadClass(javaClass.getClassName());
Constructor<?> constructor = clazz.getConstructor(Person.class, PersonDTO.class);
BaseMapper mapper = (BaseMapper)constructor.newInstance(new Person(), new PersonDTO());
Writing Bytecode• Using the generated class
– The Class loader
– Creating and loading the new class
– Add a method returning JavaClass to the factory class
– And use it
41. class MyClassLoader extends ClassLoader {
MyClassLoader(ClassLoader parent) {
super(parent);
}
public void defineClass(JavaClass javaClass) {
byte[] classBytes = javaClass.getBytes();
defineClass(javaClass.getClassName(), classBytes, 0, classBytes.length);
}
}
public JavaClass create() {
createMethod_0();
createMethod_1();
createMethod_2();
createMethod_3();
createMethod_4();
return _cg.getJavaClass();
}
MyClassLoader myClassLoader = new MyClassLoader(this.getClass().getClassLoader());
ExampleMappedClassCreator creator = new ExampleMappedClassCreator();
JavaClass javaClass = creator.create();
myClassLoader.defineClass(javaClass);
Class<?> clazz = myClassLoader.loadClass(javaClass.getClassName());
Constructor<?> constructor = clazz.getConstructor(Person.class, PersonDTO.class);
BaseMapper mapper = (BaseMapper)constructor.newInstance(new Person(), new PersonDTO());
Writing Bytecode• Using the generated class
– The Class loader
– Creating and loading the new class
– Add a method returning JavaClass to the factory class
– And use it
Create the classloader
Create the class factory
Create the class bytecode
Define the new class
Load the new class
Get the new class constructor
Call the constructor