con-fess 2015 - having fun with javassist
TRANSCRIPT
Agenda
The use cases for bytecode manipulation
Code examples with Javassist
How to create your own Java agent
speakerdeck.com/antonarhipov
github.com/zeroturnaround/callspy
Bytecode manipulation
Code analysis:
Find bugs in your application
Examine code complexity
Find classes by specific attributes / annotations
Bytecode manipulation
Code generation:
Lazily load data using proxies
Add cross-cutting concerns to the code
Generate classes from non-Java artefacts
Bytecode manipulation
Main use case for Javassist: generate proxiesi.e. at runtime, create a subclass that intercepts all method invocations
Spring AOPHibernate proxies EJB proxies
CDI proxies
java.lang.reflect.Proxy is not enough
Bytecode manipulation
Transform classes without source codeCode profiling
Code optimization
Some other crazy things
public void doSomething(){ // no logging here // argh!!! stmt; stmt; stmt; }
If only I could add more logging here…
Javassist
Bytecode manipulation made easy
Source level and bytecode level APIs
Uses vocabulary of a Java language
On-the-fly compilation of injected codewww.javassist.org
CtPool
CtClassCtClassCtClass
CtClass
CtFieldCtMethodCtConst
CtMethod
insertBeforeinsertAfterinstrument
Javassist
import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String args, Instrumentation inst) { inst.addTransformer(new ClassFileTransformer { … }); }
public static void agentmain(String args, Instrumentation inst) { premain(args, inst); }}
public static void premain(String args, Instrumentation inst)
java -javaagent:<jarfile>[=options]
META-INF/MANIFEST.MF is required
public static void agentmain(String args, Instrumentation inst)
Executed when the agent attaches to the running JVM
META-INF/MANIFEST.MF is required
Requires support for loading agents in JVM
Allows adding the code to JVM post-factum
Manifest-Version: 1.0 Ant-Version: Apache Ant 1.8.1 Created-By: 1.7.0_15-b03 (Oracle Corporation) Premain-Class: com.zeroturnaround.javarebel.java5.AgentInstall Can-Redefine-Classes: true Boot-Class-Path: jrebel.jar Main-Class: com.zeroturnaround.javarebel.java4.Install Implementation-Title: JRebel Specification-Title: jrebel Specification-Version: 5.4.0 Implementation-Version: 201309021535
Manifest.mf
import com.sun.tools.attach.VirtualMachine;
//attach to target VMVirtualMachine vm = VirtualMachine.attach("2177");
//get system properties in the target VMProperties props = vm.getSystemProperties();
//load agent into the VMvm.loadAgent("agent.jar", "arg1=x,arg2=y");
//detach from VMvm.detach();
http://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/com/sun/tools/attach/VirtualMachine.html
Instrumentation API
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
void appendToBootstrapClassLoaderSearch(JarFile jarfile);void appendToSystemClassLoaderSearch(JarFile jarfile);
Class[] getAllLoadedClasses();Class[] getInitiatedClasses(ClassLoader loader); void redefineClasses(ClassDefinition... classes);void retransformClasses(Class<?>... classes);
byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain pd, byte[] classfileBuffer);
ClassFileTransformer
public class MyTransformer implements ClassFileTransformer {
public void byte[] transform(ClassLoader loader, String className, Class<?> toRefine, ProtectionDomain pd, byte[] classfileBuffer){
ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass(new ByteArrayInputStream(classfileBuffer));
// transform the bytes as required, // for instance - with Javassist return ct.toBytecode(); }}
Class loading
Instrumentation ClassFileTransformer
ClassFileTransformer
ClassFileTransformer
Javassist
ASM