Java基础扫盲系列(三)— Java内省技术
前言
Java内省技术属于Java基础体系的的一部分,但是很多人都不甚了解。笔者也是在学习Spring源码的过程中遇到该技术模块的。为了完善技术体系,本文将全面的学习该技术。在提到Java内省技术,就不得不说Java的反射和JavaBeans技术,相信这两点大家应该都非常熟悉。本文将会从以下几个方面学习Java内省:
- Java内省和JavaBeans技术
- Java内省和反射技术的关系
- Java内省的API介绍
- Java内省实战
### Java内省和JavaBeans技术
在JavaBeans 101的规范中对于JavaBeans是这样定义的:
“A Java Bean is a reusable software component that can be manipulated visually in a builder tool.”
JavaBeans是一种可重复使用的软件组件,并且可以通过可视化的工具的方式进行操作。
如在NetBeans IDE中创建一个Button按钮的JavaBeans如下:
以上的Pannel面板中的Button按钮就是一个JavaBean,满足可重复使用的定义。通过点击该按钮将会展示该Button的可视化设计器:
在设计器中可以编辑该Button的Property和Event。这样就完成了一个JavaBean的创建。
Note:
关于NetBeans的创建可视化Button Bean的过程这里不再详细介绍,如果感兴趣可以参考:The Java™ Tutorials
当然这是Java Client的可视化编程,可以这样做。但是对于Java服务端应用而言,却无法这样使用。且站在程序猿的角度,更偏向使用coding方式解决问题。
使用coding方式编写JavaBean组件也非常容易,不需要实现任何借口或者任何特定的工具。但是JavaBean有规范约束,主要从Propeties,Method和Events三个方面约定。只有这样遵循这些规定,Bean的构建工具才可以探测检视JavaBean,再以可视化方式展示JavaBean。
Properties
属性由公共的Setter和Getter方法定义。对于可视化工具(如NetBeans),将能识别方法名称,然后将该属性展示。如下:
public class FaceBean {
private int mMouthWidth = 90;
// getter for mMouthWidth
public int getMouthWidth() {
return mMouthWidth;
}
// setter for mMouthWidth
public void setMouthWidth(int mw) {
mMouthWidth = mw;
}
}
关于属性的种类非常多,如:Indexed Properties,Bound Properties等等。如果感兴趣可以参考:The Java™ Tutorials
只需要掌握一点,属性由Setter和Getter标识定义。
Methods
方法即是除了属性的Setter和Getter之外的公共方法。如NetBeans的可视化工具将能检视JavaBeans中的方法。
内省
在大致介绍完JavaBeans后,再来看内省就简单多了。Java内省是JavaBeans技术架构体系中的一部分,这点可以参考:JavaBeans Spec
其中是这样描述内省:
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.
即运行时获取JavaBean的properties,events和methods的过程称为Java内省。简而言之,即检视JavaBean内部的信息,如方法,属性,事件。主要用来运行时获取JavaBean的内部信息。
### Java内省和反射技术的关系
学习上节关于Java内省技术的介绍,有些读者应该很快联想到Java的反射技术。Java内省和反射技术很类似(反射也具有运行获取Java对象的类型信息)。
文档中是这样描述Java内省和Java反射:
We therefore provide a composite mechanism. By default we will use a low level reflection mechanism to study the methods supported by a target bean and then apply simple design pat- terns to deduce from those methods what properties, events, and public methods are supported. However, if a bean implementor chooses to provide a BeanInfo class describing their bean then this BeanInfo class will be used to programmatically discover the beans behaviour.
为了能够提供JavaBean内部的检视,提供了一组机制用于处理获取JavaBean的内部信息。默认使用低级别的反射机制获取JavaBean的信息。但是对于指定了JavaBean的对应BeanInfo类时,将从BeanInfo中描述的JavaBean信息获取JavaBean的行为,这时将不会再使用反射机制。
所以Java内省和Java反射存在以下关系:
- Java反射是一组较低级别的API,使用起来比较复杂。而Java内省提供了一套比较简单和有语义的API用于操作JavaBean;
- Java内省机制更加简单易用;
- 由于Java内省底层默认使用Java反射机制,所以同样有Java反射机制带来的性能问题;
### Java内省的API介绍
1.Introspector
The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean
Introspector译名是内省器,它提供了一系列的方法内省获取JavaBean的properties,method和events。
Introspector底层将寻找JavaBean对应的BeanInfo,如果寻找不到,将使用反射机制分析JavaBean。
其中提供getBeanInfo方法可以获取目标JavaBean对应的BeanInfo方法。
2.BeanInfo
BeanInfo是描述JavaBean的信息的一个对象。其中描述了JavaBean的properties,method和event等。但是很多时候是不提供明确的BeanInfo,需要由上述的Introspector使用反射机制获取JavaBean信息生成对应的BeanInfo。
3.BeanDescriptor
A BeanDescriptor provides global information about a "bean", including its Java class, its displayName, etc.
BeanDescriptor是对JavaBean的全局信息描述,描述其Class,名称等。
4.PropertyDescriptor
A PropertyDescriptor describes one property that a Java Bean exports via a pair of accessor methods.
PropertyDescriptor通过一对Setter和Getter方法导出JavaBean的property,是对Property的描述。
PropertyDescriptor提供了获取Setter和Getter方法的接口,获取Property的名称等接口。
5.MethodDescriptor
A MethodDescriptor describes a particular method that a Java Bean supports for external access from other components.
MethodDescriptor用于描述JavaBean中的公共方法,它提供获取该方法对象Method,方法的参数等API。
Java内息机制还提供了一些其他的API,这里只介绍一些常用的重点接口。关于其他的接口,读者有兴趣可以参考:java.beans
下节将通过coding方式学习内息,如何使用这些API避免复杂的反射方式来操作JavaBean。
### Java内省实战
首先编写JavaBean类Person:
/**
* person for java bean.
*
* @author huaijin
*/
public class Person {
private String name;
private int age;
private List<String> address = new ArrayList<>();
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;
}
public void setAddress(String[] address) {
this.address.addAll(Arrays.asList(address));
}
public String[] getAddress() {
String[] strs = new String[this.address.size()];
return this.address.toArray(strs);
}
public void setAddress(int index, String value) {
this.address.add(index, value);
}
public String getAddress(int index) {
return this.address.get(index);
}
}
然后再使用上述介绍的API分别来操作JavaBean,内省:
public class IntrospectPerson {
public static void main(String[] args) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
Person person = new Person();
person.setName("huaijin");
person.setAge(18);
List<String> address = new ArrayList<>();
address.add("hangzhou");
address.add("anhui");
person.setAddress(address.toArray(new String[address.size()]));
BeanInfo personBeanInfo = Introspector.getBeanInfo(Person.class, Introspector.USE_ALL_BEANINFO);
PropertyDescriptor[] propertyDescriptors = personBeanInfo.getPropertyDescriptors();
// 获取PropertyDescriptor,读写属性
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
System.out.println("property name: " + propertyDescriptor.getName());
System.out.println("read before writing: " + propertyDescriptor.getReadMethod().invoke(person, null));
Class<?> propertyClass = propertyDescriptor.getPropertyType();
if (propertyClass == String.class) {
propertyDescriptor.getWriteMethod().invoke(person, "lxy");
} else if (propertyClass == int.class) {
propertyDescriptor.getWriteMethod().invoke(person, 28);
}
if (propertyDescriptor instanceof IndexedPropertyDescriptor) {
IndexedPropertyDescriptor indexedPropertyDescriptor =
(IndexedPropertyDescriptor) propertyDescriptor;
indexedPropertyDescriptor.getIndexedWriteMethod().invoke(person, 0, "2dfire");
}
System.out.println("read after writing: " + propertyDescriptor.getReadMethod().invoke(person, null) + "\n");
}
// 获取MethodDescriptor,可以操作JavaBean
MethodDescriptor[] methodDescriptors = personBeanInfo.getMethodDescriptors();
for (MethodDescriptor methodDescriptor : methodDescriptors) {
System.out.println("methodName: " + methodDescriptor.getName());
}
}
}
可以看出,利用Introspector、BeanInfo和PropertyDescriptor等比使用反射机制带来更多的简便性。
在日常开发中,或许经常使用Java内省而不知,下面就通过日常开发中使用到的案例进一步熟悉Java内息。读者或许经常使用Spring的BeanUtils工具类拷贝JavaBean的属性到另一个目标对象中。BeanUtils.copyProperties内部实现就使用了Java内省机制。
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
throws BeansException {
// 断言源对象和目标对象不能为空
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
// 获取目标对象的类型
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
// 获取目标对象的所有Properties
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
// 遍历目标对象的Properties
for (PropertyDescriptor targetPd : targetPds) {
// 获取Property的Setter方法
Method writeMethod = targetPd.getWriteMethod();
// 如果Setter方法不为空且Property没有被忽略
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
// 获取源对象对应的Property
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
// 如果源对象中存在该Property
if (sourcePd != null) {
// 获取源对象的Property对应的Getter方法
Method readMethod = sourcePd.getReadMethod();
// 如果读方法不为空,且Getter的返回类型和目标对象的Setter方法参数类型匹配
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
// 如果Getter方法非公共可见,则设置Getter方法可访问
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
// 反射调用源对象的Getter方法,获取属性值
Object value = readMethod.invoke(source);
// 如果目标对象的Setter方法非公共可见,设置其可访问
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
// 反射调用目标对象的Setter方法,设置属性值
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
以上的逻辑便是BeanUtils中利用JavaBeans中的内省技术实现对象的属性拷贝。
从源码中不难返现,其中使用了大量的反射机制。读者应该都知道,大量使用反射必然有一定的性能问题。性能在于:运行时分析类型信息,如Field、Method等,非常消耗性能。这些信息本应该在编译期间解决,在运行时获取虽然带来更大的灵活性,但是也带来不可避免的性能问题。
对于反射的性能问题,常见的手段遍是缓存分析结果信息,如缓存Method,Field等。这种手段在Spring和Java动态代理中都有体现。下面再使用Spring内部的BeanWrapper优化使用JavaBean的内省技术来分析其对性能的优化方式。
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
// 从缓存中获取PropertyDescriptor
return getCachedIntrospectionResults().getPropertyDescriptors();
}
private CachedIntrospectionResults getCachedIntrospectionResults() {
// 断言被包装的目标Bean不为空
Assert.state(getWrappedInstance() != null, "BeanWrapper does not hold a bean instance");
// 如果缓存的内省结果为空则进行内省,并缓存
if (this.cachedIntrospectionResults == null) {
this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
}
return this.cachedIntrospectionResults;
}
Spring中的BeanWrapper对于Java内省的优化,是遵循Java反射的优化的策略的。即缓存PropertyDescriptor:
private final Map<String, PropertyDescriptor> propertyDescriptorCache;
### 总结
Java内省技术提供一套高级API的机制针对JavaBean操作,底层默认通过Java反射机制获取JavaBean的信息。内省机制在Spring中有很多使用场景,如BeanUtils,BeanWrapper等。在日常开发中也可以使用简化反射机制带来的复杂性。
参考
Java introspection and reflection
java.beans
How to use Reflection, Introspection and Customization in JavaBeans
JavaBeans(TM) Specification 1.01 Final Release
Trail: JavaBeans(TM)
Java基础扫盲系列(三)— Java内省技术的更多相关文章
- java基础解析系列(三)---HashMap
java基础解析系列(三)---HashMap java基础解析系列 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
- Java基础扫盲系列(-)—— String中的format
Java基础扫盲系列(-)-- String中的format 以前大学学习C语言时,有函数printf,能够按照格式打印输出的内容.但是工作后使用Java,也没有遇到过格式打印的需求,今天遇到项目代码 ...
- Java基础扫盲系列(二)—— Java中BigDecimal和浮点类型
一直以来我几乎未使用过BigDecimal类型,只有在DB中涉及到金额字段时听说要用Decimal类型,但是今天再项目代码中看到使用BigDecimal表示贷款金额. 本篇文章不是介绍BigDecim ...
- java基础学习系列三
产生随机数 例如 [a,b] Math.random*(b-a+1)+a 公式推算 [3,55]-----[0,52]+3 *53+3
- Java基础学习笔记三 Java基础语法
Scanner类 Scanner类属于引用数据类型,先了解下引用数据类型. 引用数据类型的使用 与定义基本数据类型变量不同,引用数据类型的变量定义及赋值有一个相对固定的步骤或格式. 数据类型 变量名 ...
- java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析 ...
- java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别
java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 目录 java基础解析系列(一)---String.StringBuffer.St ...
- java基础解析系列(六)---深入注解原理及使用
java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...
- java基础解析系列(七)---ThreadLocal原理分析
java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
随机推荐
- java高并发系列 - 第4天:JMM相关的一些概念
JMM(java内存模型),由于并发程序要比串行程序复杂很多,其中一个重要原因是并发程序中数据访问一致性和安全性将会受到严重挑战.如何保证一个线程可以看到正确的数据呢?这个问题看起来很白痴.对于串行程 ...
- java.lang.ClassNotFoundException: XXX (no security manager: RMI class loader disabled)
在搞RMI远程发布,consumer去获取rmi远程服务的代理对象的时候出现了如下的错误 问题发现: 由于我发布的对象的包路径和获取的对象的包路径不一致,导致了这样的问题 解决方案: 包路径改为一致就 ...
- 在.net 程序中使用Mustache模板字符串
今天弄了一个配置随着使用环境动态切换的功能,一个基本的思路是: 将配置配置为模板的形式, 根据不同的环境定义环境变量 根据环境变量渲染模板,生成具体的配置 这里面就涉及到了一个字符串模板的功能,关于模 ...
- 如何让Python爬虫一天抓取100万张网页
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: 王平 源自:猿人学Python PS:如有需要Python学习资料的 ...
- 如何搭建属于自己的Web服务器
如今随着计算机和互联网技术的发展,上网现在已经不再是什么难事,打开浏览器,我们可以访问各种论坛站点,比如CSDN.博客园等,各种视频网站,例如爱奇艺,B站等.在网上我们可以写文章,看视频,购物,打游戏 ...
- 专访腾讯方亮:WeTest品牌全面升级,“好的产品一定深谙人性”
工欲善其事,必先利其器.在当下竞争激烈的市场环境中,精品,已经成为所有游戏厂商安身立命之本.但如何提升品质,使产品成为精品,行业内却长期缺乏公开.透明,以及具备实际参考.实操价值的标准. 制定一项标准 ...
- uni-app自定义导航栏按钮|uniapp仿微信顶部导航条
最近一直在学习uni-app开发,由于uniapp是基于vue.js技术开发的,只要你熟悉vue,基本上很快就能上手了. 在开发中发现uni-app原生导航栏也能实现一些顶部自定义按钮+搜索框,只需在 ...
- css利用padding-top设置等比例遇到的问题
外层盒子如果设置了左右margin,外层盒子设置对应比例的时候,是按外层盒子的宽+两边的margin算做横向总长度的,不是只算宽度的.
- Google在情报搜集中的基础技巧
Google在情报搜集中的基础技巧 作者:王宇阳 时间:2019-06-06 作者笔记 Google Hacking 是指使用特定的高级的google搜索语法,收集渗透测试目标的信息,查找目标的配 ...
- 74HC238引脚定义 使用方法
三八译码器 用作IO扩展与复用 用3个IO,可以控制8个输出 引脚定义 A0~A2:3个输入 E1.E2:拉低使能,可以接地 E3:拉高使能,可以接VCC Y0~Y7:8个输出 真值表 如果想输出8个 ...