Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Bytecode Manipulation with a Java Agent and Byte Buddy

442 views

Published on

Oracle Code One 2018, Birds of a Feather (BOF) Session
Bytecode Manipulation with a Java Agent and Byte Buddy
[BOF5314]
Tuesday, Oct 23, 07:30 PM - 08:15 PM | Moscone West - Room 2005

Have you ever manipulated Java bytecode? There are several bytecode engineering libraries, and Byte Buddy is one of the easiest, and you can also use Java agents, which are related to the Instrumentation class in the java.lang.instrument API. Instrumentation is the addition of bytecode to methods. Because the changes are purely additive, a Java agent does not modify application state or behavior. With Byte Buddy and a Java agent, we can add behaviors to existing classes. This session explains what Java agents and the instrumentation API are, introduces Byte Buddy, and presents sample code that uses a Java agent and Byte Buddy to modify behavior. The presentation will be useful for those who want to start manipulating Java bytecode with Byte Buddy.

Published in: Technology
  • Be the first to comment

Bytecode Manipulation with a Java Agent and Byte Buddy

  1. 1. Koichi Sakata PONOS Corporation Bytecode Manipulation with a Java Agent and Byte Buddy
  2. 2. About Me • Koichi Sakata (阪田 浩一) • KanJava JUG Leader • Java Champion • PONOS Corporation #oc1bcm2
  3. 3. Intended Audience • Those who want to start manipulating bytecode • Not for experts #oc1bcm3
  4. 4. javac Java Bytecode #oc1bcm .java • Java Code .class • Bytecode Gets loaded by JVM 4
  5. 5. What is bytecode manipulation? • Editing bytecode in the class files –Add –Delete –Replace #oc1bcm5
  6. 6. Why do we manipulate? It's fun! #oc1bcm6
  7. 7. Not Only Fun • To change code at runtime –Generate Entity objects in Hibernate –Create mock objects in Mockito #oc1bcm7
  8. 8. Bytecode Manipulation • Hard to manipulate by hand... – A class file is a binary file – JVM has a verification process #oc1bcm8
  9. 9. Bytecode Manipulation Libraries • Byte Buddy • Byteman • Javassist • cglib • ASM • Apache Commons BCEL #oc1bcm9
  10. 10. Byte Buddy • http://bytebuddy.net • Easy-to-use API • Java 11 Support • Duke's Choice Award 2015 Winner #oc1bcm10
  11. 11. Byte Buddy • Usage in major libraries –Hibernate –Mockito –Jackson –etc. #oc1bcm11
  12. 12. Class<?> subClass = new ByteBuddy() .subclass(Object.class) .method(ElementMatchers.named("toString")) .intercept(FixedValue.value("Hello World!")) .make() .load(getClass().getClassLoader()) .getLoaded(); Hello World in Byte Buddy #oc1bcm12
  13. 13. Class<?> subClass = new ByteBuddy() .subclass(Object.class) .method(ElementMatchers.named("toString")) 1. Create a subclass of Object 2. Choose the toString() Hello World in Byte Buddy #oc1bcm13
  14. 14. .method(ElementMatchers.named("toString")) .intercept(FixedValue.value("Hello World!")) 1. Intercept toString() to return a fixed string value "Hello World" Hello World in Byte Buddy #oc1bcm14
  15. 15. .make() .load(getClass().getClassLoader()) .getLoaded(); 1. Make a new type (unloaded class) 2. Load the type 3. Get the new loaded class Hello World in Byte Buddy #oc1bcm15
  16. 16. Class<?> subClass = new ByteBuddy() .subclass(Object.class) .method(ElementMatchers.named("toString")) .intercept(FixedValue.value("Hello World!")) .make() .load(getClass().getClassLoader()) .getLoaded(); Hello World in Byte Buddy #oc1bcm16
  17. 17. Demo 1 • Run Hello World –See a generated class #oc1bcm17
  18. 18. Demo code is available • http://bit.ly/oc1bcm –github.com/jyukutyo/SampleCodeForByt eBuddySeeesion #oc1bcm18
  19. 19. Usage in Mockito • SubclassBytecodeGenerator –mockClass() #oc1bcm19
  20. 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 #oc1bcm20
  21. 21. Java Agent? • Do espionage activities in your application – Profiling – Logging – Changing the target application itself #oc1bcm21
  22. 22. Java Agent • Instrument programs running on the JVM –JSR 163: JavaTM Platform Profiling Architecture –JVM calls Java Agent method with proper timing #oc1bcm22
  23. 23. Create a Java Agent • Just define methods –premain() Method –agentmain() Method #oc1bcm23
  24. 24. Create a Java Agent • premain() Method –Gets called before main() method • agentmain() Method –Gets called when the agent is attached after JVM startup #oc1bcm24
  25. 25. With Java Agent We can run bytecode manipulation code at runtime #oc1bcm25
  26. 26. #oc1bcm26
  27. 27. premain() public static void premain(String agentArgs, Instrumentation inst) • Provides opportunity to modify classes before loading –Instrumentation class has useful methods #oc1bcm27
  28. 28. Premain-Class: com.s.logging.LoggingAgent Boot-Class-Path: byte-buddy-1.8.22.jar MANIFEST.MF #oc1bcm28
  29. 29. Demo 2 • Create a simple Java Agent #oc1bcm29
  30. 30. Another Example • Add outputting logs before executing a particular method on a particular class #oc1bcm30
  31. 31. Example 1. Manipulate bytecode before loading –Use premain() 2. Replace the class file –Use ByteBuddy –Not create a subclass #oc1bcm31
  32. 32. public class Target { public void foo() { System.out.println("foo!"); } public static void main(String[] args) { new Target().foo(); } } Target Class #oc1bcm32
  33. 33. TypePool pool = TypePool.Default.ofClassPath(); return new ByteBuddy() .rebase(pool.describe("c.Target").resolve(), ClassFileLocator.ForClassLoader .ofClassPath() ) Replace the class #oc1bcm33
  34. 34. return new ByteBuddy() .rebase(...) • Replace the original implementation with new one • Save the original implementation under a different method name Replace the class #oc1bcm34
  35. 35. Enhance Existing Method • ByteBuddy#subclass() –Override the method • ByteBuddy#redefine() –Replace the implementation • ByteBuddy#rebase() –Copy&Rename the method –Replace the implementation #oc1bcm35
  36. 36. 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() Replace the class #oc1bcm36
  37. 37. .rebase(pool.describe(...).resolve(), ClassFileLocator.ForClassLoader .ofClassPath() ) • Allow to locate a class file • ofClassPath() - Scan the running application's class path Replace the class #oc1bcm37
  38. 38. return new ByteBuddy() ... .method(ElementMatchers.named("foo")) .intercept( MethodDelegation .to(LoggingInterceptor.class) .andThen(SuperMethodCall.INSTANCE) ) Intercept the method #oc1bcm38
  39. 39. Delegation • MethodDelegation.to() –Specify a Class to delegate • A best match method is used #oc1bcm39
  40. 40. return new ByteBuddy() ... .method(ElementMatchers.named("foo")) .intercept( MethodDelegation .to(LoggingInterceptor.class) .andThen(SuperMethodCall.INSTANCE) ) Intercept the method #oc1bcm40
  41. 41. public static class LoggingInterceptor { public static void intercept( @Origin Method m) { ...println("Call " + m.getName()...); } } Interceptor Class #oc1bcm41
  42. 42. Delegated Method • Annotations for Parameter –@Origin • Reference to the method/constructor #oc1bcm42
  43. 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 #oc1bcm43
  44. 44. public static void premain(String args, Instrumentation inst) { inst.addTransformer( new ClassFileTransformer() { @Override public byte[] transform(...) { premain() #oc1bcm44
  45. 45. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { // Call ByteBuddy } transform() #oc1bcm45
  46. 46. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) • className – Class name to be loaded • classfileBuffer – Class file content transform() #oc1bcm46
  47. 47. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) • Return the class file content transform() #oc1bcm47
  48. 48. Demo 3 • Add outputting logs before executing a method #oc1bcm48
  49. 49. #oc1bcm49
  50. 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 #oc1bcm50
  51. 51. Byte Buddy's AgentBuilder • Simplify the Java Agent implementation #oc1bcm51
  52. 52. new AgentBuilder.Default() .type(ElementMatchers.named("com.sakatakoic hi.logging.Target")) .transform(new AgentBuilder.Transformer() { ... }) .installOn(inst); AgentBuilder #oc1bcm52
  53. 53. AgentBuilder 1. Use AgentBuilder –instead of ByteBuddy class 2. Use AgentBuilder.Transformer –instead of ClassFileTransformer 3. Call installOn() #oc1bcm53
  54. 54. .transform(new AgentBuilder.Transformer() { public DynamicType.Builder<?> transform( DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) { return builder .method(ElementMatch...named("foo")) .intercept(...); } }) Transformer #oc1bcm54
  55. 55. Without -javaagent • -javaagent option can be removed –Call ByteBuddyAgent.install() –Call AgentBuilder#installOnByteBuddyAgent() • instead of installOn() –Need to add byte-buddy-agent.jar #oc1bcm55
  56. 56. ByteBuddyAgent.install(); new AgentBuilder.Default() .type(ElementMatchers.named("com.sakatakoic hi.logging.Target")) .transform(new AgentBuilder.Transformer() { ... }) .installOnByteBuddyAgent(); Without -javaagent #oc1bcm56
  57. 57. Demo 4 • Add outputting logs without -javaagent option #oc1bcm57
  58. 58. Wrap Up • BM is fun and close to us! –Many libraries are using • Application Examples –Hot Swap –Advanced Dynamic Proxy #oc1bcm58

×