1 )/. ) $...ÊÊq 2#$ #% 1 /-$)"% 1 *( 4./ ( " / -*+ -/4Ä % 1 #*( Å /#% 1 3 /#. "...
TRANSCRIPT
Will it blend? Java agents and OSGiSlides revision: 20190923-ea7c311
1
Welcome
2
About me
3
Outline
Quick demo
Java agents primer
Usage scenarios
OSGi integration
Integration testing
Testing demo
4
Quick demo
5
Java agents primer
6
Java instrumentation APIs
java.lang.instrument Javadoc, Java SE 8
Provides services that allow Java programming
language agents to instrument programs running
on the JVM.
7
Static agents
# 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 */
});
}
}
8
Dynamic agents
// 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 */
});
}
}
9
Class transformation
public interface ClassFileTransformer {
byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException;
}
10
Java bytecodepublic 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;
8: return
11
Bytecode generation libraries
Apache Commons BCEL
ByteBuddy
CGLib
Javassist
ObjectWeb ASM
12
Bytecode generation with Javassist
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;
}
13
Usage scenarios
14
When to use agents
1. Code outside of your control
2. No better platform facilities exist
3. (Usually) Cross-cutting concerns
15
Agent examples
1. Monitoring (logging, tracing, error reporting ...)
2. Profiling
3. Debugging
4. Mocking libraries
5. Code reload/Hot swap
16
OSGi integration
17
Mind the classloader
- 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));
18
Carefully manage dependencies
New requirements typically fail since they are not
defined by the bundle
Bundles can be processed at build-time
Patching Import-Package
DynamicImport-Package: *
19
OSGi alternatives - Weaving Hooks
Simplified deployment - OSGi bundle
Simple registration via OSGi whiteboard
Handles updated bundle package imports
OSGi-only solution
20
Integration testing
21
Packaging challenges
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
22
Custom test launchers
"unit" tests
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
23
Bootstrapping the test
// 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()
);
24
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());
25
Adding code coverage
ProcessBuilder pb = new ProcessBuilder(
javaExe.toString(),
"-javaagent:" + codeCoverageAgent,
"-javaagent:" + ja,
"-cp",
classPath,
TestApplication.class.getName()
);
26
OSGi integration testing
// note - must run in a forked container
@RunWith(PaxExam.class)
public class OsgiIT {
@Configuration
public Option[] config() throws IOException {
return options( junitBundles(), ⏎ vmOption("-javaagent:" + agentJar) );
}
@Test
public void callTimesOut() throws IOException {
assertTrue(agentReallyWorks());
}
}
27
Testing demo
28
Resources
https://docs.oracle.com/javase/8/docs/api/java/lang/instrum
summary.html
https://www.javassist.org/
https://sling.apache.org/documentation/bundles/connectio
agent.html
29
30