Java拾遗:006 - Java反射与内省

反射

Java中提供了一套精心设计的工具集,以便编写能够动态操纵Java代码的程序,这套工具集就是反射(reflective)。

反射作用

能够分析类能力的程序称为反射,反射机制功能非常强大,甚至能破坏程序可见性(访问并操纵私有变量和方法)。 反射机制可以用来:

  • 在运行中分析类的能力(比如第三方类库里的类,手上又没有源码的情况下)
  • 在运行中查看、操纵对象,如:编写一个通用的toString方法供所有类使用
  • 实现通用的数组操作代码
  • 利用Method对象,实现类似C++中的方法指针功能(表示笔者没用过)
反射API

反射的API内容比较多,类在运行时的所有信息都有对应的反射类表示,这些类大部分在java.langjava.lang.reflect包下: 下面示例代码中少一部分代码,全部都在:https://github.com/zlikun-jee/effective-java 中(com.zlikun.jee.j006包下)。

  • 表示类的java.lang.Class
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({TYPE})
    @interface MyAnnotation {
        String value() default "Hello !";
    }

    @MyAnnotation("我是一个内部类!")
    static class MyType {

        private String name;

        static {
            System.out.println("静态代码块");
        }

        {
            System.out.println("非静态代码块");
        }

        public MyType() {
            System.out.println("无参构造方法");
        }
    }

    @Test
    public void clazz() throws ClassNotFoundException, IllegalAccessException, InstantiationException {

        Class<MyType> clazz = MyType.class;

        // 使用 Class.forName() 加载类的时候,只会执行静态代码块(不会构造类实例)
        // 实际上使用 Class.forName() 加载类返回类实例等价于直接使用类.class返回类实例,区别在于后者不会执行静态代码块
        assertEquals(clazz, Class.forName(clazz.getName()));

        // 返回类全名(在使用内部类、匿名类时,中间使用$分隔,如果是外部类则是.,与getCanonicalName方法一致)
        assertEquals("com.zlikun.jee.j006.ReflectiveTest$MyType", clazz.getName());
        // 返回类标准名称
        assertEquals("com.zlikun.jee.j006.ReflectiveTest.MyType", clazz.getCanonicalName());
        // 返回类名(简称,只包含类名,不包含包名)
        assertEquals("MyType", clazz.getSimpleName());

        // 使用无参构造方法构造一个实例(如果要使用参数构造实例,需要使用Constructor实例来实现)
        // 构造实例时会执行非静态代码块和无参构造方法
        MyType mt = clazz.newInstance();
        assertNull(mt.name);

    }
  • 表示注解的java.lang.annotation.Annotation
    @Test
    public void annotations() {
        Class<MyType> clazz = MyType.class;

        // 获取类注解列表
        Annotation[] annotations = clazz.getAnnotations();

        // @com.zlikun.jee.j006.ReflectiveTest$MyAnnotation(value=我是一个内部类!)
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        // 获取指定类注解
        MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
        // @com.zlikun.jee.j006.ReflectiveTest$MyAnnotation(value=我是一个内部类!)
        System.out.println(annotation);

        // interface com.zlikun.jee.j006.ReflectiveTest$MyAnnotation
        System.out.println(annotation.annotationType());
        // @com.zlikun.jee.j006.ReflectiveTest$MyAnnotation(value=我是一个内部类!)
        System.out.println(annotation.toString());
        // class com.zlikun.jee.j006.$Proxy4
        System.out.println(annotation.getClass());

        // 获取该注解属性值
        // 我是一个内部类!
        System.out.println(annotation.value());

    }
  • 表示ClassLoader的java.lang.ClassLoader
    @Test
    public void classLoader() {
        Class<MyType> clazz = MyType.class;

        // 返回加该类的ClassLoader
        // sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(clazz.getClassLoader());

        // 其与当前测试类使用了同一个ClassLoader
        assertEquals(this.getClass().getClassLoader(), clazz.getClassLoader());
    }
  • 表示包的java.lang.Package
    @Test
    public void package0() {
        Class<MyType> clazz = MyType.class;
        Package package0 = clazz.getPackage();
        // package com.zlikun.jee.j006
        System.out.println(package0);
    }
  • 表示构造方法的java.lang.reflect.Constructor
    @Test
    public void constructors() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<Employee> clazz = Employee.class;

        System.out.println("--getConstructors--");
        // 返回构造方法列表,只返回当前类的public构造方法,不包含私有构造方法和其父类的构造方法
        Constructor[] constructors = clazz.getConstructors();
        // public com.zlikun.jee.j006.Employee(java.lang.String,java.lang.Long)
        for (Constructor<Employee> constructor : constructors) {
            System.out.println(constructor);
        }

        System.out.println("--getDeclaredConstructors--");
        // 返回当前类定义的全部构造方法(包含private和protected及包级访问权限的),不包含其父类的构造方法
        constructors = clazz.getDeclaredConstructors();
        // public com.zlikun.jee.j006.Employee(java.lang.String,java.lang.Long)
        // protected com.zlikun.jee.j006.Employee()
        // private com.zlikun.jee.j006.Employee(java.lang.Long)
        for (Constructor<Employee> constructor : constructors) {
            System.out.println(constructor);
        }

        // 如果要获取父类构造方法,需要先获取父类的类对象
        // class com.zlikun.jee.j006.Person
        Class<? super Employee> superClazz = clazz.getSuperclass();
        System.out.println(superClazz);
        // 获取父类的构造方法
        System.out.println("--superClazz.getDeclaredConstructors--");
        // public com.zlikun.jee.j006.Person()
        // public com.zlikun.jee.j006.Person(java.lang.String)
        for (Constructor<?> constructor : superClazz.getDeclaredConstructors()) {
            System.out.println(constructor);
        }
        System.out.println("--end--");

        // 根据参数类型列表返回构造方法,无参构造方法不用传入类型参数
        Constructor<Employee> constructor = clazz.getConstructor(String.class, Long.class);
        // public com.zlikun.jee.j006.Employee(java.lang.String,java.lang.Long)
        System.out.println(constructor);

        // 构造一个实例
        Employee employee = constructor.newInstance("zlikun", 350000L);
        // zlikun salary is 350000 fen
        System.out.printf("%s salary is %d fen%n", employee.getName(), employee.getSalary());

        // 能否使用private构造方法构造一个类实例
        // 首先使用getDeclaredConstructor方法才能得到这个实例
        constructor = clazz.getDeclaredConstructor(Long.class);
        // 其次修改其访问权限为true,才能使用该构造方法对象构造实例,这一点对属性、方法也适用
        constructor.setAccessible(true);
        employee = constructor.newInstance(240000L);
        assertEquals(Long.valueOf(240000L), employee.getSalary());
    }
  • 表示字段的java.lang.reflect.Field
    @Test
    public void fields() throws NoSuchFieldException, IllegalAccessException {
        Employee employee = new Employee("Ashe", 180000L);
        Class<? extends Employee> clazz = employee.getClass();

        // 同构造方法类似,只能取得仅有属性,且不包含父类属性
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        // 取得所有属性,但不包含父类属性
        fields = clazz.getDeclaredFields();
        // private java.lang.Long com.zlikun.jee.j006.Employee.salary
        for (Field field : fields) {
            System.out.println(field);
        }

        // 获取单个属性
        Field field = clazz.getDeclaredField("salary");
        assertEquals("salary", field.getName());
        assertEquals("private java.lang.Long com.zlikun.jee.j006.Employee.salary", field.toString());
        assertEquals("private java.lang.Long com.zlikun.jee.j006.Employee.salary", field.toGenericString());

        // 获取字段值(字段是私有的,所以需要设置访问权限)
        field.setAccessible(true);
        assertEquals(Long.valueOf(180000L), field.get(employee));

    }
  • 表示方法的java.lang.reflect.Method
    @Test
    public void methods() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Employee employee = new Employee("Ashe", 180000L);
        Class<? extends Employee> clazz = employee.getClass();

        // 与构造方法、属性不同,getMethods返回了所有public方法,包含从父类继承的(当然也包含Object类的)
        /* ----------------------------------------------------------------------------------------------
            public java.lang.Long com.zlikun.jee.j006.Employee.raise(long)
            public void com.zlikun.jee.j006.Employee.setSalary(java.lang.Long)
            public java.lang.Long com.zlikun.jee.j006.Employee.getSalary()
            public java.lang.String com.zlikun.jee.j006.Person.getName()
            public void com.zlikun.jee.j006.Person.setName(java.lang.String)
            public final void java.lang.Object.wait() throws java.lang.InterruptedException
            public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
            public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
            public boolean java.lang.Object.equals(java.lang.Object)
            public java.lang.String java.lang.Object.toString()
            public native int java.lang.Object.hashCode()
            public final native java.lang.Class java.lang.Object.getClass()
            public final native void java.lang.Object.notify()
            public final native void java.lang.Object.notifyAll()
        ---------------------------------------------------------------------------------------------- */
        System.out.println("--getMethods--");
        for (Method method : clazz.getMethods()) {
            System.out.println(method);
        }

        // getDeclaredMethods则仍只获取了当前类的方法(与访问范围无关)
        /* ----------------------------------------------------------------------------------------------
            public java.lang.Long com.zlikun.jee.j006.Employee.raise(long)
            public java.lang.Long com.zlikun.jee.j006.Employee.getSalary()
            public void com.zlikun.jee.j006.Employee.setSalary(java.lang.Long)
            private void com.zlikun.jee.j006.Employee.tip(long)
        ---------------------------------------------------------------------------------------------- */
        System.out.println("--getDeclaredMethods--");
        for (Method method : clazz.getDeclaredMethods()) {
            System.out.println(method);
        }

        System.out.println("--end--");

        // 获取父类里的单个方法
        Method method = clazz.getMethod("setName", String.class);
        // 执行这个方法
        method.invoke(employee, "Peter");
        // 查找执行效果
        assertEquals("Peter", employee.getName());

        // 获取当前类里的私有方法
        method = clazz.getDeclaredMethod("tip", long.class);
        method.setAccessible(true);
        // Peter偷偷跟客户要了小费(反正我是私有的,老板看不到^_^)。
        Object r = method.invoke(employee, 1200L);
        // 1200
        System.out.println(r);

    }
反射性能

关于反射机制会影响性能,这点毋庸置疑,但影响到底有多大,这个没有一个统一的说法,我做了一个简单的测试

    @Test
    public void test() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        // 2,147,483,647
        int loop = Integer.MAX_VALUE;

        Employee employee = new Employee("Ashe", 120000L);

        long time = System.currentTimeMillis();
        for (int i = 0; i < loop; i++) {
            employee.setName("Peter");
        }
        // 不用反射程序执行耗时:5455毫秒
        System.out.printf("不用反射程序执行耗时:%d毫秒%n", System.currentTimeMillis() - time);

        time = System.currentTimeMillis();
        Class<Employee> clazz = Employee.class;
        Method method = clazz.getMethod("setName", String.class);
        for (int i = 0; i < loop; i++) {
            method.invoke(employee, "Peter");
        }
        // 不用反射程序执行耗时:9555毫秒
        System.out.printf("不用反射程序执行耗时:%d毫秒%n", System.currentTimeMillis() - time);


    }

测试可以看出,使用反射设置属性比直接调用setter慢了接近一倍的样子,但在实际应用中,这点开销可以忽略不计(慢一倍还能忽略不计?那得看基准是什么,平均一微秒执行约224.7次,你没看错,我说的是微秒,所以可不就是忽略不计么)。 关于以上说法我留一点保留意见,在高并发的场景下,还是要谨慎使用反射API。原因是在JDK1.6版的Method类中invoke方法有使用synchronized加锁,但在JDK1.7及以后的版本中移除了。 所以是否在高并发下使用反射要视你的JDK版本而定,最好是做一下压力测试再决定是否使用。

内省

内省(Introspector)是 Java 语言对 Bean 类属性、事件的一种缺省处理方法,对是反射API的高级封装。其相比反射API更为易用,但功能相对受限,仅针对JavaBean使用。

内省作用

内省的作用主要体现在对JavaBean的动态操纵上,一般用于属性值的读取和设置,常用在属性复制、对象克隆等场景里。

内省API

内省的API相对比反射的简单一些(只是上层封装部分,内部操作仍是反射)。

public class IntrospectionTest {

    @Test
    public void testBeanDescriptor() {
        BeanDescriptor descriptor = new BeanDescriptor(Employee.class);

        assertEquals(Employee.class, descriptor.getBeanClass());
        assertEquals(null, descriptor.getCustomizerClass());
        assertEquals("Employee", descriptor.getDisplayName());
        assertEquals("Employee", descriptor.getName());
        assertEquals("Employee", descriptor.getShortDescription());
        assertFalse(descriptor.isHidden());
        System.out.println(descriptor.hashCode());
        Enumeration<String> enums = descriptor.attributeNames();
        while (enums.hasMoreElements()) {
            System.out.println(enums.nextElement());
        }
    }

    @Test
    public void testPropertyDescriptor() throws IntrospectionException, InvocationTargetException, IllegalAccessException {


        // PropertyDescriptor 类表示JavaBean类通过存储器导出一个属性
        PropertyDescriptor descriptor = new PropertyDescriptor("name", Employee.class);

        // name
        System.out.println(descriptor.getName());
        // name
        System.out.println(descriptor.getDisplayName());
        // class java.lang.String
        System.out.println(descriptor.getPropertyType());
        // public java.lang.String com.zlikun.jee.j006.Person.getName()
        System.out.println(descriptor.getReadMethod());
        // public void com.zlikun.jee.j006.Person.setName(java.lang.String)
        System.out.println(descriptor.getWriteMethod());
        // 541214437
        System.out.println(descriptor.hashCode());
//        descriptor.setReadMethod(null);
//        descriptor.setWriteMethod(null);


        // 传入一个实例
        Employee employee = new Employee("Ashe", 180000L);

        // 操作类属性
        // setter
        Method writeMethod = descriptor.getWriteMethod();
        // 如果setter是不可见的,设置其可见性
        writeMethod.setAccessible(true);
        // 执行setter方法
        writeMethod.invoke(employee, "Peter");

        // getter
        Method readMethod = descriptor.getReadMethod();
        readMethod.setAccessible(true);
        Object result = readMethod.invoke(employee);
        // 测试其返回结果是否与类实例一致
        assertEquals(result, employee.getName());

    }

    @Test
    public void testBeanInfo() throws IntrospectionException {

        // 这是一个工具类,用于获取Bean信息实例
        BeanInfo info = Introspector.getBeanInfo(Employee.class);

        BeanDescriptor beanDescriptor = info.getBeanDescriptor();
        System.out.println(beanDescriptor);

        PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
        System.out.println(propertyDescriptors);

        MethodDescriptor[] methodDescriptors = info.getMethodDescriptors();
        System.out.println(methodDescriptors);

    }

}

应用

反射和内省通常用于底层工具或框架开发,应用开发几乎用不到,所以专注于应用开发的童鞋可以忽略(我当然是建议多少要懂一些的)。下面实现了几个案例,主要是测试反射和内省API,实际开发中不建议直接使用(有很多专业的第三方类库提供了同样功能,如:commons-beanutilsSpring FrameworkGuava等)。

通用ToString实现
public class ToStringHelper {

    public static final <T> String genericToString(T t) {
        if (t == null) return null;

        // 获取类信息
        Class<?> clazz = t.getClass();

        // 获取所有字段
        Set<Field> fields = new HashSet<>();
        fields.addAll(Arrays.stream(clazz.getDeclaredFields()).collect(Collectors.toSet()));
        fields.addAll(Arrays.stream(clazz.getFields()).collect(Collectors.toSet()));

        // 获取字段,组装字符串
        StringBuilder builder = new StringBuilder(clazz.getCanonicalName());
        builder.append(" : {");
        for (Field field : fields) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }

            try {
                Object value = field.get(t);
                if (value != null) {
                    builder.append(field.getName()).append(" = ")
                            .append(field.getType().isPrimitive() ? value : value.toString())
                            .append(", ");
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return builder.substring(0, builder.length() - 2) + "}";
    }

    /**
     * 使用Introspection机制实现ToString
     *
     * @param t
     * @param <T>
     * @return
     */
    public static final <T> String genericToString2(T t) {
        try {
            // 获取Bean信息
            BeanInfo info = Introspector.getBeanInfo(t.getClass());

            // 提取全部属性
            PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
            // 遍历属性描述符,取得属性值
            StringBuilder builder = new StringBuilder(t.getClass().getCanonicalName());
            builder.append(" : {");

            for (PropertyDescriptor descriptor : descriptors) {
                if (descriptor.getName().equals("class")) {
                    continue;
                }
                Method method = descriptor.getReadMethod();
                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }
                try {
                    Object value = method.invoke(t);
                    if (value != null) {
                        builder.append(descriptor.getName()).append(" = ")
                                .append(method.getReturnType().isPrimitive() ? value : value.toString())
                                .append(", ");
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

            return builder.substring(0, builder.length() - 2) + "}";
        } catch (IntrospectionException e) {
            e.printStackTrace();
        }
        return null;
    }

}
属性拷贝工具类(Spring)源码解读
public class BeanHelper {

    /**
     * 从Spring源码(org.springframework.beans.BeanUtils)中抄来的(去除了外部组件依赖,和不重要的参数),这里对其进行解读
     *
     * @param source
     * @param target
     * @throws Exception
     */
    public static final void copyProperties(Object source, Object target) throws Exception {


        // 获取目标类型属性描述符列表
        PropertyDescriptor[] targetPds = getPropertyDescriptors(target.getClass());

        // 遍历属性描述符列表
        for (PropertyDescriptor targetPd : targetPds) {

            // 判断其是否包含读方法(getter)
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null) {
                // 根据属性名,从源类中找到对应的属性及其属性描述符
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                // 如果存在则进行属性复制
                if (sourcePd != null) {
                    // 获取源类型的属性读方法,判断其是否存在
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null &&
                            isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            // 判断、修改读方法可见性
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            // 调用读方法,返回属性值
                            Object value = readMethod.invoke(source);
                            // 判断、修改写方法可见性
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            // 调用写方法,写入属性值
                            writeMethod.invoke(target, value);
                        } catch (Throwable ex) {
                            throw new Exception(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }
    }

    private static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String name) throws IntrospectionException {
        return new PropertyDescriptor(name, clazz);
    }

    private static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws IntrospectionException {
        return Introspector.getBeanInfo(clazz).getPropertyDescriptors();
    }

    private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new IdentityHashMap<>(8);
    private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<>(8);

    private static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) {
        if (lhsType.isAssignableFrom(rhsType)) {
            return true;
        }
        if (lhsType.isPrimitive()) {
            Class<?> resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType);
            if (lhsType == resolvedPrimitive) {
                return true;
            }
        } else {
            Class<?> resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType);
            if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) {
                return true;
            }
        }
        return false;
    }

}

代码挺多,但思路其实很简单(Spring内部做了很多优化,比如使用缓存,这部分代码被删掉了),就使用内省API获取源对象类属性和目标对象类属性,读取源对象属性值,写入到目标对象属性中。 虽然Spring提供的工具类已经很好用,但实际应用中还是有改进空间,比如我曾经在应用中遇到复制属性时需要过滤空值(默认实现未提供这一特性),我在调用读方法得到属性值后面加了一句判断,如果值为空,则跳过后面的逻辑,避免源对象的空值覆盖了目标对象同名属性(可能有值)。

结语

关于反射和内省,一般还是写框架、库或者工具类的时候会用得多,应用开发很少会用到(不排除有喜欢用的),所以一般不要求必须掌握,但个人还是建议能掌握尽量掌握,不能掌握也要了解,至少别人写了你得能看得懂。

代码仓库:

转载于:https://my.oschina.net/zhanglikun/blog/1922508

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值