Skip to content

Commit 6b00c91

Browse files
committed
feat: support jar in jar
1 parent 9220c18 commit 6b00c91

File tree

26 files changed

+409
-147
lines changed

26 files changed

+409
-147
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class AgentInitializer {
1010

1111
private static ClassLoader classLoader;
1212

13-
public static void initialize(Instrumentation inst, File agentFile, String agentArgs)
13+
public static void initialize(Instrumentation inst, File agentFile, String agentArgs, ClassLoader parentClassLoader)
1414
throws Exception {
1515
if (classLoader != null) {
1616
return;
@@ -20,7 +20,7 @@ public static void initialize(Instrumentation inst, File agentFile, String agent
2020
System.setProperty(ConfigConstants.SHADED_LOGGER_SHOW_DATE_TIME, "true");
2121
System.setProperty(ConfigConstants.SHADED_LOGGER_DATE_TIME_FORMAT, "yyyy-MM-dd HH:mm:ss:SSS");
2222
File[] extensionFiles = getExtensionJarFiles(agentFile);
23-
classLoader = createAgentClassLoader(agentFile, extensionFiles);
23+
classLoader = new AgentClassLoader(agentFile, parentClassLoader, extensionFiles);
2424
InstrumentationHolder.setAgentClassLoader(classLoader);
2525
InstrumentationHolder.setInstrumentation(inst);
2626
AgentInstaller installer = createAgentInstaller(inst, agentFile, agentArgs);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ 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 RUNTIME_JAR_FILE_PATH = "arex.runtime.jar.file.path";
37+
public static final String APP_CLASSLOADER_NAME = "jdk.internal.loader.ClassLoaders$AppClassLoader";
3638
}

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

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55

66
import java.io.File;
77
import java.io.IOException;
8+
import java.lang.reflect.Method;
9+
import java.net.URL;
10+
import java.net.URLClassLoader;
811
import java.nio.file.Files;
912
import java.util.jar.JarEntry;
13+
import java.util.jar.JarFile;
1014
import java.util.jar.JarInputStream;
15+
16+
import io.arex.agent.bootstrap.constants.ConfigConstants;
1117
import net.bytebuddy.dynamic.ClassFileLocator;
1218

1319
public class AdviceClassesCollector {
@@ -34,13 +40,14 @@ public void addJarToLoaderSearch(File file) {
3440
private void addJarToLoaderSearch(File file, boolean isExtensionJar) {
3541
try (JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(file.toPath()))) {
3642
JarEntry jarEntry;
43+
JarFile jarFile = new JarFile(file);
3744
do {
3845
jarEntry = jarInputStream.getNextJarEntry();
3946

4047
if (jarEntry != null && !jarEntry.isDirectory()) {
4148
String entryName = jarEntry.getName();
4249
if (ServiceLoader.match(entryName)) {
43-
ServiceLoader.buildCache(file, jarEntry, entryName);
50+
ServiceLoader.buildCache(jarFile, jarEntry, entryName);
4451
}
4552
// exclude package io.arex.inst.runtime/extension, not class, and shaded class.
4653
boolean isFilterEntry = StringUtil.isEmpty(entryName) ||
@@ -62,7 +69,7 @@ private void addJarToLoaderSearch(File file, boolean isExtensionJar) {
6269

6370
} while (jarEntry != null);
6471
} catch (Throwable ex) {
65-
System.err.printf("add jar classes to advice failed, file: %s%n", file.getAbsolutePath());
72+
System.err.printf("add jar classes to advice failed, file: %s%n ex: %s", file.getAbsolutePath(), ex);
6673
}
6774
}
6875

@@ -95,6 +102,64 @@ private void addClassToInjectorCache(String adviceClassName) {
95102
}
96103
}
97104

105+
private static void appendToClassLoaderSearch(ClassLoader classLoader, File jarFile) {
106+
try {
107+
Method addURL = Class.forName("java.net.URLClassLoader").getDeclaredMethod("addURL", URL.class);
108+
addURL.setAccessible(true);
109+
110+
if (classLoader instanceof URLClassLoader) {
111+
addURL.invoke(classLoader, jarFile.toURI().toURL());
112+
}
113+
114+
/*
115+
* Due to Java 8 vs java 9+ incompatibility issues
116+
* See https://stackoverflow.com/questions/46694600/java-9-compatability-issue-with-classloader-getsystemclassloader/51584718
117+
*/
118+
ClassLoader urlClassLoader = ClassLoader.getSystemClassLoader();
119+
if (!(urlClassLoader instanceof URLClassLoader)) {
120+
urlClassLoader = new URLClassLoader(new URL[] {jarFile.toURI().toURL()}, urlClassLoader);
121+
}
122+
addURL.invoke(urlClassLoader, jarFile.toURI().toURL());
123+
124+
// append jdk.internal.AppClassLoader since jdk11
125+
appendToAppClassLoaderSearch(classLoader, jarFile);
126+
} catch (Throwable e) {
127+
System.err.printf("appendToClassLoaderSearch failed, classLoader: %s, jarFile: %s%n",
128+
classLoader.getClass().getName(), jarFile.getAbsolutePath());
129+
}
130+
}
131+
132+
/**
133+
* append jar jdk.internal.loader.ClassLoaders.AppClassLoader
134+
* if java >= 11 need add jvm option:--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED
135+
* @param classLoader
136+
* @param jarFile
137+
*/
138+
private static void appendToAppClassLoaderSearch(ClassLoader classLoader, File jarFile) {
139+
try {
140+
Class<? extends ClassLoader> loaderClass = classLoader.getClass();
141+
if (JdkUtils.isJdk11() && ConfigConstants.APP_CLASSLOADER_NAME.equalsIgnoreCase(loaderClass.getName())) {
142+
Method classPathMethod = loaderClass.getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
143+
classPathMethod.setAccessible(true);
144+
classPathMethod.invoke(classLoader, jarFile.getPath());
145+
}
146+
} catch (Throwable e) {
147+
System.err.printf("appendToAppClassLoaderSearch failed, classLoader: %s, jarFile: %s%n",
148+
classLoader.getClass().getName(), jarFile.getAbsolutePath());
149+
}
150+
}
151+
152+
public void addJacksonJarToLoaderSearch() {
153+
String runTimePath = System.getProperty(ConfigConstants.RUNTIME_JAR_FILE_PATH);
154+
if (StringUtil.isEmpty(runTimePath)) {
155+
return;
156+
}
157+
String[] split = StringUtil.split(runTimePath, ';');
158+
for (String entry : split) {
159+
appendToClassLoaderSearch(Thread.currentThread().getContextClassLoader(), new File(entry));
160+
}
161+
}
162+
98163
private byte[] getBytes(String name, ClassLoader loader) throws IOException {
99164
ClassFileLocator locator = ClassFileLocator.ForClassLoader.of(loader);
100165
return locator.locate(name).resolve();

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: 1 addition & 1 deletion
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;

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,8 @@ public static <T> List<T> load(Class<T> service, ClassLoader loader) {
4848
* SERVICE_CACHE: key: io.arex.inst.runtime.serializer.StringSerializable
4949
* value: [io.arex.foundation.serializer.gson.GsonSerializer, io.arex.foundation.serializer.jackson.JacksonSerializer]
5050
*/
51-
public static void buildCache(File file, JarEntry jarEntry, String entryName) {
52-
try(JarFile jarFile = new JarFile(file);
53-
InputStream inputStream = jarFile.getInputStream(jarEntry)) {
51+
public static void buildCache(JarFile jarFile, JarEntry jarEntry, String entryName) {
52+
try(InputStream inputStream = jarFile.getInputStream(jarEntry)) {
5453
List<String> serviceList = readAllLines(inputStream);
5554
if (CollectionUtil.isNotEmpty(serviceList)) {
5655
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
}

arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/AdviceClassesCollectorTest.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
import java.lang.reflect.Method;
1010
import java.net.URL;
1111
import java.net.URLClassLoader;
12-
import java.nio.file.Path;
13-
import java.nio.file.Paths;
1412

1513
import org.junit.jupiter.api.AfterAll;
1614
import org.junit.jupiter.api.BeforeAll;
@@ -79,11 +77,4 @@ void testNull() {
7977
assertDoesNotThrow(() -> AdviceClassesCollector.INSTANCE.addJarToLoaderSearch(null));
8078
assertDoesNotThrow(() -> AdviceClassesCollector.INSTANCE.addClassToLoaderSearch(null));
8179
}
82-
83-
@Test
84-
public void test() {
85-
final File file = new File("D:\\Users\\yongwuhe\\IdeaProjects\\arex-agent-java\\arex-agent-jar\\arex-agent-0.3.6.jar");
86-
String enrtyName = "META-INF/services/com.fasterxml.jackson.core.JsonFactory";
87-
final Path path = Paths.get(file.getAbsolutePath() + "!/" + enrtyName);
88-
}
89-
}
80+
}

arex-instrumentation-foundation/src/test/java/io/arex/foundation/util/JdkUtilsTest.java renamed to arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/JdkUtilsTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
package io.arex.foundation.util;
2-
3-
import static org.junit.jupiter.api.Assertions.*;
1+
package io.arex.agent.bootstrap.util;
42

53
import org.junit.jupiter.api.AfterEach;
64
import org.junit.jupiter.api.BeforeEach;
75
import org.junit.jupiter.api.Test;
86

9-
class JdkUtilsTest {
7+
import static org.junit.jupiter.api.Assertions.*;
108

9+
class JdkUtilsTest {
1110
@BeforeEach
1211
void setUp() {
1312
}
@@ -25,4 +24,5 @@ void getJavaVersion() {
2524
void isJdk11() {
2625
assertInstanceOf(Boolean.class, JdkUtils.isJdk11());
2726
}
28-
}
27+
28+
}

arex-agent-core/src/main/java/io/arex/agent/instrumentation/BaseAgentInstaller.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.arex.agent.bootstrap.util.FileUtils;
77
import io.arex.foundation.config.ConfigManager;
88
import io.arex.foundation.healthy.HealthManager;
9+
import io.arex.foundation.serializer.gson.GsonSerializer;
910
import io.arex.foundation.serializer.jackson.JacksonSerializer;
1011
import io.arex.foundation.services.ConfigService;
1112
import io.arex.foundation.services.DataCollectorService;
@@ -113,16 +114,8 @@ private void timedReportStatus() {
113114
private void initDependentComponents() {
114115
TraceContextManager.init(NetUtils.getIpAddress());
115116
RecordLimiter.init(HealthManager::acquire);
116-
initSerializer();
117117
initDataCollector();
118118
}
119-
120-
/**
121-
* add class to user loader search. ex: ParallelWebappClassLoader
122-
*/
123-
private void initSerializer() {
124-
Serializer.builder(JacksonSerializer.INSTANCE).build();
125-
}
126119
private void initDataCollector() {
127120
DataCollector collector = DataCollectorService.INSTANCE;
128121
if (ConfigManager.INSTANCE.isLocalStorage()) {

arex-agent/pom.xml

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,24 @@
214214
</dependency>
215215
</dependencies>
216216

217+
<profiles>
218+
<profile>
219+
<id>default</id>
220+
<properties>
221+
<xml-path>src/main/assembly/assembly.xml</xml-path>
222+
</properties>
223+
<activation>
224+
<activeByDefault>true</activeByDefault>
225+
</activation>
226+
</profile>
227+
<profile>
228+
<id>jar-with-version</id>
229+
<properties>
230+
<xml-path>src/main/assembly/assembly-with-version.xml</xml-path>
231+
</properties>
232+
</profile>
233+
</profiles>
234+
217235
<build>
218236
<plugins>
219237
<plugin>
@@ -245,6 +263,43 @@
245263
</configuration>
246264
</plugin>
247265

266+
<plugin>
267+
<groupId>org.apache.maven.plugins</groupId>
268+
<artifactId>maven-assembly-plugin</artifactId>
269+
<version>3.3.0</version>
270+
<configuration>
271+
<finalName>arex</finalName>
272+
<descriptors>
273+
<descriptor>${xml-path}</descriptor>
274+
</descriptors>
275+
<archive>
276+
<manifest>
277+
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
278+
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
279+
</manifest>
280+
<manifestEntries>
281+
<Premain-Class>io.arex.agent.ArexJavaAgent</Premain-Class>
282+
<Agent-Class>io.arex.agent.ArexJavaAgent</Agent-Class>
283+
<Can-Redefine-Classes>true</Can-Redefine-Classes>
284+
<Can-Retransform-Classes>true</Can-Retransform-Classes>
285+
<Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
286+
<Build-Time>${maven.build.timestamp}</Build-Time>
287+
<Built-By>arextest.com</Built-By>
288+
</manifestEntries>
289+
</archive>
290+
</configuration>
291+
292+
<executions>
293+
<execution>
294+
<id>make-assembly</id>
295+
<phase>package</phase>
296+
<goals>
297+
<goal>single</goal>
298+
</goals>
299+
</execution>
300+
</executions>
301+
</plugin>
302+
248303
<plugin>
249304
<groupId>org.apache.maven.plugins</groupId>
250305
<artifactId>maven-shade-plugin</artifactId>
@@ -282,7 +337,6 @@
282337
<include>net.bytebuddy:byte-buddy</include>
283338
<include>org.slf4j:slf4j-simple</include>
284339
<include>io.arex:**</include>
285-
<include>com.fasterxml.jackson.core:**</include>
286340
</includes>
287341
</artifactSet>
288342
</configuration>
@@ -304,7 +358,6 @@
304358
</delete>
305359
<copy todir="../arex-agent-jar">
306360
<fileset dir="../arex-agent/target/" includes="arex-agent*.jar" />
307-
<fileset dir="../arex-agent-bootstrap/target/" includes="arex-agent-bootstrap*.jar" />
308361
</copy>
309362
</target>
310363
</configuration>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
4+
<id>agent-${project.version}</id>
5+
<formats>
6+
<format>jar</format>
7+
</formats>
8+
<includeBaseDirectory>false</includeBaseDirectory>
9+
<fileSets>
10+
<fileSet>
11+
<directory>${project.build.directory}/classes</directory>
12+
<outputDirectory>/</outputDirectory>
13+
<includes>
14+
<include>**</include>
15+
</includes>
16+
</fileSet>
17+
</fileSets>
18+
<dependencySets>
19+
<dependencySet>
20+
<outputDirectory>/</outputDirectory>
21+
<includes>
22+
<include>com.fasterxml.jackson.core:**</include>
23+
<include>io.arex:arex-agent-bootstrap</include>
24+
</includes>
25+
<useProjectArtifact>false</useProjectArtifact>
26+
<unpack>false</unpack>
27+
<scope>runtime</scope>
28+
</dependencySet>
29+
</dependencySets>
30+
</assembly>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
4+
<id>agent</id>
5+
<formats>
6+
<format>jar</format>
7+
</formats>
8+
<includeBaseDirectory>false</includeBaseDirectory>
9+
<fileSets>
10+
<fileSet>
11+
<directory>${project.build.directory}/classes</directory>
12+
<outputDirectory>/</outputDirectory>
13+
<includes>
14+
<include>**</include>
15+
</includes>
16+
</fileSet>
17+
</fileSets>
18+
<dependencySets>
19+
<dependencySet>
20+
<outputDirectory>/</outputDirectory>
21+
<includes>
22+
<include>com.fasterxml.jackson.core:**</include>
23+
</includes>
24+
<useProjectArtifact>false</useProjectArtifact>
25+
<unpack>false</unpack>
26+
<scope>runtime</scope>
27+
</dependencySet>
28+
<dependencySet>
29+
<outputDirectory>/</outputDirectory>
30+
<includes>
31+
<include>io.arex:arex-agent-bootstrap</include>
32+
</includes>
33+
<useProjectArtifact>false</useProjectArtifact>
34+
<unpack>false</unpack>
35+
<scope>runtime</scope>
36+
<outputFileNameMapping>arex-agent-bootstrap.jar</outputFileNameMapping>
37+
</dependencySet>
38+
</dependencySets>
39+
</assembly>

0 commit comments

Comments
 (0)