# ReflectASM
**Repository Path**: hyee/ReflectASM
## Basic Information
- **Project Name**: ReflectASM
- **Description**: 使用ASM实现高效的反射,适用于Java7/8. https://github.com/hyee/ReflectASM
- **Primary Language**: Java
- **License**: BSD-3-Clause
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2017-02-09
- **Last Updated**: 2025-04-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README

Please use the [ReflectASM discussion group](http://groups.google.com/group/reflectasm-users) for support.
## Note
This is the revised version, some differences from `EsotericSoftware/reflectasm`:
* Supports `Java 8` only in order to remove the dependency of `asm-xxx.jar`
* New class `ClassAccess` as the base of `MethodAccess`/`FieldAccess`/`ConstructorAccess`
* Provides the interface to dump the dynamic class for investigation purpose
* Provides the caching options for performance purpose(default as enabled)
* Optimize some logics for performance purpose
* Auto data type conversion for invoking or construction
* Accurately position the closest method for overloading
* Supports methods/constructors with variable parameters
* Removes the harcodes of the package name
* Supports accessing non-public class/method/field
* Uses of generic types to reduce unnecessary explicit/implicit casting
* Reduces the generation of proxy classes
* Supports [MethodHandle](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandle.html#asVarargsCollector-java.lang.Class-)
To support Java 7, just change the imports in class `ClassAccess` to add back the dependency of `asm-xxx.jar`
## Overview
ReflectASM is a very small Java library that provides high performance reflection by using code generation. An access class is generated to set/get fields, call methods, or create a new instance. The access class uses bytecode rather than Java's reflection, so it is much faster. It can also access primitive fields via bytecode to avoid boxing.
#### Summary of the cost(Smaller value means better performance)
The benchmark code can be found in the `benchmark` directory, test environment:
* CPU: Intel I7-3820QM
* VM : Java 8u112 x86
* OS : Win10 x64, and as a laptop, the result is not very stable
| VM | Item | Direct | DirectMethodHandle | ReflectASM | Reflection |
| --- | --- | --------- | --------- | --------- | --------- |
| Server VM | Field Set+Get | 1.17 ns | 7.83 ns | 6.88 ns | 12.51 ns |
| Server VM | Method Call | 0.86 ns | 4.14 ns | 3.30 ns | 4.51 ns |
| Server VM | Constructor | 5.10 ns | 7.51 ns | 6.86 ns | 8.62 ns |
| Client VM | Field Set+Get | 2.49 ns | 16.30 ns | 13.75 ns | 209.21 ns |
| Client VM | Method Call | 2.13 ns | 10.54 ns | 8.32 ns | 48.85 ns |
| Client VM | Constructor | 71.00 ns | 192.87 ns | 74.80 ns | 154.43 ns |
#### Server VM
&chs=700x237&chd=t:104886099,704401675,619611373,1125590088,77170418,372776726,296855382,405673821,458851833,675729992,616966498,775650205&chds=0,1125590088&chxl=0:|Constructor - Reflection|Constructor - ReflectASM|Constructor - DirectMethodHandle|Constructor - Direct|Method Call - Reflection|Method Call - ReflectASM|Method Call - DirectMethodHandle|Method Call - Direct|Field Set+Get - Reflection|Field Set+Get - ReflectASM|Field Set+Get - DirectMethodHandle|Field Set+Get - Direct&cht=bhg&chbh=10&chxt=y&chco=660000|660033|660066|660099|6600CC|6600FF|663300|663333|663366|663399|6633CC|6633FF|666600|666633|666666)
#### Client VM
&chs=700x237&chd=t:74650984,489015485,412433873,6276273031,63781455,316114537,249572650,1465394076,2130048135,5786071571,2244086000,4632780627&chds=0,6276273031&chxl=0:|Constructor - Reflection|Constructor - ReflectASM|Constructor - DirectMethodHandle|Constructor - Direct|Method Call - Reflection|Method Call - ReflectASM|Method Call - DirectMethodHandle|Method Call - Direct|Field Set+Get - Reflection|Field Set+Get - ReflectASM|Field Set+Get - DirectMethodHandle|Field Set+Get - Direct&cht=bhg&chbh=10&chxt=y&chco=660000|660033|660066|660099|6600CC|6600FF|663300|663333|663366|663399|6633CC|6633FF|666600|666633|666666)
## Usage
Considering this class:
```java
public class TestObject {
static String fs;
public Double fd;
public int fi;
private long fl;
public TestObject() {fs = "TestObject0";}
public TestObject(int fi1, Double fd1, String fs1, long l) {}
static String func1(String str) {fs = str;return str;}
public String func2(int fi1, Double fd1, String fs1, long l) {return fs;}
}
```
Reflection with ReflectASM:
```java
ClassAccess access = ClassAccess.access(TestObject.class);
TestObject obj;
// Construction
obj = access.newInstance();
obj = access.newInstance(1, 2, 3, 4);
// Set+Get field
access.set(null, "fs", 1); // static field
System.out.println((String)access.get(null, "fs"));
access.set(obj, "fd", 2);
System.out.println((String)access.get(obj, "fd"));
// Method invoke
access.invoke(null,"func1","a"); //static call
System.out.println((String)access.invoke(obj, "func2",1,2,3,4));
```
If same field/method/constructor is referenced frequently, for performance purpose, consider following code:
```java
ClassAccess access = ClassAccess.access(TestObject.class);
TestObject obj;
//Identify the indexes for further use
int newIndex=access.indexOfConstructor(int.class, Double.class, String.class, long.class);
int fieldIndex=access.indexOfField("fd");
int methodIndex=access.indexOfMethod("func1",String.class);
//Now use the index to access object in loop or other part
for(int i=0;i<100;i++) {
obj=access.newInstanceWithIndex(newIndex,1,2,3,4);
access.set(obj,fieldIndex,123);
String result=access.invokeWithIndex(null,methodIndex,"x");
}
```
With above code, input arguments are auto-converted into the corresponding data types before the call. If auto-conversion is unnecessary in your code and all argument types are correct previously, for more performance purpose, replace as:
```java
ClassAccess access = ClassAccess.access(TestObject.class);
TestObject obj;
//Identify the indexes for further use
int newIndex=access.indexOfConstructor(int.class, Double.class, String.class, long.class);
int fieldIndex=access.indexOfField("fd");
int methodIndex=access.indexOfMethod("func1",String.class);
//Now use the index to access object in loop or other part
for(int i=0;i<100;i++) {
obj=access.accessor.newInstanceWithIndex(newIndex,1,Double.valueOf(2),"3",4L);
access.accessor.set(obj,fieldIndex,Double.valueOf(123));
String result=access.accessor.invokeWithIndex(null,methodIndex,"x");
}
```
Class reflection with ReflectASM(F=FieldAccess,M=MethodAccess,C=ConstructorAccess, {}=Array):
| ClassAccess.*method* | Equivalence | Description |
| -------------------- | ---------- | ----------- |
| *static* access(Class,[dump dir]) | (F/M/C).access | returns a wrapper object of the underlying class, in case of the 2nd parameter is specified, dumps the dynamic classes into the target folder |
| indexesOf(name,type)| | returns an index array that matches the given name and type(`field`/`method`/``) |
| indexesOf(Class,name,type)| | returns an index array that matches the given class,name and type(`field`/`method`/``) |
| indexOf(name,type)| | returns the first element of `indexesOf`|
| getHandleWithIndex(index,type) | | returns a MethodHandle regards to the index(from `indexOf`), type here can be `set/get/method/` |
| getHandle(Class,name,type,{argtypes}) | | returns a MethodHandle regards to input parameter types |
| getHandleWithArgs(Class,name,type,{args}) | | returns a MethodHandle regards to input parameter values |
| invokeWithMethodHandle(instance,index,type,{args}|| if option `invokeWithMethodHandle` is `true`(default as `false`, then all below methods will use MethodHandle to process, instead of `accessor`|
| indexOfField(name/Field) | F.getIndex | returns the field index that matches the given input|
| indexOfField(Class,name) | | returns the field index that defined in the target class|
| indexOfMethod(name/Method) | M.getIndex | returns the first method index that matches the given input|
| indexOfMethod(Class,name,{argTypes}) | | returns the first method index that defined in the target class|
| indexOfMethod(name,argCount/{argTypes}) | (M/C).getIndex | returns the first index that matches the given input|
| indexOfConstructor(constructor) | C.getIndex | returns the index that matches the given constructor|
| set(instance,Name/index,value) | F.set | Assign value to the specific field |
| set*Primitive*(instance,index,value) | F.set*Primitive* | Assign primitive value to the specific field, *primitive* here can be `Integer/Long/Double/etc` |
| get(instance,name/index) | F.get | Get field value |
| get*Primitive*(instance,index) | F.get*Primitive* | Get field value and convert to target primitive type|
| get(instance,name/index, Class) | F.get | Get field value and convert to target class type|
| invoke(instance,name,{args}) | M.invoke | Executes method |
| invokeWithIndex(instance,index,{args}) | M.invokeWithIndex | Executes method by specifying the exact index|
| invokeWithWithTypes(instance,name/index,{argTypes},{args}) | M.invokeWithWithTypes | Invokes method by specifying parameter types in order to position the accurate method|
| newInstance() | C.newInstance | Create underlying Object |
| newInstance({args}) | C.newInstance | Create underlying Object |
| newInstanceWithIndex(index,{args}) | C.newInstanceWithIndex | Create underlying Object with the specific constructor index|
| newInstanceWithTypes({argTypes},{args}) | C.newInstanceWithTypes | Create underlying Object by specifying parameter types in order to position the accurate constructor|
| getInfo || Get the underlying `ClassInfo`|
| *accessor.*newInstanceWithIndex| | Directly initializes the instance without validating/auto-conversion the input arguments|
| *accessor.*invokeWithIndex| | Directly invokes the method without validating/auto-conversion the input arguments|
| *accessor.*set/get| | Directly set/get the field without validating/auto-conversion the input arguments|
Other static fields:
* `ClassAccess.ACCESS_CLASS_PREFIX`: the prefix of the dynamic class name(format is `.`), the default value is `asm.`
* `ClassAccess.IS_CACHED`: The option to cache the method/constructor indexes and the dynamic classes, default is `true`. VM parameter `reflectasm.is_cache` can also control this option
* `ClassAccess.IS_STRICT_CONVERT`: controls the auto-conversion of the input arguments for the invoke/set/constructor functions, i.e., auto-conversion `String` into `int` when a method only accepts the `int` parameter. Default is `false`(enabled conversion), and VM parameter `reflectasm.is_strict_convert` can also control this option
## Visibility
ReflectASM can always access all members(public + non-public) that defined inside it + supper-classes + interfaces.
To access a field/method defined in the class's super-class which is also samely defined in the class itself with the same parameters, then position the index firstly and invoke/get/set with it afterwards. For example:
```java
int fieldIndex=access.indexOfField(,);
access.get(instance,fieldIndex);
access.set(instance,fieldIndex,value);
int methodIndex=access.indexOfMethod(,,{});
access.invokeWithIndex(instance,methodIndex,{arguments});
```
Refer to test case `com.hyee.reflectmeta.testOverload()/testOverloadWithLambda()` for more examples`
## Exceptions
Stack traces when using ReflectASM are a bit cleaner. Here is Java's reflection calling a method that throws a RuntimeException:
```
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.example.SomeCallingCode.doit(SomeCallingCode.java:22)
Caused by: java.lang.RuntimeException
at com.example.SomeClass.someMethod(SomeClass.java:48)
... 5 more
```
Here is the same but when ReflectASM is used:
```
Exception in thread "main" java.lang.RuntimeException
at com.example.SomeClass.someMethod(SomeClass.java:48)
at com.example.SomeClassMethodAccess.invoke(Unknown Source)
at com.example.SomeCallingCode.doit(SomeCallingCode.java:22)
```
If ReflectASM is used to invoke code that throws a checked exception, the checked exception is thrown. Because it is a compilation error to use try/catch with a checked exception around code that doesn't declare that exception as being thrown, you must catch Exception if you care about catching a checked exception in code you invoke with ReflectASM.
## How it works
Each time to call `ClassAccess.access()`, a wrapper class is created and instanced, or if the wrapper object can be found from the cache, then return from cache directly. This wrapper object is assigned as `ClassAccess.accessor`.
So any access(get/set field,invoke method,construction) to the target class, the process flow is:
```
User Calls
\/
Is auto-conversion enabled?---Yes---> ClassAccess.reArgs&Call(invoke/get/set/etc)
\ \/
-----No---> ClassAccess.accessor.callIndex(invoke/get/set/etc)
\/
TargetInstance.call(invoke/get/set/etc)
\/
Return result
```
The outstanding cost by comparing to direct call is only redirect once in minimum so the performance differences can be ignored(< 10 ns), the most time-consuming part is the target method itself.
To see how is the logic of the wrapper class, take below class `TestObject` as example:
```java
package test;
public class TestObject {
static String fs;
public Double fd;
public int fi;
private long fl;
public TestObject() {fs = "TestObject0";}
public TestObject(int fi1, Double fd1, String fs1, long l) {}
static String func1(String str) {fs = str;return str;}
public String func2(int fi1, Double fd1, String fs1, long l) {return fs;}
}
```
And then the auto-generated wrapper class is shown below, you can also call `ClassAccess.access(TestObject.class,".")` to dump the wrapper class:
```java
package asm.test;
import com.hyee.reflectmeta.Accessor;
import com.hyee.reflectmeta.ClassAccess;
import com.hyee.reflectmeta.ClassInfo;
import sun.reflect.MagicAccessorImpl;
public class TestObject extends MagicAccessorImpl implements Accessor {
static final ClassInfo classInfo = new ClassInfo();
public TestObject() {}
static {
classInfo.methodNames = new String[]{"func2", "func1"};
classInfo.methodParamTypes = new Class[][]{{Integer.TYPE, Double.class, String.class, Long.TYPE}, {String.class}};
classInfo.returnTypes = new Class[]{String.class, String.class};
classInfo.methodModifiers = new Integer[]{Integer.valueOf(1), Integer.valueOf(8)};
classInfo.methodDescs = new String[]{"(ILjava/lang/Double;Ljava/lang/String;J)Ljava/lang/String;", "(Ljava/lang/String;)Ljava/lang/String;"};
classInfo.fieldNames = new String[]{"fs", "fd", "fi", "fl"};
classInfo.fieldTypes = new Class[]{String.class, Double.class, Integer.TYPE, Long.TYPE};
classInfo.fieldModifiers = new Integer[]{Integer.valueOf(8), Integer.valueOf(1), Integer.valueOf(1), Integer.valueOf(2)};
classInfo.fieldDescs = new String[]{"Ljava/lang/String;", "Ljava/lang/Double;", "I", "J"};
classInfo.constructorParamTypes = new Class[][]{new Class[0], {Integer.TYPE, Double.class, String.class, Long.TYPE}};
classInfo.constructorModifiers = new Integer[]{Integer.valueOf(1), Integer.valueOf(1)};
classInfo.constructorDescs = new String[]{"()V", "(ILjava/lang/Double;Ljava/lang/String;J)V"};
classInfo.baseClass = test.TestObject.class;
classInfo.isNonStaticMemberClass = false;
classInfo.bucket = 13;
ClassAccess.buildIndex(classInfo);
}
public ClassInfo getInfo() {
return classInfo;
}
public T invokeWithIndex(test.TestObject var1, int var2, V... var3) {
switch(var2) {
case 0:
return var1.func2(((Integer)var3[0]).intValue(), (Double)var3[1], (String)var3[2], ((Long)var3[3]).longValue());
case 1:
return test.TestObject.func1((String)var3[0]);
default:
throw new IllegalArgumentException("Method not found: " + var2);
}
}
public T get(test.TestObject var1, int var2) {
switch(var2) {
case 0:
return test.TestObject.fs;
case 1:
return var1.fd;
case 2:
return Integer.valueOf(var1.fi);
case 3:
return Long.valueOf(var1.fl);
default:
throw new IllegalArgumentException("Field not found: " + var2);
}
}
public void set(test.TestObject var1, int var2, V var3) {
switch(var2) {
case 0:
test.TestObject.fs = (String)var3;
return;
case 1:
var1.fd = (Double)var3;
return;
case 2:
var1.fi = ((Integer)var3).intValue();
return;
case 3:
var1.fl = ((Long)var3).longValue();
return;
default:
throw new IllegalArgumentException("Field not found: " + var2);
}
}
public test.TestObject newInstanceWithIndex(int var1, T... var2) {
switch(var1) {
case 0:
return new test.TestObject();
case 1:
return new test.TestObject(((Integer)var2[0]).intValue(), (Double)var2[1], (String)var2[2], ((Long)var2[3]).longValue());
default:
throw new IllegalArgumentException("Constructor not found: " + var1);
}
}
public test.TestObject newInstance() {
return new test.TestObject();
}
}
```