前言
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为反射机制。
我们知道,Java语言不是动态语言,但是他却有非常突出的动态相关机制,反射机制。
代码
我们可以通过反射机制获取一个class的相关信息。
1. 利用Javassist获取class信息
Javassist是一个动态类库,可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。
public static void getClassInfoByJavassist(Class clazz) {
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(clazz));
try {
System.out.println("class-->"+clazz.getName());
CtClass ctClass = classPool.get(clazz.getName());
//获取常量信息
CtField[] fields=ctClass.getDeclaredFields();
for(int i=0;i<fields.length;i++) {
System.out.println("object="+fields[i].getName()+"-->value="+fields[i].getConstantValue()+"-->type="+fields[i].getType().getName());
}
//获取方法信息
CtMethod[] ctMethods = ctClass.getMethods();
for (CtMethod ctMethod : ctMethods) {
//排除equals,hash,toString等方法
if (!clazz.getName().equals(ctMethod.getDeclaringClass().getName())) {
continue;
}
MethodInfo methodInfo = ctMethod.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if (attr == null) {
// exception
}
String[] paramNames = new String[ctMethod.getParameterTypes().length];
TreeMap<Integer, String> sortMap = new TreeMap<Integer, String>();
for (int i = 0; i < attr.tableLength(); i++)
sortMap.put(attr.index(i), attr.variableName(i));
int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
paramNames = Arrays.copyOfRange(sortMap.values().toArray(new String[0]), pos, paramNames.length + pos);
CtClass[] types=ctMethod.getParameterTypes();
for(int i=0;i<paramNames.length;i++) {
System.out.println("class="+clazz.getSimpleName()+"-->method="+ctMethod.getName()+"-->isStatic="+Modifier.isStatic(ctMethod.getModifiers())+"-->paramsType="+types[i].getName()+"-->paramsName="+paramNames[i]);
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
我们新建一个Demo.class
public class Demo {
public static String getName(String str) {
String s="123";
return str+s;
}
public Integer doSomething(String str,double a,Map<String, String> map,List<String> list) {
//doSomething
Integer i=0;
return i;
}
private static final int NUM=1;
private static String s="1234";
static {
s="5678";
}
}
调用方法 getClassInfoByJavassist(Demo.class),输出class信息。
class-->com.zwt.reflect.Demo
object=NUM-->value=1-->type=int
object=s-->value=null-->type=java.lang.String
class=Demo-->method=getName-->isStatic=true-->paramsType=java.lang.String-->paramsName=str
class=Demo-->method=doSomething-->isStatic=false-->paramsType=java.lang.String-->paramsName=str
class=Demo-->method=doSomething-->isStatic=false-->paramsType=double-->paramsName=a
class=Demo-->method=doSomething-->isStatic=false-->paramsType=java.util.Map-->paramsName=map
class=Demo-->method=doSomething-->isStatic=false-->paramsType=java.util.List-->paramsName=list
2. 通过spring里的LocalVariableTableParameterNameDiscoverer获取paramsName,使用jdk自带reflect反射类获取class其他信息
在jdk1.8以下java版本中,根据jdk自带reflect包,可以拿到大部分class信息,唯一拿不到的是参数name,我们可以借助spring包里的LocalVariableTableParameterNameDiscoverer去获取paramsName。
public static void getClassInfoBySpringAndReflect(Class clazz) {
try {
LocalVariableTableParameterNameDiscoverer u =
new LocalVariableTableParameterNameDiscoverer();
Method[] methods =clazz.getDeclaredMethods();
Field[] fields=clazz.getDeclaredFields();
for(int i=0;i<fields.length;i++) {
//设置成可以access的,否则get(clazz)报错,无法读取private属性
fields[i].setAccessible(true);
System.out.println("object="+fields[i].getName()+"-->value="+fields[i].get(clazz)+"-->type="+fields[i].getType().getName());
}
for(Method method:methods) {
//使用spring LocalVariableTableParameterNameDiscoverer 获取paramsName
String[] params = u.getParameterNames(method);
Class<?> [] classType=method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.println("class="+clazz.getSimpleName()+"-->method="+method.getName()+"-->isStatic="+Modifier.isStatic(method.getModifiers())+"-->paramsType="+classType[i].getName()+"-->paramsName="+params[i]);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
调用getClassInfoBySpringAndReflect(Demo.class),也可以拿到class信息。
3.使用jdk1.8及以上java版本获取class信息
若jdk版本较高,完全可以不用借助第三方jar包而获取class信息。
jdk1.8以上,添加了Parameter类,可以获取参数paramsName.
public static void getClassInfoByJava8AndReflect(Class clazz){
try {
Method[] methods = clazz.getDeclaredMethods();
Field[] fields=clazz.getDeclaredFields();
for(int i=0;i<fields.length;i++) {
//设置成可以access的,否则get(clazz)报错,无法读取private属性
fields[i].setAccessible(true);
System.out.println("object="+fields[i].getName()+"-->value="+fields[i].get(clazz)+"-->type="+fields[i].getType().getName());
}
for (Method method : methods) {
//使用jdk1.8Parameter获取paramsNames
Parameter[] params = method.getParameters();
Class<?> [] classType=method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.println("class="+clazz.getSimpleName()+"-->method="+method.getName()+"-->isStatic="+Modifier.isStatic(method.getModifiers())+"-->paramsType="+classType[i].getName()+"-->paramsName="+params[i]);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
调用getClassInfoByJava8AndReflect(Demo.class),获取class信息。
其他
反射优点:
合理的使用反射机制可以有效降低代码冗余及代码量。并且可以让应用程序实现一些几乎不可能做到的事情。反射属于Java语言里比较高级的一个特性。
反射缺点:
如果不合理的使用反射,可能降低系统性能。
而且非常重要的一点,我们看如上代码,有一句fields[i].setAccessible(true);
这是在设置私有属性可以访问,显然,这破坏了代码的抽象性,而且可能导致安全问题的产生。