首页 » 通讯 » Java 内省机制_属性_监听器

Java 内省机制_属性_监听器

落叶飘零 2024-12-03 17:22:13 0

扫一扫用手机浏览

文章目录 [+]

在打算机科学中,自察是指打算机程序在运行时(Run time)检讨工具(Object)类型的一种能力,常日也可以称作运行时类型检讨。

这个描述非常宽泛,但有三个关键词:

Java 内省机制_属性_监听器 Java 内省机制_属性_监听器 通讯

运行时工具类型

Java 官方对 Java Beans 自察的定义:

Java 内省机制_属性_监听器 Java 内省机制_属性_监听器 通讯
(图片来自网络侵删)

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 的自察机制。

与反射的差异

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 自察机制的核心类是 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 Beans 的属性,事宜和方法,详细是基于以下几个类实现:

BeanInfoJava Bean 信息类PropertyDescriptor属性描述类MethodDescriptor方法描述类EventSetDescriptor事宜描述凑集

先看一个示例:

定义一个 Java Bean:

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

测试代码

@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);}

输出结果:

属性描述: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<?>) 这个方法。

属性处理配置绑定

通过 PropertyDescriptor 可以基于字段名为可写属性设置值。

比如我们常常会利用这样的配置文件:

user: username: zhangsan age: 1

配置文件会与工具进行数据绑定。
测试代码:

@Testpublic void test2() throws IntrospectionException { YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean(); yaml.setResources(new ClassPathResource("application.yml")); String path = "user."; Properties properties = yaml.getObject(); System.out.println(properties); 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(); try { propertyDescriptor.getWriteMethod().invoke(user,properties.get(path+property)); } catch (IllegalAccessException | InvocationTargetException ignored) { } }); System.out.println(user);}

输出结果:

User{username='zhangsan', age=1}在 Spring 中的利用

在传统的 Spring 开拓中我们须要在 web.xml 中指定一些配置参数,比如:

<servlet> <servlet-name>app</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup></servlet>

这里有一个 contextConfigLocation 参数,这个参数终极是与 FrameworkServlet 类中的一个属性进行绑定:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { private String contextConfigLocation;}

那么 Spring 是如何将 web.xml 中的配置项与属性进行绑定的呢,可以参数看org.springframework.web.servlet.HttpServletBean#init() 方法:

@Overridepublic final void init() throws ServletException { // Set bean properties from init parameters. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // Let subclasses do whatever initialization they like. initServletBean();}

可以看到 Spring 是通过 BeanWrapper 完成对属性的绑定:

public interface BeanWrapper extends ConfigurablePropertyAccessor { // 获取属性描述器 PropertyDescriptor[] getPropertyDescriptors(); PropertyDescriptor getPropertyDescriptor(String var1) throws InvalidPropertyException;}

而 BeanWrapper 又继续了 PropertyAccessor 接口:

public interface PropertyAccessor { //读属性 boolean isReadableProperty(String var1); //写属性 boolean isWritableProperty(String var1); @Nullable Class<?> getPropertyType(String var1) throws BeansException; @Nullable TypeDescriptor getPropertyTypeDescriptor(String var1) throws BeansException;}

也便是说 Spring 中 BeanWrapper 基于 Java 的自察机制实现了对属性的赋值事情,但是 Spring 并未局限于 Java 供应的 API,而是也进行了扩展和进一步的封装,如 TypeDescriptor。

可以参考 org.springframework.web.servlet.HttpServletBean#init() 中 BeanWrapper 的利用来实现对 User 工具的属性赋值:

@Testpublic void test5(){ User user = new User(); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(user); MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("username","zhangsan"); pvs.add("age",1); bw.setPropertyValues(pvs); System.out.println(user);}

输出结果:

User{username='zhangsan', age=1}类型转换

有属性赋值,一定就会有类型转换。
说白了我们从配置文件读取的数据是字符串,与属性进行参数绑定的过程中势必会有类型转换,java.beans 中供应了相应的 API:

PropertyEditor属性编辑器顶层接口PropertyEditorSupport属性编辑器实现类PropertyEditorManager属性编辑器管理器在 Spring 中供应了一个 PropertyEditorRegistrar

先看一个例子:

User 类增加 Date 属性:

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) { }}

输出结果:

User{username='zhangsan', age=1, createTime=2020-1-1 0:00:00}事宜监听

如果将右侧算作一个 Java Bean,那么这中间势必存在一个属性变革监听。
java.beans 包中也供应了相应实现:

PropertyChangeEvent属性变革事宜PropertyChangeListener属性(生效)变革监听器PropertyChangeSupport属性(生效)变革监听器管理器’VetoableChangeListener属性(反对)变革监听器VetoableChangeSupport属性(反对)变革监听器管理器

PropertyChangeEvent 的布局方法:

public PropertyChangeEvent(Object source, String propertyName, Object oldValue, Object newValue) { super(source); this.propertyName = propertyName; this.newValue = newValue; this.oldValue = oldValue;}

通过这个布局方法可以看出属性变革监听的关注点:

source事宜源propertyName发生变革的属性名称oldValue旧值newValue新值

示例代码:

在 User 中增加属性(生效)变革监听:

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}

测试代码:

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

输出结果:

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 中添加监听器:

/ 属性(反对)变革监听器 /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);}

测试代码:

@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");}

运行时创造一贯无法抛出非常。
查看源码创造 PropertyChangeSupport 和 VetoableChangeSupport 当新旧值相等时不会触发监听,于是修正测试代码:

@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);}

运行结果:

lisi,,nulljava.beans.PropertyChangeEvent[propertyName=username; oldValue=null; newValue=lisi; propagationId=null; source=User{username='lisi', age=1}]null,,lisijava.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 自察机制。

引用:

https://xiaomi-info.github.io/2020/03/16/java-beans-introspection/

如有侵权联系删除。

标签:

相关文章

语音芯片在对讲机上的应用_语音_芯片

对讲机家当是我国移动通信比较主要的一部分,家当布局呈现相应的比较明显的区域性,紧张制造企业大部分分布在东南沿海、珠三角一带。近年来...

通讯 2025-01-22 阅读0 评论0