Bytecode Manipulation with a Java Agent and Byte Buddy
如果无法正常显示,请先停止浏览器的去广告插件。
1. Bytecode Manipulation
with a Java Agent
and Byte Buddy
Koichi Sakata
PONOS Corporation
2. About Me
•
•
•
•
Koichi Sakata (阪田 浩一)
KanJava JUG Leader
Java Champion
PONOS Corporation
2
#oc1bcm
3. Intended Audience
• Those who want to start manipulating
bytecode
• Not for experts
3
#oc1bcm
4. Java Bytecode
.java
javac
• Java Code
.class
• Bytecode
Gets loaded by JVM
4
#oc1bcm
5. What is bytecode manipulation?
• Editing bytecode in the class files
– Add
– Delete
– Replace
5
#oc1bcm
6. Why do we manipulate?
It's fun!
6
#oc1bcm
7. Not Only Fun
• To change code at runtime
– Generate Entity objects in Hibernate
– Create mock objects in Mockito
7
#oc1bcm
8. Bytecode Manipulation
• Hard to manipulate by hand...
– A class file is a binary file
– JVM has a verification process
8
#oc1bcm
9. Bytecode Manipulation Libraries
•
•
•
•
•
•
Byte Buddy
Byteman
Javassist
cglib
ASM
Apache Commons BCEL
9
#oc1bcm
10. Byte Buddy
•
•
•
•
http://bytebuddy.net
Easy-to-use API
Java 11 Support
Duke's Choice Award 2015 Winner
10
#oc1bcm
11. Byte Buddy
• Usage in major libraries
– Hibernate
– Mockito
– Jackson
– etc.
11
#oc1bcm
12. Hello World in Byte Buddy
Class<?> subClass = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value ("Hello World!" ))
.make()
.load(getClass().getClassLoader())
.getLoaded();
12
#oc1bcm
13. Hello World in Byte Buddy
Class<?> subClass = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
1. Create a subclass of Object
2. Choose the toString()
13
#oc1bcm
14. Hello World in Byte Buddy
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value ("Hello World!" ))
1. Intercept toString() to return a fixed
string value "Hello World"
14
#oc1bcm
15. Hello World in Byte Buddy
.make()
.load(getClass().getClassLoader())
.getLoaded();
1. Make a new type (unloaded class)
2. Load the type
3. Get the new loaded class
15
#oc1bcm
16. Hello World in Byte Buddy
Class<?> subClass = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value ("Hello World!" ))
.make()
.load(getClass().getClassLoader())
.getLoaded();
16
#oc1bcm
17. Demo 1
• Run Hello World
– See a generated class
17
#oc1bcm
18. Demo code is available
• http://bit.ly/oc1bcm
– github.com/jyukutyo/SampleCodeForByt
eBuddySeeesion
18
#oc1bcm
19. Usage in Mockito
• SubclassBytecodeGenerator
– mockClass()
19
#oc1bcm
20. How to use BM?
1. Directly in your application
2. At build time via Maven/Gradle
3. With Java Agent
– No need for changing code
– Detachable
20
#oc1bcm
21. Java Agent?
• Do espionage activities in
your application
– Profiling
– Logging
– Changing the target
application itself
21
#oc1bcm
22. Java Agent
• Instrument programs running on the
JVM
– JSR 163: JavaTM Platform Profiling
Architecture
– JVM calls Java Agent method with
proper timing
22
#oc1bcm
23. Create a Java Agent
• Just define methods
– premain() Method
– agentmain() Method
23
#oc1bcm
24. Create a Java Agent
• premain() Method
– Gets called before main() method
• agentmain() Method
– Gets called when the agent is attached
after JVM startup
24
#oc1bcm
25. With Java Agent
We can run bytecode
manipulation code
at runtime
25
#oc1bcm
26. 26
#oc1bcm
27. premain()
public static void premain(String agentArgs,
Instrumentation inst)
• Provides opportunity to modify
classes before loading
– Instrumentation class has useful
methods
27
#oc1bcm
28. MANIFEST.MF
Premain-Class: com.s.logging.LoggingAgent
Boot-Class-Path: byte-buddy-1.8.22.jar
28
#oc1bcm
29. Demo 2
• Create a simple Java Agent
29
#oc1bcm
30. Another Example
• Add outputting logs before executing
a particular method on a particular
class
30
#oc1bcm
31. Example
1. Manipulate bytecode before loading
– Use premain()
2. Replace the class file
– Use ByteBuddy
– Not create a subclass
31
#oc1bcm
32. Target Class
public class Target {
public void foo() {
System.out.println("foo!");
}
public static void main(String[] args) {
new Target().foo();
}
}
32
#oc1bcm
33. Replace the class
TypePool pool = TypePool.Default.ofClassPath();
return new ByteBuddy()
.rebase(pool.describe("c.Target").resolve(),
ClassFileLocator.ForClassLoader
.ofClassPath()
)
33
#oc1bcm
34. Replace the class
return new ByteBuddy()
.rebase(...)
• Replace the original implementation with
new one
• Save the original implementation under a
different method name
34
#oc1bcm
35. Enhance Existing Method
• ByteBuddy#subclass()
– Override the method
• ByteBuddy#redefine()
– Replace the implementation
• ByteBuddy#rebase()
– Copy&Rename the method
– Replace the implementation
35
#oc1bcm
36. Replace the class
TypePool pool = TypePool.Default.ofClassPath();
return new ByteBuddy()
.rebase(pool.describe("c.Target")
.resolve(),
• Can't use a class literal
– JVM loads class before changing the class
• Use the TypePool class and describe()
36
#oc1bcm
37. Replace the class
.rebase(pool.describe(...).resolve(),
ClassFileLocator.ForClassLoader
.ofClassPath()
)
• Allow to locate a class file
• ofClassPath() - Scan the running
application's class path
37
#oc1bcm
38. Intercept the method
return new ByteBuddy()
...
.method(ElementMatchers.named("foo"))
.intercept(
MethodDelegation
.to(LoggingInterceptor.class)
.andThen(SuperMethodCall.INSTANCE)
)
38
#oc1bcm
39. Delegation
• MethodDelegation.to()
– Specify a Class to delegate
• A best match method is used
39
#oc1bcm
40. Intercept the method
return new ByteBuddy()
...
.method(ElementMatchers.named("foo"))
.intercept(
MethodDelegation
.to(LoggingInterceptor.class)
.andThen(SuperMethodCall.INSTANCE)
)
40
#oc1bcm
41. Interceptor Class
public static class LoggingInterceptor {
public static void intercept(
@Origin Method m) {
...println("Call " + m.getName()...);
}
}
41
#oc1bcm
42. Delegated Method
• Annotations for Parameter
– @Origin
• Reference to the method/constructor
42
#oc1bcm
43. Delegated Method
• Annotations for Parameter
– @This/@Super
• Dynamic/Super type's instance
– @Argument, @AllArguments
– @SuperCall
• Callable/Runnable instance of the super's
implementation of the method
43
#oc1bcm
44. premain()
public static void premain(String args,
Instrumentation inst) {
inst.addTransformer(
new ClassFileTransformer() {
@Override
public byte[] transform(...) {
44
#oc1bcm
45. transform()
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
// Call ByteBuddy
}
45
#oc1bcm
46. transform()
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
• className – Class name to be loaded
• classfileBuffer – Class file content
46
#oc1bcm
47. transform()
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
• Return the class file content
47
#oc1bcm
48. Demo 3
• Add outputting logs before executing
a method
48
#oc1bcm
49. 49
#oc1bcm
50. Features in ByteBuddy
• Add fields/methods
• Manipulate stack
• Generate CONDY bytecode
– Constant Dynamic – JEP 309
• Simplify Java Agent with AgentBuilder
• lots of other features
50
#oc1bcm
51. Byte Buddy's AgentBuilder
• Simplify the Java Agent
implementation
51
#oc1bcm
52. AgentBuilder
new AgentBuilder.Default()
.type(ElementMatchers.named("com.sakatakoic
hi.logging.Target"))
.transform(new AgentBuilder.Transformer() {
...
})
.installOn(inst);
52
#oc1bcm
53. AgentBuilder
1. Use AgentBuilder
– instead of ByteBuddy class
2. Use AgentBuilder.Transformer
– instead of ClassFileTransformer
3. Call installOn()
53
#oc1bcm
54. Transformer
.transform(new AgentBuilder.Transformer() {
public DynamicType.Builder<?> transform(
DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule javaModule) {
return builder
.method(ElementMatch...named("foo"))
.intercept(...);
}
})
54
#oc1bcm
55. Without -javaagent
• -javaagent option can be removed
– Call ByteBuddyAgent.install()
– Call AgentBuilder#installOnByteBuddyAgent()
• instead of installOn()
– Need to add byte-buddy-agent.jar
55
#oc1bcm
56. Without -javaagent
ByteBuddyAgent.install();
new AgentBuilder.Default()
.type(ElementMatchers.named("com.sakatakoic
hi.logging.Target"))
.transform(new AgentBuilder.Transformer() {
...
})
.installOnByteBuddyAgent();
56
#oc1bcm
57. Demo 4
• Add outputting logs without
-javaagent option
57
#oc1bcm
58. Wrap Up
• BM is fun and close to us!
– Many libraries are using
• Application Examples
– Hot Swap
– Advanced Dynamic Proxy
58
#oc1bcm