写点什么

Java 反射与内省 (参考小米内部资料)

用户头像
知春秋
关注
发布于: 2020 年 06 月 24 日

Java


反射


Class


​ Class 类的实例表示在运行中的 Java 应用程序的类和接口。enum是一个类,annotation是一个接口。每一个数组都是一个类,这个类由相同元素的数组和维数所共享。对于基础数据类型booleanbytecharshortintlongfloatdouble和关键字void都代表一个类。


​ 类没有公共的构造函数,那么 Java 虚拟机加载类的时候会调用 defineClass 方法来构造。


Bean.getClass.newInstance()方法默认调用无参构造函数初始化对象。如果没有就抛出一个异常。


java.lang.reflect.Constructor.newInstance(Object... param)可以通过带参构造函数初始化对象。


java.lang.reflect包下的三个类FieldMethodConstructor分别描述类的域、方法和构造器。


FieldgetType方法用于描述域所属类型的 Class 对象。


Class类中的getFieldsgetMethodsgetConstructors方法将返回public的域、方法和构造器数组,其中包括超类的public成员。


Class类的getDeclaredFieldsgetDeclaredMethodsgetDeclaredConstructorsDeclared方法将返回类中所有的域、方法和构造器数组。包括privateprotected成员,但是不包括超类的成员。


setAccessible()方法是AccessibleObject类中的一个方法,它是FieldMethodConstructor的公共超类。


构造函数创建对象


Class<?> aClass = Class.forName("com.lang.pojo.User");// 获取所有public的构造函数Constructor<?>[] constructors = aClass.getConstructors();// 获取所有的构造函数,包括private的Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();for (Constructor<?> declaredConstructor : declaredConstructors) {    // 设置暴力访问,如果不设置那么private方法就不可以访问    declaredConstructor.setAccessible(true);    int parameterCount = declaredConstructor.getParameterCount();    if (parameterCount == 0) {        // 没有参数,调用无参构造函数        Object o = declaredConstructor.newInstance();        System.out.println(o);    } else {        // 可以构建对象,参数为可变参数        Object o = declaredConstructor.newInstance("jack", 10, "美国加州");        System.out.println(o);    }}
复制代码


获取返回值类型


Class<?> aClass = Class.forName("com.lang.pojo.User");// 获取所有public的构造函数Constructor<?>[] constructors = aClass.getConstructors();// 获取所有的构造函数,包括private的Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();for (Constructor<?> declaredConstructor : declaredConstructors) {    // 设置暴力访问,如果不设置那么private方法就不可以访问    declaredConstructor.setAccessible(true);    // 获取方法的返回值类型    AnnotatedType type = declaredConstructor.getAnnotatedReturnType();    Type type1 = type.getType();    // 获取返回值的名称:com.lang.pojo.User    String typeName = type1.getTypeName();    System.out.println(typeName);}
复制代码


获取参数类型


Class<?> aClass = Class.forName("com.lang.pojo.User");// 获取所有public的构造函数Constructor<?>[] constructors = aClass.getConstructors();// 获取所有的构造函数,包括private的Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();for (Constructor<?> declaredConstructor : declaredConstructors) {    // 设置暴力访问,如果不设置那么private方法就不可以访问    declaredConstructor.setAccessible(true);    Type[] parameterTypes = declaredConstructor.getGenericParameterTypes();    for (Type type : parameterTypes) {        System.out.println(type.getTypeName());    }}
复制代码


核心方法汇集


java.lang.Class


类对象


java.lang.reflect.Constructor


构造函数


java.lang.reflect.Field


属性


java.lang.reflect.Method


方法


注意

java.lang.reflect 的类中很多方法都是通用的,这里列举出来的只是工作使用比较频繁的。如果对于 Java Bean 的操作可以使用内省技术更加便捷。


提示

​ 在启动时,包含 main 方法的类被加载。那么它就会加载所有需要的类。这些被加载的类又会继续加载它们需要的类,以此类推。对于一个大型的应用程序来说,这样程序启动就需要消耗很多的时间。不过可以确保 main 方法包含的类没有显式的引用其他类,等启动后调用Class.forName手动加载其他类。


内省


Java 官方对 Java Beans 内省的定义:


At runtime and in the builder environment we need to be able to figure out which properties, events, and methods a Java Bean supports. We call this process introspection.


从 Java Bean 的角度来看,这里的对象就是 Bean 对象,主要关注点是属性、方法和事件等,也就是说在运行时可以获取相应的信息进行一些处理,这就是 Java Beans 的内省机制。


与反射的区别


By default we will use a low level reflection mechanism to study the methods supported by a target bean and then apply simple design patterns to deduce from those methods what properties, events, and public methods are supported.


Java Beans 内省其实就是对反射的一种封装 。


Java Beans 内省机制


核心类库


Java Beans 内省机制的核心类是 Introspector


The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.


这个内省工具类提供了标准的工具方法对于了解 Java Bean 的属性、方法和事件提供了支持。


核心对象


快速入门


Java Bean


public class User {
private String username;
private Integer age;
// getter/setter // toString
}
复制代码


Test Demo


@Testpublic void test1() throws IntrospectionException {    //获取 User Bean 信息    BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class);    //属性描述    PropertyDescriptor[] propertyDescriptors = userBeanInfo.getPropertyDescriptors();    System.out.println("属性描述:");    Stream.of(propertyDescriptors).forEach(System.out::println);    //方法描述    System.out.println("方法描述:");    MethodDescriptor[] methodDescriptors = userBeanInfo.getMethodDescriptors();    Stream.of(methodDescriptors).forEach(System.out::println);    //事件描述    System.out.println("事件描述:");    EventSetDescriptor[] eventSetDescriptors = userBeanInfo.getEventSetDescriptors();    Stream.of(eventSetDescriptors).forEach(System.out::println);}
复制代码


Result Info


属性描述:java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer introspector.bean.User.getAge(); writeMethod=public void introspector.bean.User.setAge(java.lang.Integer)]java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()]java.beans.PropertyDescriptor[name=username; propertyType=class java.lang.String; readMethod=public java.lang.String introspector.bean.User.getUsername(); writeMethod=public void introspector.bean.User.setUsername(java.lang.String)]方法描述:java.beans.MethodDescriptor[name=getClass; method=public final native java.lang.Class java.lang.Object.getClass()]java.beans.MethodDescriptor[name=setAge; method=public void introspector.bean.User.setAge(java.lang.Integer)]java.beans.MethodDescriptor[name=getAge; method=public java.lang.Integer introspector.bean.User.getAge()]java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException]java.beans.MethodDescriptor[name=notifyAll; method=public final native void java.lang.Object.notifyAll()]java.beans.MethodDescriptor[name=notify; method=public final native void java.lang.Object.notify()]java.beans.MethodDescriptor[name=getUsername; method=public java.lang.String introspector.bean.User.getUsername()]java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait() throws java.lang.InterruptedException]java.beans.MethodDescriptor[name=hashCode; method=public native int java.lang.Object.hashCode()]java.beans.MethodDescriptor[name=setUsername; method=public void introspector.bean.User.setUsername(java.lang.String)]java.beans.MethodDescriptor[name=wait; method=public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException]java.beans.MethodDescriptor[name=equals; method=public boolean java.lang.Object.equals(java.lang.Object)]java.beans.MethodDescriptor[name=toString; method=public java.lang.String introspector.bean.User.toString()]事件描述:
复制代码


可以看出通过内省机制可以获取 Java Bean 的属性、方法描述,这里事件描述是空的(关于事件相关会在后面介绍)。由于 Java 类都会继承 Object 类,可以看到这里将 Object 类相关的属性和方法描述也输出了,如果想将某个类的描述信息排除可以使用 java.beans.Introspector#getBeanInfo(java.lang.Class, java.lang.Class) 这个方法。


类型转换


核心对象



Java Bean


public class User {
private String username;
private Integer age; private Date createTime;
// getter/setter // toString
}
复制代码


日期类型转换器


/** * 日期属性编辑器 */public class DatPropertyEditor extends PropertyEditorSupport {    @Override    public void setAsText(String text) {        try {            setValue((text == null) ? null : new SimpleDateFormat("yyyy-MM-dd").parse(text));        } catch (ParseException e) {            e.printStackTrace();        }    }}
复制代码


在之前的例子中内省设置属性值都是直接通过 PropertyDescriptor 获取属性的写方法通过反射去赋值,而如果需要对值进行类型转换,则需要通过 PropertyEditorSupport#setAsText 调用 setValue 方法,然后 setValue 方法触发属性属性修改事件:


public class PropertyEditorSupport implements PropertyEditor {    public void setValue(Object value) {        this.value = value;        firePropertyChange();    }}
复制代码


要注意这里的 value 实际上是临时存储在 PropertyEditorSupport 中,PropertyEditorSupport 则作为事件源,从而得到类型转换后的 value,再通过 PropertyDescriptor 获取属性的写方法通过反射去赋值。


@Testpublic void test6() throws IntrospectionException, FileNotFoundException {   Map<String,Object> properties = ImmutableMap.of("age",1,"username","zhangsan","createTime","2020-01-01");    User user = new User();    //获取 User Bean 信息,排除 Object    BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class, Object.class);    //属性描述    PropertyDescriptor[] propertyDescriptors = userBeanInfo.getPropertyDescriptors();    Stream.of(propertyDescriptors).forEach(propertyDescriptor -> {        //获取属性名称        String property = propertyDescriptor.getName();        //值        Object value = properties.get(property);        if (Objects.equals("createTime", property)) {            //设置属性编辑器            propertyDescriptor.setPropertyEditorClass(DatPropertyEditor.class);            //创建属性编辑器            PropertyEditor propertyEditor = propertyDescriptor.createPropertyEditor(user);            //添加监听器            propertyEditor.addPropertyChangeListener(evt -> {                //获取转换后的value                Object value1 = propertyEditor.getValue();                setPropertyValue(user, propertyDescriptor, value1);            });            propertyEditor.setAsText(String.valueOf(value));            return;        }        setPropertyValue(user, propertyDescriptor, value);    });    System.out.println(user);}
/** * 设置属性值 */private void setPropertyValue(User user, PropertyDescriptor propertyDescriptor, Object value1) { try { propertyDescriptor.getWriteMethod().invoke(user, value1); } catch (IllegalAccessException | InvocationTargetException ignored) { }}
复制代码


事件监听


核心对象



Java Bean


public class User {
private String username;
private Integer age;
/** * 属性(生效)变化监听器管理器 */ private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
/** * 启动属性(生效)变化 * @param propertyName * @param oldValue * @param newValue */ private void firePropertyChange(String propertyName, String oldValue, String newValue) { PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, oldValue, newValue); propertyChangeSupport.firePropertyChange(event); }
/** * 添加属性(生效)变化监听器 */ public void addPropertyChangeListener(PropertyChangeListener listener){ propertyChangeSupport.addPropertyChangeListener(listener); }
/** * 删除属性(生效)变化监听器 */ public void removePropertyChangeListener(PropertyChangeListener listener){ propertyChangeSupport.removePropertyChangeListener(listener); }
/** * 获取属性(生效)变化监听器 */ public PropertyChangeListener[] getPropertyChangeListeners() { return propertyChangeSupport.getPropertyChangeListeners(); }
public void setUsername(String username) { String oldValue = this.username; this.username = username; firePropertyChange("username", oldValue, username); } // getter/setter // toString}
复制代码


Test Demo


@Testpublic void test3(){    User user = new User();    user.setAge(1);    user.setUsername("zhangsan");    user.addPropertyChangeListener(System.out::println);    user.setUsername("lisi");    user.setUsername("wangwu");}
复制代码


Result


java.beans.PropertyChangeEvent[propertyName=name; oldValue=zhangsan; newValue=lisi; propagationId=null; source=User{username='lisi', age=1}]java.beans.PropertyChangeEvent[propertyName=name; oldValue=lisi; newValue=wangwu; propagationId=null; source=User{username='wangwu', age=1}]
复制代码


可以看到在添加了监听器后,当 username 属性发生变化的时候会出发监听事件。


再看看另外一种监听器 VetoableChangeListener。在 User 中添加监听器:


Java Bean(User)


/** * 属性(否决)变化监听器 */private VetoableChangeSupport vetoableChangeSupport = new VetoableChangeSupport(this);
/** * 启动属性(否决)变化 * @param propertyName * @param oldValue * @param newValue */private void fireVetoableChange(String propertyName, String oldValue, String newValue) throws PropertyVetoException { PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, oldValue, newValue); vetoableChangeSupport.fireVetoableChange(event);}
/** * 添加属性(否决)变化监听器 */public void addVetoableChangeListener(VetoableChangeListener listener){ vetoableChangeSupport.addVetoableChangeListener(listener);}
/** * 删除属性(否决)变化监听器 */public void removeVetoableChangeListener(VetoableChangeListener listener){ vetoableChangeSupport.removeVetoableChangeListener(listener);}
public void setUsername(String username) throws PropertyVetoException { String oldValue = this.username; fireVetoableChange("username",oldValue,username); this.username = username; firePropertyChange("username", oldValue, username);}
复制代码


Test Demo


@Testpublic void test3() throws PropertyVetoException {    User user = new User();    user.setAge(1);    user.addVetoableChangeListener(evt -> {        System.out.println(evt.getNewValue()+",,"+evt.getOldValue());        if (Objects.equals(evt.getNewValue(), evt.getOldValue())) {            throw new PropertyVetoException("当前属性值未发生任何变化", evt);        }    });    user.addPropertyChangeListener(System.out::println);    user.setUsername("lisi");    user.setUsername("zhangsan");    user.setUsername("zhangsan");}
复制代码


运行时发现一直无法抛出异常。查看源码发现 PropertyChangeSupportVetoableChangeSupport 当新旧值相等时不会触发监听,于是修改测试代码:


Test Demo


@Testpublic void test3() throws PropertyVetoException {    User user = new User();    user.setAge(1);    user.addVetoableChangeListener(evt -> {        System.out.println(evt.getNewValue()+",,"+evt.getOldValue());        if (Objects.isNull(evt.getNewValue())) {            throw new PropertyVetoException("username 不能为null", evt);        }    });    user.addPropertyChangeListener(System.out::println);    user.setUsername("lisi");    user.setUsername(null);}
复制代码


Result


lisi,,nulljava.beans.PropertyChangeEvent[propertyName=username; oldValue=null; newValue=lisi; propagationId=null; source=User{username='lisi', age=1}]null,,lisi
java.beans.PropertyVetoException: username 不能为null
at introspector.test.IntrospectorTest.lambda$test3$1(IntrospectorTest.java:78) at java.beans.VetoableChangeSupport.fireVetoableChange(VetoableChangeSupport.java:375)
复制代码


可以发现当符合“否决”属性变化的条件时,会抛出 PropertyVetoException 异常阻断属性的变化。


在之前的示例中 userBeanInfo 输出的 EventSetDescriptor 为空,这是因为并未到 User 类中增加事件。现在再测试一下获取 EventSetDescriptor


@Testpublic void test1() throws IntrospectionException {    BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class, Object.class);    EventSetDescriptor[] eventSetDescriptors = userBeanInfo.getEventSetDescriptors();    Stream.of(eventSetDescriptors).forEach(System.out::println);}
复制代码


java.beans.EventSetDescriptor[name=propertyChange; inDefaultEventSet; listenerType=interface java.beans.PropertyChangeListener; getListenerMethod=public java.beans.PropertyChangeListener[] introspector.bean.User.getPropertyChangeListeners(); addListenerMethod=public void introspector.bean.User.addPropertyChangeListener(java.beans.PropertyChangeListener); removeListenerMethod=public void introspector.bean.User.removePropertyChangeListener(java.beans.PropertyChangeListener)]java.beans.EventSetDescriptor[name=vetoableChange; inDefaultEventSet; listenerType=interface java.beans.VetoableChangeListener; addListenerMethod=public void introspector.bean.User.addVetoableChangeListener(java.beans.VetoableChangeListener); removeListenerMethod=public void introspector.bean.User.removeVetoableChangeListener(java.beans.VetoableChangeListener)]
复制代码


在 Java 生态飞速发展的今天,很多底层技术细节都被高级框架所屏蔽,而 Java Beans 就是其中一种。也许平时根本就用不到,但是其代码设计和思想理念不应该被忽视。Dubbo 2.7 之后提出了“服务自省”的概念,其灵感就来源于 Java Beans 内省机制。


发布于: 2020 年 06 月 24 日阅读数: 144
用户头像

知春秋

关注

不忘初心,方得始终。初心易得,始终难守。 2020.04.29 加入

Java高级工程师,从业多年主要负责互联网电商、金融系统开发与架构设计。对分布式存储、分布式缓存、分布式消息中间件、分布式搜索引擎 / 实时流式计算、容器技术有一定的研究和认识,对主流开源框架源码熟悉。

评论 (1 条评论)

发布
用户头像
辛苦了
2020 年 09 月 26 日 21:55
回复
没有更多了
Java反射与内省(参考小米内部资料)