最近在看Spring IOC容器的源码,发现里面有使用到java的反射和内省机制,在这里记录和总结一下。
反射(reflection)机制
什么是反射
反射是Java 编程语言中的一个特性。它允许正在执行的Java 程序检查或“内省”自身,并操纵程序的内部属性。 例如,Java类可以获取其所有成员的名称并显示它们。因此,反射提供了以下一些功能
:
- 获取某个类定义的方法信息,包括方法名,参数,返回值等信息。
- 获取某个类的构造函数。
- 获取某个类的属性。
- 通过方法名调用类的相应的方法。
- 修改对象的属性值。
- 创建某个类对应的对象。
反射的涉及到的类与接口
所有跟反射相关的类与接口都在java.lang.reflect包下面,涉及到的类与接口也比较多,也无法全部介绍,下面主要介绍Class相关的。
一个类中主要包含有构造函数(Constructor), 方法(Method), 字段(Field),修饰符(Modifier), 接口(Interface), 父类(SuperClass)等组成部分。
下面以Constructor和Method类为例,这两个类都是继承自同一个接口Executable,其关系如下:
AnnotatedElemnet接口标识一个可以被Java Annotation注解的Java语言元素(包括Field, Parameter, Method, Constructor, Class, Package,也就意味着类中的这些元素可以使用注解),该接口允许通过反射读取注解。
AccessibleObject类是Field、Method和Constructor的基类。当使用反射对象时,它提供Java语言访问控制检查的功能,同时可以关闭访问权限检查。当Field、Method和Constructor分别用于设置或获取字段、调用方法或创建和初始化类的新实例时,将执行访问检查(针对public、default、protected和private的字段,方法,构造函数)。
Member接口描述了Field、Method和Constructor需要实现的方法,可以通过这些方法获得修饰符,名字,声明的类,是否是编译器引入的等信息。
GenericDeclaration接口也继承了AnnotatedElemnet,是所有使用了类型变量的公共接口,也就是说只有实现了这个接口的才能在对应“实体”上声明“泛型变量”,目前实现GenericDeclaration接口的类包括Class(类), Method(方法), Constructor(构造器), 通过新增的getTypeParameters()方法获取泛型相关的信息。
Executable类则是Constructor和Method的公共父类,包含了一些公共的方法。
经过上面的分析,可以知道通过反射我们几乎可以得到所有跟类相关的信息,包括类拥有的字段,构造函数,方法,以及相应的修饰符,还有泛型相关的信息,同时可以通过反射创建一个对象,调用一个方法,更改字段的值。
反射中常用的方法
AccessibleObject 中定义的常用方法
方法名 | 描述 |
---|---|
public static void setAccessible(AccessibleObject[] array, boolean flag) | 根据flag决定开启还是关闭array数组里面的对象的访问控制检查 |
public void setAccessible(boolean flag) throws SecurityException | 设置当前元素的访问检查标志 |
public boolean isAccessible() | 返回当前元素的访问检查标志 |
Member接口中定义的常用方法
方法名 | 描述 |
---|---|
public String getName() | 获取当前元素的名字 |
public Class<?> getDeclaringClass() | 获取声明该元素的Class |
public int getModifiers() | 获取当前元素的修饰符 |
public boolean isSynthetic() | 如果该元素是编译器是引入的,则返回true,否则返回false |
Executable类中定义的常用方法
方法名 | 描述 |
---|---|
public Parameter[] getParameters() | 返回该元素的参数列表 |
public int getParameterCount() | 返回该元素的参数数量 |
public abstract Class<?>[] getExceptionTypes() | 返回所有的异常类型 |
Class类中常用的方法
方法名 | 描述 |
---|---|
public Field[] getFields() | 返回该Class所有可访问的public的字段,包括父类的和接口的 |
public Field getField(String name) throws NoSuchFieldException, SecurityException | 根据名字在该Class所有可访问的public字段中查找一个符合的字段 |
public Field[] getDeclaredFields() throws SecurityException | 返回该元素声明的所有的字段,包括public, default, protected, private的,不包括父类和接口 |
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException | 根据名字在该Class已声明的字段中查找一个符合的字段 |
public Constructor<T> getConstructor(Class<?>... parameterTypes) | 根据指定的参数类型返回一个public的构造函数 |
public Constructor<?>[] getDeclaredConstructors() throws SecurityException | 返回该元素的所有构造函数,包括public, default, protected, private的,不包括父类和接口 |
public Method[] getMethods() throws SecurityException | 返回所有可访问的public的方法,包括父类的 |
public Method[] getDeclaredMethods() throws SecurityException | 返回该元素所有的声明的方法,包括public, default, protected, private的,不包括父类和接口 |
public Type[] getGenericInterfaces() | 返回直接实现的接口(包含泛型参数) |
public Type getGenericSuperclass() | 返回直接继承的父类(包含泛型参数) |
public Class<?>[] getInterfaces() | 返回直接继承的父类(由于泛型擦除,不包含泛型参数) |
public native Class<? super T> getSuperclass() | 返回直接实现的接口(由于泛型擦除,不包含泛型参数) |
对于Constrcutor和Method也有类似的根据参数列表和方法名查找相应的Constructor和Method的方法,就不再一一列举了。
因为Constructor 和 Method 继承自Executable抽象类,因此需要实现Executable中的抽象方法,常用方法就是Executabel中的方法。
注意:以上列举的只是常用的方法,除此之外还有其他的方法,可查看reflect包下的java文件。
反射的使用
反射的使用步骤:
创建一个Class
// 第一种方式 Class<?> cls1 = Student.class; // 第二种方式 Class<?> cls2 = Class.forName("com.example.Student")
使用反射
具体的示例如下:
获取所有的方法信息。public class MethodExample { public static void main(String[] args) throws ClassNotFoundException { Class<?> cls = Class.forName("com.luoxj.Student"); Class<?> curClass = cls; while(curClass != null) { Method[] methods = curClass.getDeclaredMethods(); for(Method m : methods) { System.out.println("=================" + m.getDeclaringClass() + "==================="); System.out.println("name:" + m.getName()); System.out.println("declare class:" + m.getDeclaringClass()); System.out.println("modifier:" + Modifier.toString(m.getModifiers())); Class<?>[] params = m.getParameterTypes(); for(int i = 0; i < params.length; i++) { System.out.println("param #" + i + " " + params[i]); } Class<?>[] exceptions = m.getExceptionTypes(); for(int i = 0; i < exceptions.length; i++) { System.out.println("exception #" + i + " " + exceptions[i]); } System.out.println("return type:" + m.getReturnType()); } curClass = curClass.getSuperclass(); } } }
通过反射创建一个对象
public class CreateExample { public static void main(String[] args) { try { Class<?> cls = Class.forName("com.luoxj.Student"); Class[] paramsType = new Class[3]; paramsType[0] = String.class; paramsType[1] = Integer.TYPE; paramsType[2] = String.class; Constructor constructor = cls.getConstructor(paramsType); Object[] params = new Object[3]; params[0] = "Bob"; params[1] = 10; params[2] = "TJU"; Student student = (Student) constructor.newInstance(params); System.out.println(student.toString()); } catch (Throwable e) { e.printStackTrace(); } } }
修改变量值
public class ChangeFieldExample { public static void main(String[] args) { try { Class cls = Class.forName("com.luoxj.Student"); Field fld = cls.getDeclaredField("schoolName"); Student student = new Student(); fld.setAccessible(true); fld.set(student, "TJU"); System.out.println("schoolName = " + student.getSchoolName()); } catch (Throwable e) { System.err.println(e); } } }
内省(intropector)机制
什么内省机制
Introspector 类提供了一种标准方式来了解目标 Java Bean 支持的属性、事件和方法。
Java Bean指的是符合一定规范编写的Java类,具体的规范包括:
- 属性私有,属性包括普通的类型的属性,EventListener。
- 提供默认的无参构造函数
- 私有化的属性可以通过setXxx, getXxx, addXxx, removeXxx, isXxx等类似的方法设置或获得,其中EvenetListener的方法格式为addXxxEventListener, setXxxEventListener。getXxxEventListener,removeXxxEventListener等。
也就意味着我们可以通内省机制获取类的JavaBean的属性,事件和方法。
内省涉及到的类与接口
内省涉及到的主要的类与接口如下:
Introspector 类: 提供标准的方式来获取Java Bean支持的属性,事件和方法等信息。
BeanInfo 接口: 定义了获取上述 Java Bean 信息的接口。
FeatureDescriptor: 是 PropertyDescriptor、EventSetDescriptor 和 MethodDescriptor 等的公共基类,它支持一些可以设置和获取任意的内省描述符的公共信息。
FeatureDescriptor 的主要属性有:
// expert 标志当前的Descriptor是给专家使用还是普通用户使用
private boolean expert;
// hidden 标志当前的Descriptor是给工具使用还是普通用户使用,如果是给工具使用,则应该隐藏,返回true。
private boolean hidden;
// preferred 标志当前的Descriptor对普通用户很重要
private boolean preferred;
// 简要描述
private String shortDescription;
// 名字
private String name;
// 名字
private String displayName;
// 当前Descriptor 相关的属性
private Hashtable<String, Object> table;
内省的使用
使用步骤:
1、获取BeanInfo
2、获取Descriptor
具体的示例如下:
public class IntrospectorExample {
public static void main(String[] args) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(Student.class);
BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
System.out.println("BeanName:" + beanDescriptor.getName());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
Object defaultValue = new Object();
if (propertyDescriptor.getPropertyType().equals(Integer.TYPE)){
defaultValue = 0;
} else if (propertyDescriptor.getPropertyType().equals(String.class)) {
defaultValue = "Empty String";
}
propertyDescriptor.setValue("default", defaultValue);
System.out.println("name:" + propertyDescriptor.getName()
+ ", displayName:" + propertyDescriptor.getDisplayName()
+ ", type:" + propertyDescriptor.getPropertyType()
+ ", isExpert: " + propertyDescriptor.isExpert()
+ ", isConstrained: " + propertyDescriptor.isConstrained()
+ ", isHidden: " + propertyDescriptor.isHidden()
+ ", isPreferred: " + propertyDescriptor.isPreferred()
+ ", isBound: " + propertyDescriptor.isBound()
+ ", readMethod:" + propertyDescriptor.getReadMethod()
+ ",writeMethod:" + propertyDescriptor.getWriteMethod()
+ ", value:" + propertyDescriptor.getValue("default"));
}
MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
for(MethodDescriptor methodDescriptor : methodDescriptors) {
System.out.print("methodName:" + methodDescriptor.getName()
+ ", methodDisplayName:" + methodDescriptor.getDisplayName()
+ ", method:" + methodDescriptor.getMethod());
ParameterDescriptor[] parameterDescriptors = methodDescriptor.getParameterDescriptors();
if (null == parameterDescriptors) {
System.out.println();
continue;
}
for(int i = 0; i < parameterDescriptors.length; i++) {
System.out.print(", #" + i + "paramName" + parameterDescriptors[i].getName() + ", paramClass" + parameterDescriptors[i].getClass());
}
System.out.println();
}
} catch (IntrospectionException e) {
e.printStackTrace();
}
}
}
反射与内省的区别
内省操作只针对JavaBean,只有符合JavaBean规则的类的成员才可以采用内省API进行操作,而反射则不同,一个类的所有成员都可以进行反射操作。
内省和反射的操作也有很大不同,内省是先得到属性描述器PropertyDecriptor后再进行各种操作,反射则是先得到类的字节码Class后再进行各种操作的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。