1

最近在看Spring IOC容器的源码,发现里面有使用到java的反射和内省机制,在这里记录和总结一下。

反射(reflection)机制

什么是反射

反射是Java 编程语言中的一个特性。它允许正在执行的Java 程序检查或“内省”自身,并操纵程序的内部属性。 例如,Java类可以获取其所有成员的名称并显示它们。因此,反射提供了以下一些功能

  • 获取某个类定义的方法信息,包括方法名,参数,返回值等信息。
  • 获取某个类的构造函数。
  • 获取某个类的属性。
  • 通过方法名调用类的相应的方法。
  • 修改对象的属性值。
  • 创建某个类对应的对象。

反射的涉及到的类与接口

所有跟反射相关的类与接口都在java.lang.reflect包下面,涉及到的类与接口也比较多,也无法全部介绍,下面主要介绍Class相关的。

一个类中主要包含有构造函数(Constructor), 方法(Method), 字段(Field),修饰符(Modifier), 接口(Interface), 父类(SuperClass)等组成部分。

下面以Constructor和Method类为例,这两个类都是继承自同一个接口Executable,其关系如下:
image.png

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文件。

反射的使用

反射的使用步骤:

  1. 创建一个Class

    // 第一种方式
    Class<?> cls1 = Student.class;
    // 第二种方式
    Class<?> cls2 = Class.forName("com.example.Student")
  2. 使用反射
    具体的示例如下:
    获取所有的方法信息。

    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 等的公共基类,它支持一些可以设置和获取任意的内省描述符的公共信息。
image.png

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后再进行各种操作的。


风是客
19 声望1 粉丝