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.

Java agents for fun and (not so much) profit

148 views

Published on

Java agents are a little-known but extremely powerful part of the Java ecosystem. Agents are able to transform existing classes at runtime, allowing scenarios such as logging and monitoring, hot reload or gathering code coverage. However, their usage presents a number of pitfalls as well.

In this talk we will present the steps of writing a java agent from scratch, indicate various common mistakes and pain points and draw conclusions on best practices.

After this talk participants will have a better understanding of the Java instrumentation API and about should / should not be done with it.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Java agents for fun and (not so much) profit

  1. 1. WritingJavaagentsforfun and(notsomuch)profit RobertMunteanu,Adobe Slides revision: 20191004-37627ef #Devoxx #JavaAgentsFun @rombert 1
  2. 2. Aboutme 2
  3. 3. Outline Quick demo Java agents primer Platform notes Integration testing Testing demo Final considerations 3
  4. 4. Quickdemo 4
  5. 5. Javaagentsprimer 5
  6. 6. JavainstrumentationAPIs java.lang.instrument Javadoc, Java SE 8 Provides services that allow Java programming language agents to instrument programs running on the JVM. 6
  7. 7. Staticagents # loaded at application startup $ java -javaagent:agent.jar -jar app.jar Premain-Class: org.example.my.Agent import java.lang.instrument.*; public class Agent { public static void premain(String args,⏎ Instrumentation inst) { inst.addTransformer(new ClassFileTransformer() { /* implementation elided */ }); } } 7
  8. 8. Dynamicagents // dynamically attached to a running JVM VirtualMachine vm = VirtualMachine.attach(vmPid); vm.loadAgent(agentFilePath); vm.detach(); Agent-Class: org.example.my.Agent import java.lang.instrument.*; public class Agent { public static void agentmain(String args,⏎ Instrumentation inst) { inst.addTransformer(new ClassFileTransformer() { /* implementation elided */ }); } } 8
  9. 9. Classtransformation public interface ClassFileTransformer { byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException; } 9
  10. 10. Javabytecode public static void main(java.lang.String[]); Code: 0: getstatic #16⏎ // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #22⏎ // String Hello, world 5: invokevirtual #24⏎ // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return 10
  11. 11. Bytecodegenerationlibraries Apache Commons BCEL ByteBuddy CGLib Javassist ObjectWeb ASM 11
  12. 12. BytecodegenerationwithJavassist public class SimpleTransformer implements ClassFileTransformer { public byte[] transform(...) throws ... { ClassPool classPool = ClassPool.getDefault(); CtMethod method = classPool.getMethod(⏎ Descriptor.toJavaName(className), "main"); method.insertAfter("System.out.println " + ⏎ "("... hello yourself!...");"); byte[] newClass = method.getDeclaringClass()⏎ .toBytecode(); method.getDeclaringClass().detach(); return newClass; } } 12
  13. 13. Agentexamples 1. Code coverage 2. Monitoring (logging, tracing, error reporting ...) 3. Profiling 4. Debugging 5. Mocking libraries 6. Code reload/Hot swap 13
  14. 14. Platformnotes 14
  15. 15. Mindtheclassloader - ClassPool defaultPool = ClassPool.getDefault(); - CtClass cc = defaultPool.get(⏎ - Descriptor.toJavaName(className)); + ClassPool classPool = new ClassPool(true); + classPool.appendClassPath(new LoaderClassPath(loader)); + classPool.insertClassPath(new ByteArrayClassPath(⏎ + Descriptor.toJavaName(className), classfileBuffer)); 15
  16. 16. Spring-AOP @Aspect public class Aspects { @Pointcut("execution(* service(..))") public void webRequest() {} } @Aspect public class LoggerHandler { @Before("com.myapp.Aspects.webRequest()") public void logWebRequest(Request request) { System.out.println("Handling web request " + request); } } 16
  17. 17. Spring-AOP  Simplified deployment - Spring beans  Simplified programming model  Powerful DSL to express interception targets  Only intercepts calls to Spring beans  Spring-only solution 17
  18. 18. OSGi-WeavingHooks @Component public class SimpleWeavingHook implements WeavingHook { public void weave(WovenClass wovenClass) { if ( !wovenClass.getClassName().equals(CLASS_TO_WEAVE) ) return; try { wovenClass.setBytes(weave0(wovenClass)); } catch (NotFoundException | CannotCompileException | IOException e) { LOGGER.warn("Failed weaving class {}", wovenClass.getClassName(), e); } } } 18
  19. 19. OSGi-WeavingHooks  Simplified deployment - OSGi bundle  Simple registration via OSGi whiteboard  Handles updated bundle package imports  OSGi-only solution 19
  20. 20. Integrationtesting 20
  21. 21. Thechallenges Java agents... must be packaged as a Jar file, with a specific manifest not trivially attached to the current process usually a one-way deal, no support for rolling back class changes 21
  22. 22. Asolution Build a testing harness that... launch Java process with custom agents attached require separate communication channel with java agent no out-of-the-box support for code coverage and other tools 22
  23. 23. Bootstrappingthetest // 1. which java? String javaHome = System.getProperty("java.home"); Path javaExe = Paths.get(javaHome, "bin", "java"); // 2. which jar? String ja = findAgentJar(); // 3. which classpath? String classPath = buildClassPath(); // 4. launch ProcessBuilder pb = new ProcessBuilder( javaExe.toString(), "-javaagent:" + ja, "-cp", classPath, TestApplication.class.getName() ); 23
  24. 24. Bootstrappingthetest // 5. verifying side effects Path stdout = Paths.get("target", "stdout.txt"); Path stderr = Paths.get("target", "stderr.txt"); pb.redirectInput(Redirect.INHERIT); pb.redirectOutput(stdout.toFile()); pb.redirectError(stderr.toFile()); // 6. adding code coverage ProcessBuilder pb = new ProcessBuilder( javaExe.toString(), "-javaagent:" + codeCoverageAgent, "-javaagent:" + ja, "-cp", classPath, TestApplication.class.getName() ); 24
  25. 25. OSGiintegrationtesting // note - must run in a forked container @RunWith(PaxExam.class) public class OsgiIT { @Configuration public Option[] config() throws IOException { return options( junitBundles(), ⏎ vmOption("-javaagent:" + findAgentJar()) ); } @Test public void callTimesOut() throws IOException { assertTrue(agentReallyWorks()); } } 25
  26. 26. Testingdemo 26
  27. 27. Finalconsiderations 27
  28. 28. WhentouseJavaagents 1. Code outside of your control 2. No better platform facilities exist 3. (Usually) Cross-cutting concerns 28
  29. 29. Resources https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/pa summary.html https://www.javassist.org/ https://sling.apache.org/documentation/bundles/connection-timeo agent.html https://site.mockito.org/ https://byteman.jboss.org/ 29

×