Skip to content

Commit a9e1e14

Browse files
committed
Add MongoDB 5.x plugin and extend 4.x to cover 5.0-5.1
MongoDB 5.2+ replaced MongoClientDelegate with package-private MongoClusterImpl. New mongodb-5.x-plugin module with: - MongoClusterImplInstrumentation: intercept constructor (Cluster at arg[1]) and getOperationExecutor() to propagate remotePeer - MongoClusterOperationExecutorInstrumentation: intercept inner class OperationExecutorImpl constructor and execute() methods Key challenge: OperationExecutorImpl is created inside MongoClusterImpl's constructor before onConstruct fires. Solved by calling getOperationExecutor() via setAccessible reflection in onConstruct. Note: Same-package helper classes (@InternalAccessor approach) do NOT work because agent and application use different classloaders — Java treats them as different runtime packages even with identical package names. Locally verified: 5.0.1, 5.1.4 (4.x plugin), 5.2.0, 5.5.1 (5.x plugin) all passed.
1 parent 28f1a11 commit a9e1e14

File tree

5 files changed

+57
-1
lines changed

5 files changed

+57
-1
lines changed

.claude/skills/new-plugin/SKILL.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ Pick interception points based on these principles:
5757
**Principle 1: Data accessibility without reflection.**
5858
Choose methods where the information you need (peer address, operation name, request/response details, headers for inject/extract) is directly available as method arguments, return values, or accessible through the `this` object's public API. **Never use reflection to read private fields.** If the data is not accessible at one method, look at a different point in the execution flow.
5959

60+
If the target class is **package-private** (e.g., `final class` without `public`), you cannot import or cast to it. **Same-package helper classes do NOT work** because the agent and application use different classloaders — Java treats them as different runtime packages even with the same package name (`IllegalAccessError`). Use `setAccessible` reflection to call public methods:
61+
```java
62+
try {
63+
java.lang.reflect.Method method = objInst.getClass().getMethod("publicMethodName");
64+
method.setAccessible(true); // Required for package-private class
65+
Object result = method.invoke(objInst);
66+
} catch (Exception e) {
67+
LOGGER.warn("Failed to access method", e);
68+
}
69+
```
70+
6071
**Principle 2: Use `EnhancedInstance` dynamic field to propagate context inside the library.**
6172
This is the primary mechanism for passing data between interception points. The agent adds a dynamic field to every enhanced class via `EnhancedInstance`. Use it to:
6273
- Store server address (peer) at connection/client creation time, retrieve it at command execution time

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ packages/
2222
/test/jacoco/classes
2323
/test/jacoco/*.exec
2424
test/jacoco
25+
.claude/settings.local.json

apm-sniffer/apm-sdk-plugin/CLAUDE.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,25 @@ public class MyPluginConfig {
145145
```
146146
Config key becomes: `plugin.myplugin.some_setting`
147147

148+
### Accessing Package-Private Classes
149+
150+
When a plugin needs to call methods on a **package-private** class in the target library (e.g., `MongoClusterImpl` which is `final class` without `public`), you cannot import or cast to it from the plugin package.
151+
152+
**Same-package helper classes do NOT work** because the agent and application use different classloaders. Even though the package names match, Java considers them different runtime packages, so package-private access is denied (`IllegalAccessError`).
153+
154+
**Solution: use `setAccessible` reflection** to call public methods on package-private classes:
155+
```java
156+
try {
157+
java.lang.reflect.Method method = objInst.getClass().getMethod("publicMethodName");
158+
method.setAccessible(true); // Required for package-private class
159+
Object result = method.invoke(objInst);
160+
} catch (Exception e) {
161+
LOGGER.warn("Failed to access method", e);
162+
}
163+
```
164+
165+
**When to use:** Only when the target class is package-private and you need to call its public methods. Prefer normal casting when the class is public.
166+
148167
### Dependency Management
149168

150169
**Plugin dependencies must use `provided` scope:**

apm-sniffer/apm-sdk-plugin/mongodb-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v5/interceptor/MongoClusterImplConstructorInterceptor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
4848

4949
// The OperationExecutorImpl is created INSIDE this constructor (before onConstruct fires),
5050
// so its constructor interceptor couldn't read the peer yet. Set it now.
51-
// MongoClusterImpl is package-private, access getOperationExecutor via reflection.
51+
// MongoClusterImpl is package-private and loaded by application classloader.
52+
// Same-package helpers from agent classloader cannot access it (different runtime packages).
53+
// Use setAccessible reflection to call getOperationExecutor().
5254
try {
5355
java.lang.reflect.Method getExecutor = objInst.getClass().getMethod("getOperationExecutor");
5456
getExecutor.setAccessible(true);

docs/en/setup/service-agent/java-agent/Java-Plugin-Development-Guide.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,3 +621,26 @@ Please follow these steps:
621621
1. Send a pull request and ask for review.
622622
1. The plugin committers will approve your plugins, plugin CI-with-IT, e2e, and the plugin tests will be passed.
623623
1. The plugin is accepted by SkyWalking.
624+
625+
### Accessing package-private target classes
626+
627+
When a plugin needs to call methods on a **package-private** class in the target library (e.g., `MongoClusterImpl` which is `final class` without `public`), you cannot import or cast to it from the plugin's `org.apache.skywalking` package.
628+
629+
**Important:** Same-package helper classes do NOT work because the agent and application use different classloaders. Java treats them as different runtime packages even with identical package names, so package-private access is denied with `IllegalAccessError`.
630+
631+
**Solution:** Use `setAccessible` reflection to call public methods on package-private classes:
632+
633+
```java
634+
try {
635+
java.lang.reflect.Method method = objInst.getClass().getMethod("publicMethodName");
636+
method.setAccessible(true); // Required: class is package-private
637+
Object result = method.invoke(objInst);
638+
if (result instanceof EnhancedInstance) {
639+
((EnhancedInstance) result).setSkyWalkingDynamicField(value);
640+
}
641+
} catch (Exception e) {
642+
logger.warn("Failed to access method", e);
643+
}
644+
```
645+
646+
**When to use:** Only when the target class is package-private and you need to call its public methods. Prefer normal casting in interceptors when the class is public.

0 commit comments

Comments
 (0)