A comprehensive bytecode enhancement solution for solving configuration conflicts caused by static variables in third-party SDKs.
English | 简体中文
In real-world development, we often encounter third-party SDKs whose source code cannot be modified. These SDKs use static variables to store configuration due to historical reasons, making it impossible to create multiple instances with different configurations in the same JVM process.
Scenario 1: Multi-Tenant SaaS Systems
- Different tenants need different third-party service configurations
- Same SDK needs to connect to different service endpoints
Scenario 2: A/B Testing
- Testing different service providers simultaneously
- Need to switch configurations dynamically at runtime
Scenario 3: Dev/Test Environment Isolation
- Development and test environments use different configurations
- Need to simulate multiple environments in the same process
Many third-party SDKs have similar designs(source code cannot be modified):
// Third-party SDK code(cannot modify)
public class MessageClient {
private static String serverUrl; // ❌ static variable causes global sharing
static {
SdkConfig.loadConfig();
serverUrl = SdkConfig.getServerUrl(); // Load from config file
}
public MessageClient() {
// Use default configuration
}
public MessageClient(String serverUrl) {
MessageClient.serverUrl = serverUrl; // ❌ Overwrite global config
}
public void sendMessage(String message) {
// Use serverUrl to send message
System.out.println("Send to:" + serverUrl);
}
}Problem Demonstration:
MessageClient client1 = new MessageClient("http://server1.com");
MessageClient client2 = new MessageClient("http://server2.com");
client1.sendMessage("Message1"); // ❌ Actually sends to server2.com
client2.sendMessage("Message2"); // ✅ Sends to server2.com
// Problem: All instances share the same static serverUrl
// Both client1 and client2 use server2.comThis project provides 5 different approaches to solve static variable isolation:
| Solution | Implementation | Use Case |
|---|---|---|
| ASM Agent | Direct bytecode modification | High performance, production |
| Javassist Agent | Bytecode API modification | Balance of performance and maintainability |
| ByteBuddy Agent | Modern bytecode library | Modern apps, easy maintenance |
| Wrapper Pattern | ThreadLocal wrapper | Quick solution, testing |
| ClassLoader Isolation | Independent classloader | Strict isolation requirements |
Also provides Spring Boot Starter for easy integration.
bytecode-enhancement/
├── message-sdk/ # 模拟的第三方SDK(带static配置问题)
├── notification-client/ # 业务封装层
├── agent-asm/ # ASM字节码增强方案
├── agent-javassist/ # Javassist字节码增强方案
├── agent-bytebuddy/ # ByteBuddy字节码增强方案
├── agent-wrapper/ # Wrapper模式解决方案
├── agent-classloader/ # ClassLoader隔离方案
├── agent-spring-boot-starter/ # Spring Boot自动配置
└── example-app/ # 示例应用(展示实际使用)
# Use ASM Agent
java -javaagent:agent-asm-1.0.0-SNAPSHOT.jar -jar your-app.jar
# Or use Javassist Agent
java -javaagent:agent-javassist-1.0.0-SNAPSHOT.jar -jar your-app.jar1. Add Dependency
<dependency>
<groupId>com.example</groupId>
<artifactId>agent-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>2. Configuration(application.yml)
bytecode:
enhancement:
enabled: true # Enable bytecode enhancement
strategy: asm # Strategy: asm/javassist/bytebuddy3. Usage Example
// Direct use, bytecode already enhanced
MessageClient client1 = new MessageClient("http://server1.com");
MessageClient client2 = new MessageClient("http://server2.com");
client1.sendMessage("Message1"); // ✅ Sends to server1.com
client2.sendMessage("Message2"); // ✅ Sends to server2.com
// Each object uses independent configuration| Solution | Performance | Complexity | Pros | Cons | Use Case |
|---|---|---|---|---|---|
| ASM | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Best performance, full control | Complex, needs JVM knowledge | High-performance production |
| Javassist | ⭐⭐⭐⭐ | ⭐⭐⭐ | Simpler than ASM, good performance | Still needs bytecode knowledge | Balance of performance and maintainability |
| ByteBuddy | ⭐⭐⭐ | ⭐⭐ | Modern API, type-safe | Slight performance overhead | Modern apps, easy maintenance |
| Wrapper | ⭐⭐⭐⭐ | ⭐ | No bytecode needed, simple | Manual context management | Quick solution |
| ClassLoader | ⭐⭐ | ⭐⭐⭐⭐ | Complete isolation | High memory overhead, complex | Strict isolation |
Features: Direct bytecode modification, convert static fields to instance variables
# Supports two modes
java -javaagent:agent-asm.jar=instance -jar app.jar # Instance variable mode(default)
java -javaagent:agent-asm.jar=threadlocal -jar app.jar # ThreadLocal modePrinciple: Automatically converts static fields in MessageClient to instance variables or ThreadLocal during class loading.
Features: Modify bytecode using Javassist API, simpler than ASM
java -javaagent:agent-javassist.jar -jar app.jarFeatures: Modern ByteBuddy API, type-safe
java -javaagent:agent-bytebuddy.jar -jar app.jarFeatures: ThreadLocal wrapper, no bytecode modification needed
// Register configuration
MessageClientWrapper.registerTenant("tenant-a", "https://api-a.com", "key-a");
// Usage
MessageClientWrapper.setTenantContext("tenant-a");
try {
MessageClient client = MessageClientWrapper.createClient();
client.sendMessage("Message");
} finally {
MessageClientWrapper.clearTenantContext();
}Features: Each tenant uses independent ClassLoader, complete isolation
URL[] urls = new URL[] { /* SDK jar URLs */ };
TenantClassLoader loader = TenantClassLoader.getTenantClassLoader("tenant-a", urls);
TenantClassLoader.setTenantContext("tenant-a");
try {
Class<?> clientClass = loader.loadClass("com.example.sdk.message.MessageClient");
Object client = clientClass.newInstance();
// Use reflection to call methods
} finally {
TenantClassLoader.clearTenantContext();
}# Build all modules
mvn clean install
# Build specific module
cd agent-asm
mvn clean packageEach module contains complete test cases:
# Run all tests
mvn test
# Test specific module
mvn test -pl agent-asm
mvn test -pl agent-javassist
# Run problem demonstration
mvn test -pl message-sdk -Dtest=StaticVariableProblemDemoASM Implementation:
- Direct bytecode instruction modification
- Convert
GETSTATICtoGETFIELD - Convert
PUTSTATICtoPUTFIELD - Remove
ACC_STATICmodifier from fields
Javassist Implementation:
- Modify using Javassist API
- Convert static fields to ThreadLocal
- Add getter/setter methods
ByteBuddy Implementation:
- Use Advice to intercept method calls
- Modern fluent API
All Agents output modified bytecode to target/transformed-classes/ directory:
# View modified bytecode
javap -v agent-asm/target/transformed-classes/instance/MessageClient.class- 字节码增强两种模式对比 - Instance vs ThreadLocal mode comparison (ASM/Javassist/ByteBuddy)
- 实现方案对比与局限性分析 - Comprehensive comparison of all 5 implementation approaches
- agent-asm/README.md - ASM Agent detailed documentation
- agent-spring-boot-starter/README.md - Spring Boot Starter documentation
- CONTRIBUTING.md - Contribution guide
Contributions welcome! Please read CONTRIBUTING.md for details.
This project is licensed under the MIT License.
// Tenant A uses Aliyun SMS
MessageClient clientA = new MessageClient("https://dysmsapi.aliyuncs.com");
// Tenant B uses Tencent Cloud SMS
MessageClient clientB = new MessageClient("https://sms.tencentcloudapi.com");// Test two service providers simultaneously
MessageClient providerA = new MessageClient("https://provider-a.com");
MessageClient providerB = new MessageClient("https://provider-b.com");// Development environment
MessageClient devClient = new MessageClient("http://dev.example.com");
// Test environment
MessageClient testClient = new MessageClient("http://test.example.com");Applicable: Third-party SDK source code cannot be modified, uses static variables for configuration
Not Applicable: Your own code(should directly use instance variables)
-
Source Code Cannot Be Modified: This project solves the problem of third-party SDK source code that cannot be modified. If it's your own code, you should directly modify the design to use instance variables instead of static variables.
-
Thread Safety: All solutions are thread-safe, using ThreadLocal or instance variables to store configuration.
-
Performance Impact:
- ASM/Javassist: Almost no performance impact
- ByteBuddy: Slight performance impact
- Wrapper: Requires manual context management
- ClassLoader: Higher memory overhead
-
Compatibility:
- Java 8+
- Spring Boot 2.x/3.x
- Supports all mainstream JVMs
-
Production Environment: All solutions have been validated in production environments.
- 🐛 Issue Reporting: Submit issues on GitHub
- 💬 Discussion: Welcome to discuss in Discussions
- 👥 Code Contribution: Read CONTRIBUTING.md
If this project helps you, please give it a Star ⭐
- Implemented ASM bytecode enhancement(supports instance and threadlocal modes)
- Implemented Javassist bytecode enhancement
- Implemented ByteBuddy bytecode enhancement
- Implemented Wrapper pattern solution
- Implemented ClassLoader isolation solution
- Provided Spring Boot Starter auto-configuration
- Complete test cases and documentation