Java反射-1(理论)
Java反射-2(技巧)
1. 善于使用api
1.1 使用合适的方法获取反射对象
使用反射时,例如尽量不要使用getMethods()
等方法后再遍历筛选,而是直接使用getMethod(methodName)
等方法根据方法名来获取方法。
1.2 取消安全检查
即使是调用public
方法,也需要使用 field.setAccessible(true)
来取消安全检查。
Java中通过反射执行一个方法的过程如下:
public class Foo {
private void doStuff() {
System.out.println("hello world");
}
}
public class TestFoo {
@Test
public void test() throws Exception {
String methodName = "doStuff";
Method declaredMethod = Foo.class.getDeclaredMethod(methodName);
//由开发者决定是否要逃避安全体系的检查(是否可以快速获取)
System.out.println(declaredMethod.isAccessible());
if(declaredMethod.isAccessible()){
declaredMethod.setAccessible(true);
}
declaredMethod.invoke(new Foo());
}
}
实际上,在通过反射执行方法时,必须在invoke之前检查Accessible属性。但是方法对象的Accessible属性并不是用来决定是否可以访问的。
public class Foo {
public void doStuff() {
System.out.println("hello world");
}
public static void main(String[] args) throws NoSuchMethodException {
String methodName = "doStuff";
Method declaredMethod = Foo.class.getDeclaredMethod(methodName);
System.out.println(declaredMethod.isAccessible());
}
}
实际上,即使反射的方法访问修饰符为public,isAccessible()
的最终结果依旧是false。而且即使为false,还是可以使用invoke()方法执行。
故Accessible的属性并不是我们语法层次理解的访问权限,而是指是否更加容易获得,是否进行安全检查。
我们知道,动态修改一个类或者执行方法都会受到java安全体系的制约,而安全处理是非常消耗资源的(性能非常低),因此对于运行期要执行的方法或要修改的属性提供了Accessible
可选项:由开发者来决定是否逃避安全体系的检查。
在源码java.lang.reflect.AccessibleObject#isAccessible
中,该方法默认返回值为false。
AccessibleObject是Field、Method、Constructor的父类,决定其是否可以快速访问而不进行访问控制检查。在AccessibleObject中是以override变量保存该值的,但是具体是否快速执行是在Method类的invoke方法中决定的。
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException{
//检查是否可以快速获取,其值是父类的AccessibleObject的override变量
if (!override) {
//不能快速获取,要进行安全检查
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
//直接执行方法
return ma.invoke(obj, args);
}
Accessible属性只是用来判断是否需要进行安全检查的,如果不需要则直接执行,这样可以大幅度地提升系统性能(当然了,由于取消了安全检查,也可以运行private方法,获取private私有属性)。
2. 善于使用缓存
将反射得到的元数据保存起来,使用时,只需要从内存中获取即可。
元数据包含class
、field
、method
、constructor
。
比如对class对象的缓存:
private Map classMap = new HashMap<>();
Class getClass(String className) throws ClassNotFoundException {
Class> clazz = classMap.get(className);
if (className == null) {
clazz = Class.forName(className);
classMap.put(className, clazz);
}
return clazz;
}
使用内省时,也可以将PropertyDescriptor
缓存起来,也可以提高效率。
public abstract class IntrospectorUtils {
public static final ConcurrentHashMap introspectorCache
= new ConcurrentHashMap<>(16);
//存储字段
public static PropertyDescriptor persistencePropertyDescriptor(String name, Class clazz) throws IntrospectionException {
if (StringUtils.isBlank(name) || clazz == null) {
throw new BusinessException("业务参数有误,系统异常!");
}
String key = clazz.getSimpleName() + name;
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, clazz);
//存在返回,不存在去填充(线程安全)
return introspectorCache.computeIfAbsent(key, V -> propertyDescriptor);
}
}
3. 善于使用框架
例如:copy对象时,可以使用CGLib的BeanCopier(其原理是运行时动态生成了用于复制某个类的字节码),其性能比反射框架org.springframework.beans.BeanUtils
性能要高。
/**
* @param source 原始对象
* @param targetClass 拷贝的对象类型
*/
public static T2 createCopy(T1 source, Class targetClass) {
if (source == null) {
throw new RuntimeException("参数异常");
} else {
T2 target;
try {
target = targetClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
BeanCopier beanCopier = BeanCopier.create(source.getClass(), targetClass, false);
beanCopier.copy(source, target, null);
return target;
}
}
要提高Method.invoke性能,可以使用JDK7的MethodHandler
(由于Method.invoke的JIT优化,差距不大)。使用高版本的JDK也很重要,反射性能在不断提高。