Dynamic Code Execution in Java Using Compiler API for Runtime Fixes
When production systems encounter unexpected issues, deploying a fix often requires going through a lengthy release cycle. To circumvent this, one approach is to allow dynamic execution of Java code at runtime via an HTTP endpoint. This technique leverages the Java Compiler API to compile and run user-provided source code on the fly.
The implementation described here supports:
- Arbitrary SQL operations through MyBatis
- Seamless integration with Spring’s dependency injection
Core Components
Tool and ToolProvider
Tool defines a standard interface for invoking command-line tools programmatically. Its run method executes the tool with specified I/O streams and arguments.
int run(InputStream in, OutputStream out, OutputStream err, String... args);
ToolProvider supplies system-level tools such as the Java compiler:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaCompiler Workflow
The JavaCompiler interface enables programmatic compilation. Key steps include:
- File Management: Use
StandardJavaFileManagerto manage source and class files. - Compilation Task: Create a
CompilationTaskusinggetTask(), specifying output directory and source units. - Diagnostics: Optionally attach a listener to capture compilation errors.
In-Memory Compilation
Instead of writing source files to disk, a custom JavaFileObject implementation holds source code in memory:
private static class InMemorySource extends SimpleJavaFileObject {
private final String code;
InMemorySource(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
Runtime Execution with Spring Integration
After successful compilation, the generated class is loaded using a URLClassLoader. The framework then:
- Reflectively inspects constructor parameters
- Resolves required dependencies from the Spring
ApplicationContext - Instantiates the handler and invokes its
handle()method
Fields annotated with @Ignore are excluded from dependency injection.
Handler Interface Contract
Uploaded code must implement a predefined interface:
public interface DynamicHandler {
Object handle();
}
Universal Data Access Layer
A service class (DynamicDataManager) provides generic CRUD operations using MyBatis with dynamic SQL:
@Mapper
public interface DynamicDataMapper {
@Update("UPDATE ${table} SET ${updates} WHERE ${condition}")
void update(@Param("table") String table,
@Param("updates") String updates,
@Param("condition") String condition);
@Select("SELECT ${columns} FROM ${table} WHERE ${condition}")
List<Map<String, Object>> select(@Param("table") String table,
@Param("columns") String columns,
@Param("condition") String condition);
}
Result mapping converts database rows (snake_case) to POJO fields (camelCase), handling primitive and wrapper type conversions safely.
Example Usage
A sample handler updating an order record:
public class OrderFixHandler implements DynamicHandler {
@Ignore
private static final Logger logger = LoggerFactory.getLogger(OrderFixHandler.class);
private final DynamicDataManager manager;
public Object handle() {
manager.update("t_order", "delivery_type=1, u_t=UNIX_TIMESTAMP()", "id=2381916717");
return "Order updated successfully";
}
}
This file is uploaded via a multipart POST request to /manual/allPowerful/go.
Security Warning: Allowing arbitrary code execution poses severe security risks. This mechanism should never be exposed in untrusted environments.