Inject Transformation
The @Inject
annotation allows you to insert custom code at specific points in target methods.
Basic Usage
@ModifyClass("com.example.Calculator")
public class CalculatorHooks {
@Inject(
methodName = "add",
methodDesc = "(II)I",
at = At.HEAD
)
public static CallbackInfo onAddStart(int a, int b) {
System.out.println("Adding: " + a + " + " + b);
return CallbackInfo.empty();
}
}
Annotation Parameters
methodName (required)
The name of the target method to inject into.
methodName = "add"
methodDesc (required)
The JVM descriptor of the target method signature.
methodDesc = "(II)I" // int add(int a, int b)
See Method Descriptors for details.
at (required)
Where to inject the code within the method.
At Enum - Injection Points
At.HEAD
Inject at the very beginning of the method, before any existing code.
@Inject(methodName = "calculate", methodDesc = "()I", at = At.HEAD)
public static CallbackInfo atMethodStart() {
System.out.println("Method started");
return CallbackInfo.empty();
}
Result:
public int calculate() {
System.out.println("Method started"); // Injected
// Original code here
}
At.RETURN
Inject before every return statement in the method.
@Inject(methodName = "getValue", methodDesc = "()I", at = At.RETURN)
public static CallbackInfo beforeReturn(CallbackInfo ci) {
System.out.println("Returning: " + ci.returnValue);
return CallbackInfo.empty();
}
Result:
public int getValue() {
if (condition) {
System.out.println("Returning: " + value); // Injected
return value;
}
System.out.println("Returning: " + defaultValue); // Injected
return defaultValue;
}
At.TAIL
Inject at the very end of the method, after all code but before implicit return.
@Inject(methodName = "cleanup", methodDesc = "()V", at = At.TAIL)
public static CallbackInfo atMethodEnd() {
System.out.println("Cleanup complete");
return CallbackInfo.empty();
}
Hook Method Parameters
Hook methods receive the same parameters as the target method, plus CallbackInfo
:
// Target method:
public String process(String input, int count) { ... }
// Hook method:
@Inject(methodName = "process", methodDesc = "(Ljava/lang/String;I)Ljava/lang/String;", at = At.HEAD)
public static CallbackInfo processHook(String input, int count, CallbackInfo ci) {
// Can access parameters
return CallbackInfo.empty();
}
CallbackInfo - Controlling Behavior
The CallbackInfo
object allows you to control how the injection behaves:
public class CallbackInfo {
public boolean cancelled; // Skip original code?
public Object returnValue; // Return custom value?
}
Cancelling Execution
Skip the original method and return early:
@Inject(methodName = "authenticate", methodDesc = "(Ljava/lang/String;)Z", at = At.HEAD)
public static CallbackInfo checkPermission(String user, CallbackInfo ci) {
if (!user.equals("admin")) {
ci.cancelled = true;
ci.returnValue = false; // Return false without running original code
}
return ci;
}
Custom Return Values
Return a custom value instead of the original result:
@Inject(methodName = "getCached", methodDesc = "()Ljava/lang/Object;", at = At.HEAD)
public static CallbackInfo useCachedValue(CallbackInfo ci) {
Object cached = getFromCache();
if (cached != null) {
ci.cancelled = true;
ci.returnValue = cached;
}
return ci;
}
Multiple Injections
You can inject into the same method multiple times:
@ModifyClass("com.example.Service")
public class ServiceHooks {
@Inject(methodName = "handle", methodDesc = "(Ljava/lang/String;)V", at = At.HEAD)
public static CallbackInfo logStart(String input) {
System.out.println("Start: " + input);
return CallbackInfo.empty();
}
@Inject(methodName = "handle", methodDesc = "(Ljava/lang/String;)V", at = At.RETURN)
public static CallbackInfo logEnd(String input) {
System.out.println("End: " + input);
return CallbackInfo.empty();
}
}
Both injections will be applied.
Instance Methods vs Static Methods
For instance methods, the first parameter is usually this
(or the object instance):
// Target instance method:
public class Calculator {
public int add(int a, int b) { return a + b; }
}
// Hook can receive 'this':
@Inject(methodName = "add", methodDesc = "(II)I", at = At.HEAD)
public static CallbackInfo onAdd(Calculator self, int a, int b) {
System.out.println("Calculator instance: " + self);
return CallbackInfo.empty();
}
Best Practices
- Keep hooks simple: Complex logic should be in separate methods
- Avoid exceptions: Handle exceptions within hook methods
- Use At.HEAD for guards: Check conditions early
- Be careful with At.RETURN: Multiple returns need handling
- Test thoroughly: Verify injections work correctly
Examples
See Examples - Inject for complete working examples.
Next Steps
- Learn about Invoke for method interception
- Explore Advanced Usage
- Check API Reference