Skip to content

Commit b75d20e

Browse files
committed
feat: package all in one agent jar
1 parent 5b6fdb3 commit b75d20e

File tree

31 files changed

+639
-206
lines changed

31 files changed

+639
-206
lines changed

README.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,7 @@ or build the artifacts with the following commands. The build process supports J
7575

7676
`mvn clean install -DskipTests`
7777

78-
The agent jar is in the folder `arex-agent-jar/` after the build process.
79-
There will be two jar files in the folder.
80-
81-
```other
82-
arex-agent.jar
83-
arex-agent-bootstrap.jar
84-
```
78+
The `arex-agent.jar` is in the folder `arex-agent-jar/` after the build process.
8579

8680
If you wanna jar with version, build the artifacts with the following commands.
8781

arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/AgentInitializer.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,27 @@
55
import java.io.File;
66
import java.lang.instrument.Instrumentation;
77
import java.lang.reflect.Constructor;
8+
import java.util.jar.JarFile;
89

910
public class AgentInitializer {
1011

1112
private static ClassLoader classLoader;
13+
private static JarFile agentJarFile;
1214

13-
public static void initialize(Instrumentation inst, File agentFile, String agentArgs)
15+
/**
16+
* @param parentClassLoader Normally, the parentClassLoader should be ClassLoaders.AppClassLoader.
17+
*/
18+
public static void initialize(Instrumentation inst, File agentFile, String agentArgs, ClassLoader parentClassLoader)
1419
throws Exception {
1520
if (classLoader != null) {
1621
return;
1722
}
18-
23+
agentJarFile = new JarFile(agentFile);
1924
System.setProperty("arex.agent.jar.file.path", agentFile.getAbsolutePath());
2025
System.setProperty(ConfigConstants.SHADED_LOGGER_SHOW_DATE_TIME, "true");
2126
System.setProperty(ConfigConstants.SHADED_LOGGER_DATE_TIME_FORMAT, "yyyy-MM-dd HH:mm:ss:SSS");
2227
File[] extensionFiles = getExtensionJarFiles(agentFile);
23-
classLoader = createAgentClassLoader(agentFile, extensionFiles);
28+
classLoader = new AgentClassLoader(agentFile, parentClassLoader, extensionFiles);
2429
InstrumentationHolder.setAgentClassLoader(classLoader);
2530
InstrumentationHolder.setInstrumentation(inst);
2631
AgentInstaller installer = createAgentInstaller(inst, agentFile, agentArgs);
@@ -40,10 +45,6 @@ private static void addJarToLoaderSearch(File agentFile, File[] extensionFiles)
4045
}
4146
}
4247

43-
private static AgentClassLoader createAgentClassLoader(File agentFile, File[] extensionFiles) {
44-
return new AgentClassLoader(agentFile, getParentClassLoader(), extensionFiles);
45-
}
46-
4748
private static File[] getExtensionJarFiles(File jarFile) {
4849
String extensionDir = jarFile.getParent() + "/extensions/";
4950
return new File(extensionDir).listFiles(AgentInitializer::isJar);
@@ -59,7 +60,7 @@ private static AgentInstaller createAgentInstaller(Instrumentation inst, File fi
5960
return (AgentInstaller) constructor.newInstance(inst, file, agentArgs);
6061
}
6162

62-
private static ClassLoader getParentClassLoader() {
63-
return AgentInitializer.class.getClassLoader();
63+
public static JarFile getAgentJarFile() {
64+
return agentJarFile;
6465
}
6566
}

arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ private ConfigConstants() {
3333
public static final String SHADED_LOGGER_SHOW_DATE_TIME = "shaded.org.slf4j.simpleLogger.showDateTime";
3434
public static final String SHADED_LOGGER_DATE_TIME_FORMAT = "shaded.org.slf4j.simpleLogger.dateTimeFormat";
3535
public static final String COVERAGE_PACKAGES = "arex.coverage.packages";
36+
public static final String APP_CLASSLOADER_NAME = "jdk.internal.loader.ClassLoaders$AppClassLoader";
3637
}

arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/AdviceClassesCollector.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package io.arex.agent.bootstrap.util;
22

3+
import io.arex.agent.bootstrap.AgentInitializer;
34
import io.arex.agent.bootstrap.InstrumentationHolder;
45
import io.arex.agent.bootstrap.cache.AdviceInjectorCache;
56

67
import java.io.File;
78
import java.io.IOException;
89
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.nio.file.Paths;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.concurrent.ConcurrentHashMap;
916
import java.util.jar.JarEntry;
17+
import java.util.jar.JarFile;
1018
import java.util.jar.JarInputStream;
19+
1120
import net.bytebuddy.dynamic.ClassFileLocator;
1221

1322
public class AdviceClassesCollector {
@@ -18,6 +27,9 @@ public class AdviceClassesCollector {
1827
private static final String CLASS_SERIALIZER_PREFIX = "io/arex/foundation/serializer";
1928
private static final String CLASS_SUFFIX = ".class";
2029
private static final int CLASS_SUFFIX_LENGTH = CLASS_SUFFIX.length();
30+
private static final String JAR_SUFFIX = ".jar";
31+
private static final String THIRD_PARTY = "third-party";
32+
private static final Map<String, List<String>> THIRD_PARTY_NESTED_JARS_PATH_MAP = new ConcurrentHashMap<>();
2133

2234
private AdviceClassesCollector() {
2335
}
@@ -26,21 +38,31 @@ public void addJarToLoaderSearch(File file) {
2638
if (file == null) {
2739
return;
2840
}
29-
3041
boolean isExtensionJar = file.getAbsolutePath().contains("extensions");
3142
addJarToLoaderSearch(file, isExtensionJar);
3243
}
3344

3445
private void addJarToLoaderSearch(File file, boolean isExtensionJar) {
3546
try (JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(file.toPath()))) {
3647
JarEntry jarEntry;
48+
JarFile jarFile = new JarFile(file);
3749
do {
3850
jarEntry = jarInputStream.getNextJarEntry();
3951

4052
if (jarEntry != null && !jarEntry.isDirectory()) {
4153
String entryName = jarEntry.getName();
4254
if (ServiceLoader.match(entryName)) {
43-
ServiceLoader.buildCache(file, jarEntry, entryName);
55+
ServiceLoader.buildCache(jarFile, jarEntry, entryName);
56+
continue;
57+
}
58+
// ex: entryName : third-party/jackson/jackson-databind-2.13.1.jar -> secondaryDirectory: jackson
59+
if (StringUtil.startWith(entryName, THIRD_PARTY) && entryName.endsWith(JAR_SUFFIX)) {
60+
Path entryPath = Paths.get(entryName);
61+
int pathNameCount = entryPath.getNameCount();
62+
String secondaryDirectory = pathNameCount > 1 ? entryPath.getName(pathNameCount - 2).toString() : entryPath.getName(0).toString();
63+
List<String> filePathList = THIRD_PARTY_NESTED_JARS_PATH_MAP.computeIfAbsent(secondaryDirectory, k -> new ArrayList<>());
64+
filePathList.add(entryName);
65+
continue;
4466
}
4567
// exclude package io.arex.inst.runtime/extension, not class, and shaded class.
4668
boolean isFilterEntry = StringUtil.isEmpty(entryName) ||
@@ -62,7 +84,7 @@ private void addJarToLoaderSearch(File file, boolean isExtensionJar) {
6284

6385
} while (jarEntry != null);
6486
} catch (Throwable ex) {
65-
System.err.printf("add jar classes to advice failed, file: %s%n", file.getAbsolutePath());
87+
System.err.printf("add jar classes to advice failed, file: %s, exception: %s%n", file.getAbsolutePath(), ex);
6688
}
6789
}
6890

@@ -100,4 +122,19 @@ private byte[] getBytes(String name, ClassLoader loader) throws IOException {
100122
return locator.locate(name).resolve();
101123
}
102124

125+
public void addJarInThirdPartyToLoaderSearch(String jarPackageName) {
126+
try {
127+
JarFile agentJarFile = AgentInitializer.getAgentJarFile();
128+
List<String> filePathList = THIRD_PARTY_NESTED_JARS_PATH_MAP.get(jarPackageName);
129+
for (String filePath : filePathList) {
130+
JarEntry jarEntry = agentJarFile.getJarEntry(filePath);
131+
File extractNestedJar = JarUtils.extractNestedJar(agentJarFile, jarEntry, filePath);
132+
JarUtils.appendToClassLoaderSearch(Thread.currentThread().getContextClassLoader(), extractNestedJar);
133+
}
134+
} catch (Exception ex) {
135+
System.err.printf("addJarInThirdPartyToLoaderSearch failed, jarPackageName: %s%n", jarPackageName);
136+
137+
}
138+
}
139+
103140
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package io.arex.agent.bootstrap.util;
2+
3+
import io.arex.agent.bootstrap.constants.ConfigConstants;
4+
5+
import java.io.File;
6+
import java.io.FileOutputStream;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.lang.reflect.InvocationTargetException;
10+
import java.lang.reflect.Method;
11+
import java.net.URL;
12+
import java.net.URLClassLoader;
13+
import java.util.jar.JarEntry;
14+
import java.util.jar.JarFile;
15+
16+
public class JarUtils {
17+
private static Method addURL;
18+
private static final String AREX_TEMP_DIR = System.getProperty("java.io.tmpdir") + File.separator + "arex";
19+
20+
static {
21+
try {
22+
addURL = Class.forName("java.net.URLClassLoader").getDeclaredMethod("addURL", URL.class);
23+
addURL.setAccessible(true);
24+
} catch (Exception e) {
25+
System.err.println("Failed to get addURL method from URLClassLoader");
26+
}
27+
}
28+
public static File extractNestedJar(JarFile file, JarEntry entry, String entryName) throws IOException {
29+
File outputFile = createFile(AREX_TEMP_DIR + File.separator + entryName);
30+
try(InputStream inputStream = file.getInputStream(entry);
31+
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
32+
byte[] buffer = new byte[1024];
33+
int length;
34+
while ((length = inputStream.read(buffer)) > 0) {
35+
outputStream.write(buffer, 0, length);
36+
}
37+
}
38+
return outputFile;
39+
}
40+
41+
private static File createFile(String path) throws IOException {
42+
File file = new File(path);
43+
if (!file.getParentFile().exists()) {
44+
file.getParentFile().mkdirs();
45+
}
46+
boolean newFile = file.createNewFile();
47+
if (newFile) {
48+
System.out.printf("create file: %s%n", file.getAbsolutePath());
49+
}
50+
return file;
51+
}
52+
53+
/**
54+
* tomcat jdk <= 8 : classLoader : ParallelWebappClassLoader, ClassLoader.getSystemClassLoader() : Launcher$AppClassLoader
55+
* jdk > 8 : classLoader : ParallelWebappClassLoader, ClassLoader.getSystemClassLoader() : ClassLoaders$AppClassLoader
56+
*/
57+
public static void appendToClassLoaderSearch(ClassLoader classLoader, File jarFile) {
58+
try {
59+
if (classLoader instanceof URLClassLoader) {
60+
addURL.invoke(classLoader, jarFile.toURI().toURL());
61+
}
62+
63+
/*
64+
* Due to Java 8 vs java 9+ incompatibility issues
65+
* See https://stackoverflow.com/questions/46694600/java-9-compatability-issue-with-classloader-getsystemclassloader/51584718
66+
*/
67+
ClassLoader urlClassLoader = ClassLoader.getSystemClassLoader();
68+
if (!(urlClassLoader instanceof URLClassLoader)) {
69+
try (URLClassLoader tempClassLoader = new URLClassLoader(new URL[] {jarFile.toURI().toURL()}, urlClassLoader)) {
70+
addURL.invoke(tempClassLoader, jarFile.toURI().toURL());
71+
}
72+
} else {
73+
addURL.invoke(urlClassLoader, jarFile.toURI().toURL());
74+
}
75+
appendToAppClassLoaderSearch(classLoader, jarFile);
76+
} catch (Exception e) {
77+
System.err.printf("appendToClassLoaderSearch failed, classLoader: %s, jarFile: %s%n",
78+
classLoader.getClass().getName(), jarFile.getAbsolutePath());
79+
}
80+
}
81+
82+
/**
83+
* append jar jdk.internal.loader.ClassLoaders.AppClassLoader
84+
* if java >= 11 need add jvm option:--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED
85+
* @param classLoader
86+
* @param jarFile
87+
*/
88+
private static void appendToAppClassLoaderSearch(ClassLoader classLoader, File jarFile) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
89+
Class<? extends ClassLoader> loaderClass = classLoader.getClass();
90+
if (JdkUtils.isJdk11OrLater() && ConfigConstants.APP_CLASSLOADER_NAME.equalsIgnoreCase(loaderClass.getName())) {
91+
Method classPathMethod = loaderClass.getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
92+
classPathMethod.setAccessible(true);
93+
classPathMethod.invoke(classLoader, jarFile.getPath());
94+
}
95+
}
96+
}

arex-instrumentation-foundation/src/main/java/io/arex/foundation/util/JdkUtils.java renamed to arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/JdkUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.arex.foundation.util;
1+
package io.arex.agent.bootstrap.util;
22

33
public class JdkUtils {
44
public static final int JDK_11 = 11;
@@ -16,7 +16,7 @@ public static int getJavaVersion() {
1616
return Integer.parseInt(version);
1717
}
1818

19-
public static boolean isJdk11() {
19+
public static boolean isJdk11OrLater() {
2020
return getJavaVersion() >= JDK_11;
2121
}
2222
}

arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/ServiceLoader.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.arex.agent.bootstrap.util;
22

33
import java.io.BufferedReader;
4-
import java.io.File;
54
import java.io.InputStream;
65
import java.io.InputStreamReader;
76
import java.io.Reader;
@@ -48,9 +47,8 @@ public static <T> List<T> load(Class<T> service, ClassLoader loader) {
4847
* SERVICE_CACHE: key: io.arex.inst.runtime.serializer.StringSerializable
4948
* value: [io.arex.foundation.serializer.gson.GsonSerializer, io.arex.foundation.serializer.jackson.JacksonSerializer]
5049
*/
51-
public static void buildCache(File file, JarEntry jarEntry, String entryName) {
52-
try(JarFile jarFile = new JarFile(file);
53-
InputStream inputStream = jarFile.getInputStream(jarEntry)) {
50+
public static void buildCache(JarFile jarFile, JarEntry jarEntry, String entryName) {
51+
try(InputStream inputStream = jarFile.getInputStream(jarEntry)) {
5452
List<String> serviceList = readAllLines(inputStream);
5553
if (CollectionUtil.isNotEmpty(serviceList)) {
5654
String className = entryName.substring(PREFIX.length());

arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/AgentInitializerTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ static void tearDown() {
3737
@Test
3838
void testFirstInitialize() {
3939
try (MockedConstruction<AgentClassLoader> mocked = Mockito.mockConstruction(AgentClassLoader.class, (mock, context) -> Mockito.doReturn(InstrumentationInstallerTest.class).when(mock).loadClass(any()))){
40-
Assertions.assertDoesNotThrow(() -> AgentInitializer.initialize(instrumentation, zipFile, null));
40+
Assertions.assertDoesNotThrow(() -> AgentInitializer.initialize(instrumentation, zipFile, null, AgentInitializerTest.class.getClassLoader()));
4141
} catch (Throwable ex) {
4242
ex.printStackTrace();
4343
}
@@ -46,8 +46,8 @@ void testFirstInitialize() {
4646
@Test
4747
void testDoubleInitialize() {
4848
try (MockedConstruction<AgentClassLoader> mocked = Mockito.mockConstruction(AgentClassLoader.class, (mock, context) -> Mockito.doReturn(InstrumentationInstallerTest.class).when(mock).loadClass(any()))){
49-
Assertions.assertDoesNotThrow(() -> AgentInitializer.initialize(instrumentation, zipFile, null));
50-
Assertions.assertDoesNotThrow(() -> AgentInitializer.initialize(instrumentation, zipFile, null));
49+
Assertions.assertDoesNotThrow(() -> AgentInitializer.initialize(instrumentation, zipFile, null, AgentInitializerTest.class.getClassLoader()));
50+
Assertions.assertDoesNotThrow(() -> AgentInitializer.initialize(instrumentation, zipFile, null, AgentInitializerTest.class.getClassLoader()));
5151
} catch (Throwable ex) {
5252
ex.printStackTrace();
5353
}

0 commit comments

Comments
 (0)