java bytecode and classes

42
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

Upload: yoavwix

Post on 28-Nov-2014

5.062 views

Category:

Technology


0 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Java bytecode and classes

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 AbrahamiWix.com

Page 2: Java bytecode and classes

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

Page 3: Java bytecode and classes

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

Page 4: Java bytecode and classes

Java Classloading

Commandline Java App Tomcat (6)

Bootstrap classloader

Ext classloader

Application/ System

classloader

Application/ System

classloader

Common classloader

WAR 1classloader

WAR 2classloader

Page 5: Java bytecode and classes

Java Classloading

Commandline Java App Tomcat (6)

Bootstrap classloader

Ext classloader

Application/ System

classloader

Application/ System

classloader

Common classloader

WAR 1classloader

WAR 2classloader

Loads the Application Jars from the classpath

Loads onlybootstrap.jar and tomcat-juli.jar

Loads Tomcat commons library jars

Loads jars in the webapp lib directory

Page 6: Java bytecode and classes

LETS TALK BUSINESSOk, we understand classes. Where is the cool stuff?

Page 7: Java bytecode and classes

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

Page 8: Java bytecode and classes

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)

Page 9: Java bytecode and classes

The Java Proxy

• Example codepublic 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");

Page 10: Java bytecode and classes

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

Page 11: Java bytecode and classes

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/>

Page 12: Java bytecode and classes

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

Page 13: Java bytecode and classes

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/>

Page 14: Java bytecode and classes

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>

Page 15: Java bytecode and classes

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-INF\content.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>

Page 16: Java bytecode and classes

ASPECTJ 101As long as we’re talking about aspects…

Page 17: Java bytecode and classes

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

Page 18: Java bytecode and classes

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

Page 19: Java bytecode and classes

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

Page 20: Java bytecode and classes

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

Page 21: Java bytecode and classes

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"); } }

Page 22: Java bytecode and classes

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

Page 23: Java bytecode and classes

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); } }

Page 24: Java bytecode and classes

BACK TO THE COOL STUFF

Page 25: Java bytecode and classes

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

Page 26: Java bytecode and classes

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[]).

Page 27: Java bytecode and classes

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.

Page 28: Java bytecode and classes

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

Page 29: Java bytecode and classes

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

Page 30: Java bytecode and classes

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

Page 31: Java bytecode and classes

Writing Bytecode

• Example – mapping objects from type A to type BUsing 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

Page 32: Java bytecode and classes

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 memberspublic 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); } ...

Page 33: Java bytecode and classes

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); }

Page 34: Java bytecode and classes

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

Page 35: Java bytecode and classes

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

publicReturn type

Input arguments

Class nameMethod name

Input argument names

Page 36: Java bytecode and classes

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

Page 37: Java bytecode and classes

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

Page 38: Java bytecode and classes

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

Page 39: Java bytecode and classes

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(); }

Page 40: Java bytecode and classes

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

Page 41: Java bytecode and classes

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

Page 42: Java bytecode and classes

IT IS THAT SIMPLE