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

简介: 大多数人不知道的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等方法要遵循某种规范。(驼峰规则)` ```java 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; } } ``` ```java 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():获得所有事件描述器。 ```java 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)构造函数获取。 ```java 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(); } } ``` 输出结果如下: ```bash beanClass:class com.nobody.Person customizerClass:null displayName:Person name: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):设置用于写入属性值的方法。 ```java 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("------------------------------------------"); } } } ``` 输出结果: ```bash 属性名: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的方式获取。 ```java PropertyDescriptor namePropertyDescriptor = new PropertyDescriptor("name", Person.class); ```
# MethodDescriptor java.beans.MethodDescriptor,即方法描述器,通过它可以获取到类相关的方法,如下所示: ```java 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("---------------------------"); } } } ``` 输出结果如下: ```bash 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 --------------------------- ```
# 内省应用 在项目实战中,我们一般使用最多的是 `Introspector`,`BeanInfo`,`PropertyDescriptor`,这三者结合起来使用。 比如,我们通过内省可以实现,JavaBean和Map互转,不同JavaBean对象属性拷贝等功能。 ```java 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 Map beanToMap(T bean, boolean putIfNull) throws IntrospectionException, InvocationTargetException, IllegalAccessException { if (bean == null) { return new HashMap<>(); } Map 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 List> beansToMaps(List beans, boolean putIfNull) throws IllegalAccessException, IntrospectionException, InvocationTargetException { if (null == beans || beans.size() == 0) { return new ArrayList<>(); } List> result = new ArrayList<>(beans.size() + 1); // 转换每一个bean for (Object bean : beans) { result.add(beanToMap(bean, putIfNull)); } return result; } public static T mapToBean(Map map, Class 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 List mapsToBeans(List> maps, Class clz) throws InvocationTargetException, IntrospectionException, InstantiationException, IllegalAccessException { if (null == maps || maps.size() == 0) { return new ArrayList<>(); } List result = new ArrayList<>(); for (Map map : maps) { result.add(mapToBean(map, clz)); } return result; } public static 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 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()`获取不到方法,所以出现空指针。 ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210323154141277.png?1x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW5saXhpYW8wMDc=,size_16,color_FFFFFF,t_70)
相关文章
|
设计模式 人工智能 安全
AQS:Java 中悲观锁的底层实现机制
AQS(AbstractQueuedSynchronizer)是Java并发包中实现同步组件的基础工具,支持锁(如ReentrantLock、ReadWriteLock)和线程同步工具类(如CountDownLatch、Semaphore)等。Doug Lea设计AQS旨在抽象基础同步操作,简化同步组件构建。 使用AQS需实现`tryAcquire(int arg)`和`tryRelease(int arg)`方法以获取和释放资源,共享模式还需实现`tryAcquireShared(int arg)`和`tryReleaseShared(int arg)`。
642 32
AQS:Java 中悲观锁的底层实现机制
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
816 0
|
人工智能 前端开发 安全
Java开发不可不知的秘密:类加载器实现机制
类加载器是Java中负责动态加载类到JVM的组件,理解其工作原理对开发复杂应用至关重要。本文详解类加载过程、双亲委派模型及常见类加载器,并介绍自定义类加载器的实现与应用场景。
465 4
|
Java 区块链 网络架构
酷阿鲸森林农场:Java 区块链系统中的 P2P 区块同步与节点自动加入机制
本文介绍了基于 Java 的去中心化区块链电商系统设计与实现,重点探讨了 P2P 网络在酷阿鲸森林农场项目中的应用。通过节点自动发现、区块广播同步及链校验功能,系统实现了无需中心服务器的点对点网络架构。文章详细解析了核心代码逻辑,包括 P2P 服务端监听、客户端广播新区块及节点列表自动获取等环节,并提出了消息签名验证、WebSocket 替代 Socket 等优化方向。该系统不仅适用于农业电商,还可扩展至教育、物流等领域,构建可信数据链条。
|
缓存 Dubbo Java
理解的Java中SPI机制
本文深入解析了JDK提供的Java SPI(Service Provider Interface)机制,这是一种基于接口编程、策略模式与配置文件组合实现的动态加载机制,核心在于解耦。文章通过具体示例介绍了SPI的使用方法,包括定义接口、创建配置文件及加载实现类的过程,并分析了其原理与优缺点。SPI适用于框架扩展或替换场景,如JDBC驱动加载、SLF4J日志实现等,但存在加载效率低和线程安全问题。
876 7
理解的Java中SPI机制
|
存储 Java 编译器
Java 中 .length 的使用方法:深入理解 Java 数据结构中的长度获取机制
本文深入解析了 Java 中 `.length` 的使用方法及其在不同数据结构中的应用。对于数组,通过 `.length` 属性获取元素数量;字符串则使用 `.length()` 方法计算字符数;集合类如 `ArrayList` 采用 `.size()` 方法统计元素个数。此外,基本数据类型和包装类不支持长度属性。掌握这些区别,有助于开发者避免常见错误,提升代码质量。
1220 1
|
人工智能 JavaScript Java
Java反射机制及原理
本文介绍了Java反射机制的基本概念、使用方法及其原理。反射在实际项目中比代理更常用,掌握它可以提升编程能力并理解框架设计原理。文章详细讲解了获取Class对象的四种方式:对象.getClass()、类.class、Class.forName()和类加载器.loadClass(),并分析了Class.forName()与ClassLoader的区别。此外,还探讨了通过Class对象进行实例化、获取方法和字段等操作的具体实现。最后从JVM类加载机制角度解析了Class对象的本质及其与类和实例的关系,帮助读者深入理解Java反射的工作原理。
371 0
|
缓存 运维 Java
Java静态代码块深度剖析:机制、特性与最佳实践
在Java中,静态代码块(或称静态初始化块)是指类中定义的一个或多个`static { ... }`结构。其主要功能在于初始化类级别的数据,例如静态变量的初始化或执行仅需运行一次的初始化逻辑。
626 4
|
Java 开发者
Java中的异常处理机制深度剖析####
本文深入探讨了Java语言中异常处理的重要性、核心机制及其在实际编程中的应用策略,旨在帮助开发者更有效地编写健壮的代码。通过实例分析,揭示了try-catch-finally结构的最佳实践,以及如何利用自定义异常提升程序的可读性和维护性。此外,还简要介绍了Java 7引入的多异常捕获特性,为读者提供了一个全面而实用的异常处理指南。 ####
313 20
|
运维 Java 编译器
Java 异常处理:机制、策略与最佳实践
Java异常处理是确保程序稳定运行的关键。本文介绍Java异常处理的机制,包括异常类层次结构、try-catch-finally语句的使用,并探讨常见策略及最佳实践,帮助开发者有效管理错误和异常情况。
1390 6