Java反射与内省

简介: Java反射与内省

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

类对象

方法 作用
static Class<?> forName(String className) 返回描述类名为className的Class对象
T newInstance() 返回这个类的一个新的实例
Field[] getFields() 返回public的域,包括超类
Field getField(String name) 返回指定名称的public的域
Field[] getDeclaredFields() 返回所有的域,不包括超类
Field getDeclaredField(String name) 返回指定名称的域,私有也可以返回
Method[] getMethods() 返回public的方法,包括超类
Method[] getDeclaredMethods() 返回所有的方法,不包括超类
Constructor<?>[] getConstructors() 返回public的构造器,不包括超类
Constructor<?>[] getDeclaredConstructors() 返回所有的构造器,不包括超类

java.lang.reflect.Constructor

构造函数

方法 作用
T newInstance(Object… initargs) 构造一个这个构造器所属类的新实例
Class getDeclaringClass() 返回一个用于描述类中定义的构造器、方法或域的Class对象
Class<?>[] getExceptionTypes() 返回一个描述方法抛出的异常类型的Class对象数组
int getModifiers() 返回一个描述构造器、方法或域的修饰符的整型数值,使用java.lang.reflect.Modifier类中的方法分析这个返回值
String getName() 返回一个描述构造器、方法或域的名称字符串
Class<?>[] getParameterTypes() 返回一个描述参数类型的Class对象数组

java.lang.reflect.Field

属性

方法 作用
Object get(Object obj) 返回obj对象中用Field对象表示的域值
void set(Object obj, Object value) 用一个新值value设置obj对象中Field对象表示的域

java.lang.reflect.Method

方法

方法 作用
Object invoke(Object obj, Object… args) 执行这个对象的方法

注意

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的属性、方法和事件提供了支持。

核心对象

对象 描述
BeanInfo Java Bean 信息类
PropertyDescriptor 属性描述类
MethodDescriptor 方法描述类
EventSetDescriptor 事件描述集合

快速入门

Java Bean

public class User {
    private String username;
    private Integer age;
    // getter/setter
    // toString
}

Test Demo

@Test
public 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) 这个方法。

类型转换

核心对象

对象 描述
PropertyEditor 属性编辑器顶层接口
PropertyEditorSupport 属性编辑器实现类
PropertyEditorManager 属性编辑器管理器

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 获取属性的写方法通过反射去赋值。

@Test
public 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) {
    }
}

事件监听

核心对象

对象 描述
PropertyChangeEvent 属性变化事件
PropertyChangeListener 属性(生效)变化监听器
PropertyChangeSupport 属性(生效)变化监听器管理器
VetoableChangeListener 属性(否决)变化监听器
VetoableChangeSupport 属性(否决)变化监听器管理器

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

@Test
public 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

@Test
public 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

@Test
public 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,,null
java.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

@Test
public 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 内省机制。

相关文章
|
13天前
|
存储 Java
[Java]反射
本文详细介绍了Java反射机制的基本概念、使用方法及其注意事项。首先解释了反射的定义和类加载过程,接着通过具体示例展示了如何使用反射获取和操作类的构造方法、方法和变量。文章还讨论了反射在类加载、内部类、父类成员访问等方面的特殊行为,并提供了通过反射跳过泛型检查的示例。最后,简要介绍了字面量和符号引用的概念。全文旨在帮助读者深入理解反射机制及其应用场景。
11 0
[Java]反射
|
2月前
|
安全 Java 索引
Java——反射&枚举
本文介绍了Java反射机制及其应用,包括获取Class对象、构造方法、成员变量和成员方法。反射允许在运行时动态操作类和对象,例如创建对象、调用方法和访问字段。文章详细解释了不同方法的使用方式及其注意事项,并展示了如何通过反射获取类的各种信息。此外,还介绍了枚举类型的特点和使用方法,包括枚举的构造方法及其在反射中的特殊处理。
59 9
Java——反射&枚举
|
25天前
|
安全 Java 测试技术
🌟Java零基础-反射:从入门到精通
【10月更文挑战第4天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
23 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
22天前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
14 0
|
2月前
|
Java 程序员 编译器
Java的反射技术reflect
Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
|
2月前
|
存储 安全 Java
扫盲java基础-反射(一)
扫盲java基础-反射(一)
|
2月前
|
Java
扫盲java基础-反射(二)
扫盲java基础-反射(二)
|
4月前
|
安全 Java 测试技术
day26:Java零基础 - 反射
【7月更文挑战第26天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
34 5
|
3月前
|
缓存 安全 Java
【Java 第十篇章】反射
Java 反射技术让程序能在运行时动态获取类信息并操作对象,极大提升了灵活性与扩展性。本文将介绍反射的基本概念、原理及应用,包括如何使用 `Class`、`Field`、`Method` 和 `Constructor` 类进行动态操作。此外,还将探讨反射在动态加载、框架开发与代码测试中的应用场景,并提醒开发者注意性能与安全方面的问题,帮助你更合理地运用这一强大工具。
28 0