写点什么

大多数人不知道的 Java 知识 - Java 内省机制

发布于: 2021 年 03 月 23 日
大多数人不知道的Java知识 - Java内省机制

Java 内省机制


在计算机科学中,内省是指计算机程序在运行时(Runtime)检查对象(Object)类型的一种能力,也叫运行时类型检查。 内省和反射是不同概念。相对于内省,反射更进一步,反射是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。内省底层是利用了反射原理。


Java 内省(Introspector)是 Java 语言对 Bean 类属性、方法,事件的一种缺省处理方法。例如类 Person 中有属性 name,那我们可以通过 getName,setName 来得到其值或者设置新的值。通过 getName/setName 来访问 name 属性,这就是默认的规则。Java 中提供了一套 API 用来访问某个属性的 getter/setter 方法,通过这些 API 可以使你不需要了解这个规则(但你最好还是要搞清楚),这些 API 存放于包 java.beans 中。


一般的做法是通过类 Introspector 来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器 PropertyDescriptor,通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,最后就可以通过反射机制来调用这些方法了。


Introspector


官方介绍


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


For each of those three kinds of information, the Introspector will separately analyze the bean's class and superclasses looking for either explicit or implicit information and use that information to build a BeanInfo object that comprehensively describes the target bean.


java.beans.Introspector,即内省。它提供了一套标准的访问 Java Bean 的属性事件以及方法的处理方法。 对于 Java Bean 的这 3 种信息,Introspector 会分析 Java Bean 以及它的父类的显示和隐式的信息,然后构建一个全面描述此 Java Bean 的BeanInfo对象。


在 Java 中,JavaBean 是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的属性,且方法名符合某种命名规则。例如 DTO,VO 等,我们在业务或者模块之间传递信息,可以将信息封装到 JavaBean 中。


既然封装到 JavaBean 中,那就会有设置(setter)和读取(getter)JavaBean 中私有属性等操作。Introspector 可以帮我们做到这件事,不过要注意,JavaBean中的getter和setter等方法要遵循某种规范。(驼峰规则)


package com.nobody;
/** * @Description JavaBean类 * @Author Mr.nobody * @Date 2021/1/24 * @Version 1.0 */public class Person { private String id; private String name; private int age; public Person(String id, String name, int age) { this.id = id; this.name = name; this.age = age; }
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }}
复制代码


package com.nobody;
import java.beans.*;
/** * @Description * @Author Mr.nobody * @Date 2021/3/23 * @Version 1.0.0 */public class Demo { public static void main(String[] args) throws IntrospectionException { // 不内省父类的信息,第二个参数stopClass代表从stopClass开始往上的父类不再内省 BeanInfo beanInfo = Introspector.getBeanInfo(Person.class, Object.class); // 会内省起所有父类的信息 BeanInfo includeParentBeanInfo = Introspector.getBeanInfo(Person.class); }}
复制代码


BeanInfo


官方介绍


Use the BeanInfo interface to create a BeanInfo class and provide explicit information about the methods, properties, events, and other features of your beans.


java.beans.BeanInfo是一个接口,它有几个默认的实现类,我们一般默认生成的 BeanInfo 对象其实是 GenericBeanInfo 类的实例。简而言之,BeanInfo 对象能提供关于 JavaBean 的方法,属性,事件以及其他特征的明确信息。 其主要方法如下:

  • getPropertyDescriptors():获得所有属性描述器。

  • getBeanDescriptor():获得对象描述器。

  • getMethodDescriptors():获得所有方法描述器。

  • getEventSetDescriptors():获得所有事件描述器。


package com.nobody;
import java.beans.*;
/** * @Description * @Author Mr.nobody * @Date 2021/3/23 * @Version 1.0.0 */public class Demo { public static void main(String[] args) throws IntrospectionException { // 不内省父类的信息,第二个参数stopClass代表从stopClass开始往上的父类不再内省 BeanInfo beanInfo = Introspector.getBeanInfo(Person.class, Object.class); BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor(); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors(); EventSetDescriptor[] eventSetDescriptors = beanInfo.getEventSetDescriptors(); }}
复制代码


BeanDescriptor


官方介绍


A BeanDescriptor provides global information about a "bean", including its Java class, its displayName, etc.


This is one of the kinds of descriptor returned by a BeanInfo object, which also returns descriptors for properties, method, and events.


java.beans.BeanDescriptor,即对象描述器。它提供了一个 JavaBean 的全局信息,例如 JavaBean 的类型,类名等信息。


我们一般是从 BeanInfo 对象获取 BeanDescriptor 对象,不过也可以直接通过 new BeanDescriptor(Class<?> beanClass)构造函数获取。


package com.nobody;
import java.beans.*;
/** * @Description * @Author Mr.nobody * @Date 2021/3/23 * @Version 1.0.0 */public class Demo { public static void main(String[] args) throws IntrospectionException {
// 不内省父类的信息,第二个参数stopClass代表从stopClass开始往上的父类不再内省 BeanInfo beanInfo = Introspector.getBeanInfo(Person.class, Object.class); // 从BeanInfo对象获取BeanDescriptor对象 BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor(); // 通过new BeanDescriptor(Class<?> beanClass)构造函数获取BeanDescriptor对象 // BeanDescriptor beanDescriptor = new BeanDescriptor(Person.class);
Class<?> beanClass = beanDescriptor.getBeanClass(); Class<?> customizerClass = beanDescriptor.getCustomizerClass(); String displayName = beanDescriptor.getDisplayName(); String name = beanDescriptor.getName();
System.out.println("beanClass:" + beanClass); System.out.println("customizerClass:" + customizerClass); System.out.println("displayName:" + displayName); System.out.println("name:" + name);

PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors(); EventSetDescriptor[] eventSetDescriptors = beanInfo.getEventSetDescriptors(); }}
复制代码


输出结果如下:

beanClass:class com.nobody.PersoncustomizerClass:nulldisplayName:Personname:Person
复制代码


PropertyDescriptor


官方介绍


A PropertyDescriptor describes one property that a Java Bean exports via a pair of accessor methods.


java.beans.PropertyDescriptor,即属性描述器。描述了 Java Bean 的一个属性,通过一对读取方法。即 PropertyDescriptor 里面封装了 JavaBean 的其中一个属性的相关信息(例如属性名,属性类型,get 和 set 等方法)。其主要方法如下:

  • getName():获得属性名。

  • getPropertyType():获得属性类型。

  • getReadMethod():获得用于读取属性值的方法。

  • getWriteMethod():获得用于写入属性值的方法。

  • setReadMethod(Method readMethod):设置用于读取属性值的方法。

  • setWriteMethod(Method writeMethod):设置用于写入属性值的方法。


package com.nobody;
import java.beans.*;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.UUID;
/** * @Description * @Author Mr.nobody * @Date 2021/3/23 * @Version 1.0.0 */public class Demo { public static void main(String[] args) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
// 不内省父类的信息,第二个参数stopClass代表从stopClass开始往上的父类不再内省 BeanInfo beanInfo = Introspector.getBeanInfo(Person.class, Object.class);
Person person = new Person(UUID.randomUUID().toString(), "Mr_nobody", 18);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { Class<?> propertyType = propertyDescriptor.getPropertyType(); String propertyName = propertyDescriptor.getName(); Method readMethod = propertyDescriptor.getReadMethod(); Method writeMethod = propertyDescriptor.getWriteMethod();
System.out.println("属性名:" + propertyName); System.out.println("属性类型:" + propertyType); System.out.println("写方法名:" + writeMethod.getName()); System.out.println("读方法名:" + readMethod.getName());
if ("age".equals(propertyName)) { writeMethod.invoke(person, 20); } System.out.println("属性值:" + readMethod.invoke(person)); System.out.println("------------------------------------------"); } }}
复制代码


输出结果:

属性名:age属性类型:int写方法名:setAge读方法名:getAge属性值:20------------------------------------------属性名:id属性类型:class java.lang.String写方法名:setId读方法名:getId属性值:a6ccda55-c895-438e-893f-7fa448aba35a------------------------------------------属性名:name属性类型:class java.lang.String写方法名:setName读方法名:getName属性值:Mr_nobody------------------------------------------
复制代码


当然,除了从 BeanInfo 对象获取 PropertyDescriptor 对象,也可以直接 new 的方式获取。


PropertyDescriptor namePropertyDescriptor = new PropertyDescriptor("name", Person.class);
复制代码


MethodDescriptor


java.beans.MethodDescriptor,即方法描述器,通过它可以获取到类相关的方法,如下所示:


package com.nobody;
import java.beans.*;import java.lang.reflect.Method;import java.lang.reflect.Type;
/** * @Description * @Author Mr.nobody * @Date 2021/3/23 * @Version 1.0.0 */public class Demo { public static void main(String[] args) throws IntrospectionException {
// 不内省父类的信息,第二个参数stopClass代表从stopClass开始往上的父类不再内省 BeanInfo beanInfo = Introspector.getBeanInfo(Person.class, Object.class);
MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors(); for (MethodDescriptor methodDescriptor : methodDescriptors) {
Method method = methodDescriptor.getMethod(); System.out.println(method); System.out.println("方法名:" + method.getName());
Type[] genericParameterTypes = method.getGenericParameterTypes(); if (genericParameterTypes != null) { for (Type genericParameterType : genericParameterTypes) { System.out.println("方法参数类型:" + genericParameterType.getTypeName()); } }
Class<?> returnType = method.getReturnType(); System.out.println("方法返回类型:" + returnType.getTypeName()); System.out.println("---------------------------"); } }}
复制代码


输出结果如下:

public java.lang.String com.nobody.Person.getName()方法名:getName方法返回类型:java.lang.String---------------------------public void com.nobody.Person.setId(java.lang.String)方法名:setId方法参数类型:java.lang.String方法返回类型:void---------------------------public void com.nobody.Person.setAge(int)方法名:setAge方法参数类型:int方法返回类型:void---------------------------public void com.nobody.Person.setName(java.lang.String)方法名:setName方法参数类型:java.lang.String方法返回类型:void---------------------------public int com.nobody.Person.getAge()方法名:getAge方法返回类型:int---------------------------public java.lang.String com.nobody.Person.getId()方法名:getId方法返回类型:java.lang.String---------------------------public java.lang.String com.nobody.Person.toString()方法名:toString方法返回类型:java.lang.String---------------------------
复制代码


内省应用


在项目实战中,我们一般使用最多的是 IntrospectorBeanInfoPropertyDescriptor,这三者结合起来使用。


比如,我们通过内省可以实现,JavaBean 和 Map 互转,不同 JavaBean 对象属性拷贝等功能。


package com.nobody.util;
import java.beans.BeanInfo;import java.beans.IntrospectionException;import java.beans.Introspector;import java.beans.PropertyDescriptor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.*;
/** * @Description Bean工具类 * @Author Mr.nobody * @Date 2021/3/23 * @Version 1.0.0 */public class BeanUtils {
public static <T> Map<String, Object> beanToMap(T bean, boolean putIfNull) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
if (bean == null) { return new HashMap<>(); } Map<String, Object> returnMap = new HashMap<>();
// 获取bean的BeanInfo对象 BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass(), Object.class);
// 获取属性描述器 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 属性名 String propertyName = propertyDescriptor.getName(); // 获取该属性的值 Method readMethod = propertyDescriptor.getReadMethod(); // 属性的值 Object value = readMethod.invoke(bean); if (value == null && !putIfNull) { continue; } returnMap.put(propertyName, value); } return returnMap; }
public static <T> List<Map<String, Object>> beansToMaps(List<T> beans, boolean putIfNull) throws IllegalAccessException, IntrospectionException, InvocationTargetException {
if (null == beans || beans.size() == 0) { return new ArrayList<>(); }
List<Map<String, Object>> result = new ArrayList<>(beans.size() + 1); // 转换每一个bean for (Object bean : beans) { result.add(beanToMap(bean, putIfNull)); } return result; }
public static <T> T mapToBean(Map<String, Object> map, Class<T> clz) throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException {
// 生成bean实例 T bean = clz.newInstance(); if (null == map) { return bean; }
BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 属性名 String propertyName = propertyDescriptor.getName(); // 获取属性值 Object value = map.get(propertyName); // 写入属性值 Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(bean, value); } return bean; }
public static <T> List<T> mapsToBeans(List<Map<String, Object>> maps, Class<T> clz) throws InvocationTargetException, IntrospectionException, InstantiationException, IllegalAccessException {
if (null == maps || maps.size() == 0) { return new ArrayList<>(); }
List<T> result = new ArrayList<>(); for (Map<String, Object> map : maps) { result.add(mapToBean(map, clz));
} return result; }
public static <T1, T2> void copyProperties(T1 origin, T2 dest, boolean setNull, String[] excludeFieldNames) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
// 获取源类的BeanInfo对象 BeanInfo originBeanInfo = Introspector.getBeanInfo(origin.getClass(), Object.class); // 获取源类的属性描述器 PropertyDescriptor[] originPropertyDescriptors = originBeanInfo.getPropertyDescriptors();
// 获取目标类的BeanInfo对象 BeanInfo destBeanInfo = Introspector.getBeanInfo(dest.getClass(), Object.class); // 获取目标类的属性描述器 PropertyDescriptor[] destPropertyDescriptors = destBeanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : destPropertyDescriptors) { String propertyName = propertyDescriptor.getName(); // 是否需要排除的属性 boolean excludeField = false; if (excludeFieldNames != null) { for (String excludeFieldName : excludeFieldNames) { if (Objects.equals(excludeFieldName, propertyName)) { excludeField = true; break; } } } if (excludeField) { continue; } // 遍历源类的所有属性,如果存在此属性则进行拷贝 for (PropertyDescriptor originPropertyDescriptor : originPropertyDescriptors) { String originPropertyName = originPropertyDescriptor.getName(); if (Objects.equals(propertyName, originPropertyName)) { // 读取属性值 Method readMethod = originPropertyDescriptor.getReadMethod(); Object srcValue = readMethod.invoke(origin); if (srcValue != null || setNull) { // 设置属性值 Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(dest, srcValue); } break; } } } }
public static <T1, T2> void copyProperties(T1 origin, T2 dest) throws IllegalAccessException, IntrospectionException, InvocationTargetException { copyProperties(origin, dest, false, null); }}
复制代码


以上是我们手写的 JavaBean 相关的转换工具类,当然市场上已经有很多成熟的工具包了,例如 Apache 的 commons-beanutils 包,里面就提供了许多实际开发中的应用场景会用到的 API,大家不妨可以试用看看。


注意事项


开头提到 JavaBean 的 get/set 方法名要遵循某种规则,即驼峰规则。如下,我们将某个属性的 get 方法换个名字,例如将 id 属性的 get 方法名改为 getUid()。那么我们就获取不到属性 id 的读方法的,即取到的是 null。因为在取得 id 这个属性的属性描述器时,我们获取到了属性名,但是因为 get 方法没有遵循规则,所以调用getReadMethod()获取不到方法,所以出现空指针。




发布于: 2021 年 03 月 23 日阅读数: 31
用户头像

CSDN博客专家,微信搜一搜 - 陈皮的JavaLib 2020.02.22 加入

CSDN博客专家,专注各项技术领域的Java开发工程师,微信搜一搜【陈皮的JavaLib】,关注后学习更多技术文章和一线大厂面试资料和技术电子书籍。

评论

发布
暂无评论
大多数人不知道的Java知识 - Java内省机制