反射与内省 --- 应该知道的常见用法

616 阅读11分钟

目录

反射 (Reflect)

在框架开发中,都是基于配置文件开发的,在配置文件中配置了类,可以通过读取配置文件中的类名,然后通过反射得到类中的所有内容,或是让类中的某个方法来执行。

也就是说,反射是在运行时获取一个类的所有信息,可以获取到 .class 的任何定义的信息(包括成员 变量,成员方法,构造器等)可以操纵类的字段、方法、构造器等部分。

反射的原理

  • 得到 class 文件
    • 把 java 文件保存到本地硬盘,得到 .java 文件
    • 编译 java 文件,得到 .class 文件
    • JVM 把 .class 文件加载到内存中,class 文件在内存中使用 Class 类表示
  • 通过 class 文件得到 Class 类,可以通过以下 3 种方式获得 Class 类
    • 通过成员变量获得: 类名.class
    • 通过具体对象获得: 对象.getClass()
    • 通过 Class 的静态方法获取: Class.forName("classFilePath")
  • 通过 Class 类获取 class 文件中的内容,包括:成员变量,构造方法,普通方法,它们都可以用相应的类表示:
    • 成员方法:Field
    • 构造方法:Constructor
    • 普通方法:Method

使用反射操作属性

public void test3() {
    try {
        Class c2 = Class.forName("cn.itcast.test09.Person");  // 得到Class类
        Person p11 = (Person) c2.newInstance();  // 得到Person类的对象,返回
        Field[] fields = c2.getDeclaredFields();  // 得到所有的属性,返回一个Field数组
        Field f1 = c2.getDeclaredField("name");  // 通过属性名称得到特定属性,参数是属性的名称

        // 如果操作的是私有的属性,不让操作,可以通过setAccessible(true)操作私有属性
        f1.setAccessible(true);
        f1.set(p11, "wangwu"); // 设置name值,相当于p.name = "wangwu";
        System.out.println(f1.get(p11)); // 相当于 p.name
    }catch(Exception e) {
        e.printStackTrace();
    }
}

使用反射操作构造函数(创建类的实例)

无参数构造方法

通过 Class 对象的 newInstance() 方法创建。

public void test1() throws Exception {
    Class c3 = Class.forName("cn.itcast.test09.Person");
    // 无参数的构造方法就是直接使用newInstance()方法
    Person p = (Person) c3.newInstance();
    p.setName("zhangsan");
    System.out.println(p.getName());
}

有参数构造方法

不能再通过 Class 对象的 newInstance() 方法创建了,要先得到要调用的构造函数的 Consturctor 对象,然后通过 Constructor 对象的 newInstance() 方法创建。

public void test2() throws Exception {
    Class c1 = Class.forName("cn.itcast.test09.Person");

    // 获取所有的构造方法
    Constructor[] css = c1.getConstructors();
    // 获取特定的构造方法:传递是有参数的构造方法里面参数类型,类型使用class的形式传递
    Constructor cs = c1.getConstructor(String.class, String.class);

    // 通过有参数的构造方法创建Person实例,而不是通过Class的对象
    Person p1 = (Person) cs.newInstance("lisi","100");
    System.out.println(p1.getId()+" "+p1.getName());
}

使用反射操作方法

实例方法

public void test4() throws Exception {
    Class c4 = Class.forName("cn.itcast.test09.Person");
    Person p4 = (Person) c4.newInstance();

    // 得到所有的普通方法
    Method[] mds = c4.getDeclaredMethods();
    // 得到特定的普通方法,传递两个参数:第一个参数:方法名称;第二个参数:方法里面参数的类型
    Method m1 = c4.getDeclaredMethod("setName", String.class);

    // 使用invoke执行方法,传递两个参数:第一个参数:person实例;第二个参数:设置的值
    // 在这里要传入person对象的原因是:我们需要知道到底是哪一个对象的setName方法执行了
    // 如果要操作的是私有的方法 ,需要 m1.setAccessible(true);
    m1.invoke(p4, "niuqi");
    System.out.println(p4.getName());
}

静态方法

静态方法调用方式是 类名.方法名,不需要类的实例,所以使用反射操作静态方法的时候,也是不需要实例的,在 invoke 方法的第一个参数传入 null 即可: m1.invoke(null, "niuqi");。 例如,我们利用反射操作一个主方法 main 方法(在学习《深入理解Java虚拟机》时遇到了这个用法:

Class clazz = ...
try {
	Method method = clazz.getMethod("main", new Class[] { String[].class});
	method.invoke(null, new String[] {null});
} catch (Throwable e){
	e.printStackTrace();
}

Modifier解析修饰符

利用反射我们可以获得类的属性、构造器、方法等,那么如何判断类或变量、方法的修饰符呢? Java针对类、成员变量、方法,有很多修饰符,例如public、private、static、final、synchronized、abstract等,这些修饰符用来控制访问权限或其他特性。

通过查看源码,我们发现,FieldMethodConstructor 都直接或间接实现了 Member 接口,看下Member接口:Member 表示一个类中的成员,包括成员变量、方法、构造方法三种实现。 Java 文档:

Interface Member 所有已知实现类: Constructor , Executable , Field , 方法 Member是一个反映关于单个成员(字段或方法)或构造函数的标识信息的接口。

Member接口有个方法:

int getModifiers() 作为整数返回由此 Member所表示的成员或构造方法的 Java语言修饰符。

我们想要得到的应该是一个表示修饰符的字符串,却得到的是一个整数,那么一个整数表示怎样的修饰符或者这个整数怎么转化为修饰符呢?

在这里,需要用到java.lang.reflect.Modifier这个类。Modifier提供了很多静态方法。

static String toString(int mod) 返回描述指定修饰符中的访问修饰符标志的字符串。 static boolean isPrivate(int mod) 如果整数参数包含 private修饰符,则返回 true , false返回false。
static boolean isProtected(int mod) 如果整数参数包含 protected修饰符,则返回 true , false false。
static boolean isPublic(int mod) 如果整数参数包含 public修饰符,则返回 true , false false。
static boolean isStatic(int mod) 如果整数参数包含 static修饰符,则返回 true , false false。

如 public static String toString(int mod) 就可以输出该整数对应的所有的修饰符。 public static boolean isPublic(int mod) 就可以判断该整数对应的是不是包含public修饰符。

我们再来看下 Modifier 源码:

	public static final int PUBLIC           = 0x00000001;
    public static final int PRIVATE          = 0x00000002;
    public static final int PROTECTED        = 0x00000004;
    public static final int STATIC           = 0x00000008;
    public static final int FINAL            = 0x00000010;
    public static final int SYNCHRONIZED     = 0x00000020;
    public static final int VOLATILE         = 0x00000040;
    public static final int TRANSIENT        = 0x00000080;
    public static final int NATIVE           = 0x00000100;
    public static final int INTERFACE        = 0x00000200;
    public static final int ABSTRACT         = 0x00000400;
    public static final int STRICT           = 0x00000800;
    static final int BRIDGE    = 0x00000040;
    static final int VARARGS   = 0x00000080;
    static final int SYNTHETIC = 0x00001000;
    static final int ANNOTATION  = 0x00002000;
    static final int ENUM      = 0x00004000;
    static final int MANDATED  = 0x00008000;

把它们转换成二进制,可以看出,其实 Modifier 使用一个二进制的位来表示是否包含某个修饰符。 而接口 Member 中有个方法,返回的整数就是该成员表示所有修饰符的数字的和的值:

    public int getModifiers();

内省 (Introspector)

内省是基于反射实现的,主要用于操作 JavaBean,相比反射使用起来要方便一些。可以获取 bean 的 getter/setter 方法,也就是说,只要 JavaBean 有 getXxx() 方法,不管这个 Bean 有没有 Xxx 属性,使用内省我们都认为它有。

为了更好的理解,在讲解内省之前,我们先来介绍一下 JavaBean 的规范。

JavaBean 的规范

  • JavaBean是一种特殊的类,主要用于传递数据信息。
  • 必须有一个public的无参数构造器。
  • 属性可以通过get、set、is(可以替代get,用在布尔型属性上)方法或遵循特定命名规范的其他方法访问。
  • 可序列化。
  • 提供 get/set 方法,如果只有 get 方法,那么这个属性是只读属性。
  • 属性:有 get/set 方法的成员,还可以没有成员,只有 get/set 方法。属性名称由 get/set 方法来决定,而不是成员名称。
  • 我的理解:属性是一个逻辑概念,能访问通过符合规则的方法访问到的就是属性! 实例代码:
/**
 * @author ycw
 */

import java.io.Serializable;

public class UserInfo implements Serializable {     //可序列化

    // 属性名:使用驼峰命名法。
    private String UserId;

    private String userName;

    private boolean isVIP;

    // 有一个public的无参数构造器。
    public UserInfo() {
    }

    public UserInfo(String userId, String userName, boolean isVIP) {
        UserId = userId;
        this.userName = userName;
        this.isVIP = isVIP;
    }

    public UserInfo(String userId, String userName) {
        UserId = userId;
        this.userName = userName;
    }

    public String getUserId() {
        return UserId;
    }

    public void setUserId(String userId) {
        UserId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    // is 可以替代 get,用在布尔型属性上
    public boolean isVIP() {
        return isVIP;
    }

    public void setVIP(boolean VIP) {
        isVIP = VIP;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "UserId='" + UserId + '\'' +
                ", userName='" + userName + '\'' +
                ", isVIP=" + isVIP +
                '}' ;
    }
}

MethodUtils

MethodUtils 通过反射访问对象的方法并且执行方法。

/**
 * @author ycw
 */
public class MethodUtilsTest {
    @Test
    /* MethodUtils通过反射访问对象的方法并且执行方法。*/
    public void test1() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException {
        UserInfo user1 = new UserInfo();
        // 直接通过反射获得类对象,方法,执行
        Class<?> clazz = Class.forName("org.simplespring.commonsBeanutilsTest.model.UserInfo");
        Method method = clazz.getMethod("setUserName", String.class);
        method.invoke(user1, "令狐冲");
        System.out.println(user1);

        // 通过方法名和参数类型获得可访问方法
        method = MethodUtils.getAccessibleMethod(UserInfo.class, "setUserName", String.class);
        method.invoke(user1, "东方不败");
        System.out.println(user1);

        // 可以直接通过invokeMethod执行方法
        MethodUtils.invokeMethod(user1, "setUserName", "任我行");
        System.out.println(user1);
    }
}

执行结果:

UserInfo{UserId='null', userName='令狐冲', isVIP=false}
UserInfo{UserId='null', userName='东方不败', isVIP=false}
UserInfo{UserId='null', userName='任我行', isVIP=false}

ConstructorUtils

ConstructorUtils 通过反射提供了构造方法相关的便捷操作方法。

/**
 * @author ycw
 */
public class ConstructorUtilsTest {
    @Test
    public void test2() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 直接通过反射获得类对象,构造器,创建实例
        Class<?> clazz = Class.forName("org.simplespring.commonsBeanutilsTest.model.UserInfo");
        Constructor<?> constructor1 = clazz.getConstructor(String.class, String.class, boolean.class);
        Object user1 = constructor1.newInstance("独孤九剑弟子1号", "令狐冲", true);
        System.out.println(user1);

        // ConstructorUtils 通过反射提供了构造方法获取的便捷操作方法。
        Constructor<UserInfo> constructor2 = ConstructorUtils.getAccessibleConstructor(UserInfo.class, new Class[]{String.class, String.class, boolean.class});
        UserInfo user2 = constructor2.newInstance("魔教1号", "东方不拜", true);
        System.out.println(user2);

        // ConstructorUtils 创建实例更简洁的方法
        UserInfo user3 = ConstructorUtils.invokeConstructor(UserInfo.class, new String[]{"魔教老1号", "任我行"});
        System.out.println(user3);
    }
}

执行结果:

UserInfo{UserId='独孤九剑弟子1号', userName='令狐冲', isVIP=true}
UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
UserInfo{UserId='魔教老1号', userName='任我行', isVIP=false}

PropertyUtils

PropertyUtils 通过反射提供了对象属性的便捷操作方法。

/**
 * @author ycw
 */
public class PropertyUtilsTest {
    @Test
    public void test3() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {

        UserInfo user1 = new UserInfo("魔教1号", "东方不拜", true);
        UserInfo user2 = new UserInfo("魔教老1号", "任我行");
        System.out.println(user1);
        System.out.println(user2);

        // 使用 PropertyUtils 将一个 bean 实例的所有属性全部拷贝到另一个实例上,注意属性会完全覆盖,使用时应注意!
        PropertyUtils.copyProperties(user2, user1);
        System.out.println(user2);
        
        // 为一个实例设置特定属性值
        PropertyUtils.setProperty(user1, "userName", "东方必败");
        System.out.println(user1);
    }
}

执行结果:

UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
UserInfo{UserId='魔教老1号', userName='任我行', isVIP=false}
UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
UserInfo{UserId='魔教1号', userName='东方必败', isVIP=true}

BeanUtils

BeanUtils 通过反射提供了Bean对象的便捷操作方法。

/**
 * @author ycw
 */
public class BeanUtilsTest {
    @Test
    public void test4() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        // BeanUtils 是一个更为通用的工具,通过反射提供了Bean对象的便捷操作方法。
        UserInfo user1 = new UserInfo("魔教1号", "东方不拜", true);
        UserInfo user2 = new UserInfo("魔教老1号", "任我行");

        // 可以代替 PropertyUtils 提供一些操作属性的方法
        // 复制属性
        BeanUtils.copyProperties(user2, user1);
        System.out.println(user2);
        // 获取属性
        System.out.println(BeanUtils.getProperty(user2, "userName"));
        // 设置属性
        BeanUtils.setProperty(user2, "userName", "任盈盈");
        System.out.println(user2);

        // 封装 Map 数据到 JavaBean 对象中
        UserInfo user3 = new UserInfo();
        Map<String, String> properties = new HashMap<>();
        properties.put("userId", "华山掌门");
        properties.put("userName", "岳不群");
        BeanUtils.populate(user3, properties);
        System.out.println(user3);
    }
}

执行结果:

UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
东方不拜
UserInfo{UserId='魔教1号', userName='任盈盈', isVIP=true}
UserInfo{UserId='华山掌门', userName='岳不群', isVIP=false}

ConvertUtils

ConvertUtils 提供了数据类型相互转换的方法。 最基本的 convert() 方法将一个字符串转化为特定类型的实例对象。

public static Object convert(String value, Class<?> clazz) {
        return ConvertUtilsBean.getInstance().convert(value, clazz);
    }

示例:

/**
 * @author ycw
 */
public class ConvertUtilsTest {
    @Test
    public void test5() {
        // 将一个字符串转化为特定类型的实例对象。
        String numStr = "123";
        int num = (int) ConvertUtils.convert(numStr, Integer.class);
        System.out.println(num);
    }
}

执行结果:

123

反射和内省的区别

反射就像给类照镜子,这个的所有信息会毫无保留的反射到镜子中,将这个类的所有信息照出来,能照出来就是有,照不出来就是没有,得到的东西都是客观真实存在的。

而内省的目的是找出 bean 的 getter 和 setter 以便操作这个 bean,所以只要看到有 getter 或者 setter 就认为这个类有那么一个字段,比如看到 getName() 内省就会认为这个类中有 name 字段,但事实上并不一定会有 name。