聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用【享学Spring】(上)

简介: 聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用【享学Spring】(上)

前言


本篇文章聊聊Spring数据访问、绑定体系中一个非常重要的组成: 属性访问器(PropertyAccessor)。

首先提醒各位,注意此接口和属性解析器(PropertyResolver)是有本质区别的:属性解析器是用来获取配置数据的,详细使用办法可参考:【小家Spring】关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析


属性访问器PropertyAccessor接口的作用是存/取Bean对象的属性。为了体现这个接口它的重要性,据我目前了解我此处贴出这么一句话:


  • 所有Spring创建的Bean对象都使用该接口存取Bean属性值

PropertyAccessor


它是可以访问命名属性named properties(例如对象的bean属性或对象中的字段)的类的公共接口。大名鼎鼎的BeanWrapper接口也继承自它,它所在包是org.springframework.beans(BeanWrapper也在此包)


// @since 1.1  出现得非常早
public interface PropertyAccessor {
  // 简单的说就是级联属性的分隔符。
  // 比如foo.bar最终会调用getFoo().getBar()两个方法
  String NESTED_PROPERTY_SEPARATOR = ".";
  char NESTED_PROPERTY_SEPARATOR_CHAR = '.';
  // 代表角标index的符号  如person.addresses[0]  这样就可以把值放进集合/数组/Map里了
  String PROPERTY_KEY_PREFIX = "[";
  char PROPERTY_KEY_PREFIX_CHAR = '[';
  String PROPERTY_KEY_SUFFIX = "]";
  char PROPERTY_KEY_SUFFIX_CHAR = ']';
  // 此属性是否可读。若属性不存在  返回false
  boolean isReadableProperty(String propertyName);
  // 此出行是否可写。若属性不存在,返回false
  boolean isWritableProperty(String propertyName);
  // 读方法
  @Nullable
  Class<?> getPropertyType(String propertyName) throws BeansException;
  @Nullable
  TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException;
  @Nullable
  Object getPropertyValue(String propertyName) throws BeansException;
  // 写方法  
  void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException;
  void setPropertyValue(PropertyValue pv) throws BeansException;
  // 批量设置值
  void setPropertyValues(Map<?, ?> map) throws BeansException;
  // 说明:PropertyValues和PropertyValue关系特别像PropertySources和PropertySource的关系
  void setPropertyValues(PropertyValues pvs) throws BeansException;
  // 可控制是否接受非法的字段、value值扽  ignoreUnknown/ignoreInvalid分别对应非法属性和非法value值的处理策略~
  void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) throws BeansException;
  void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException;
}


它的继承树如下:


image.png


最终的实现类主要有DirectFieldAccessor和BeanWrapperImpl,本文作为铺垫,着重聊聊DirectFieldAccessor这个访问器实现类~


说明一下:DirectFieldAccessFallbackBeanWrapper它在spring-data-commons这个jar里面,所以若你没有使用spring-data-xxx是木有此实现类的~~~


ConfigurablePropertyAccessor


可配置的PropertyAccessor。它是一个子接口,提供了可配置的能力,并且它还继承了PropertyEditorRegistry、TypeConverter等接口~~~


// @since 2.0
public interface ConfigurablePropertyAccessor extends PropertyAccessor, PropertyEditorRegistry, TypeConverter {
  // 设置一个ConversionService ,用于对value值进行转换
  // 它是Spring3.0后推出来替代属性编辑器PropertyEditors的方案~
  void setConversionService(@Nullable ConversionService conversionService);
  @Nullable
  ConversionService getConversionService();
  // 设置在将属性编辑器应用于属性的新值时是**否提取旧属性值**。
  void setExtractOldValueForEditor(boolean extractOldValueForEditor);
  boolean isExtractOldValueForEditor();
  // 设置此实例是否应尝试“自动增长”包含null的嵌套路径。
  // true:为null的值会自动被填充为一个默认的value值,而不是抛出异常NullValueInNestedPathException
  void setAutoGrowNestedPaths(boolean autoGrowNestedPaths);
  boolean isAutoGrowNestedPaths();
}


按照Spring的设计,对此接口提供了一个抽象实现:AbstractPropertyAccessor


AbstractPropertyAccessor


实现了部分父类的接口以及提供一些模版实现~


// @since 2.0  它继承自TypeConverterSupport 相当于实现了TypeConverter以及PropertyEditorRegistry的所有内容
public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor {
  // 这两个属性上面已经解释了~~~
  private boolean extractOldValueForEditor = false;
  private boolean autoGrowNestedPaths = false;  
  ... // 省略get/set方法
  // setPropertyValue是抽象方法~~~
  @Override
  public void setPropertyValue(PropertyValue pv) throws BeansException {
    setPropertyValue(pv.getName(), pv.getValue());
  }
  @Override
  public void setPropertyValues(Map<?, ?> map) throws BeansException {
    setPropertyValues(new MutablePropertyValues(map));
  }
  // MutablePropertyValues和MutablePropertySources特别像,此处就不再介绍了
  // 此方法把Map最终包装成了一个MutablePropertyValues,它还有个web子类:ServletRequestParameterPropertyValues
  @Override
  public void setPropertyValues(Map<?, ?> map) throws BeansException {
    setPropertyValues(new MutablePropertyValues(map));
  }
  // 当然也可以直接传入一个PropertyValues   这里传入fasle,表示默认要求属性和value值必须都合法否则抛出异常
  @Override
  public void setPropertyValues(PropertyValues pvs) throws BeansException {
    setPropertyValues(pvs, false, false);
  }
  @Override
  public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) throws BeansException {
    setPropertyValues(pvs, ignoreUnknown, false);
  }
  // 此抽象类最重要的实现方法~~~
  @Override
  public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException {
    List<PropertyAccessException> propertyAccessExceptions = null;
    // 显然绝大多数情况下,都是MutablePropertyValues~~~~ 直接拿即可
    List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
    // 遍历一个一个执行,批量设置值最终也还是调用的单个的~~~~
    // 这里面是否要抛出异常,ignoreUnknown和ignoreInvalid就生效了。分别对应NotWritablePropertyException和NullValueInNestedPathException两个异常
    for (PropertyValue pv : propertyValues) {
      try {
        setPropertyValue(pv);
      } catch (NotWritablePropertyException ex) {
        if (!ignoreUnknown) {
          throw ex;
        }
        // Otherwise, just ignore it and continue...
      } catch (NullValueInNestedPathException ex) {
        if (!ignoreInvalid) {
          throw ex;
        }
        // Otherwise, just ignore it and continue...
      } catch (PropertyAccessException ex) {
        if (propertyAccessExceptions == null) {
          propertyAccessExceptions = new ArrayList<>();
        }
        // 把异常收集,因为是for循环,最终一次性抛出
        propertyAccessExceptions.add(ex);
      }
    }
    // If we encountered individual exceptions, throw the composite exception.
    if (propertyAccessExceptions != null) {
      PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[0]);
      throw new PropertyBatchUpdateException(paeArray);
    }
  }
  // 子类AbstractNestablePropertyAccessor重写了此方法
  // Redefined with public visibility.
  @Override
  @Nullable
  public Class<?> getPropertyType(String propertyPath) {
    return null;
  }
  // 抽象方法  相当于具体的get/set方法由子类去实现的~~
  @Override
  @Nullable
  public abstract Object getPropertyValue(String propertyName) throws BeansException;
  @Override
  public abstract void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException;
}


它主要完成了对PropertyEditorRegistry和TypeConverter等接口的间接实现,然后完成了批量操作的模版操作,但是很明显最终的落地的get/set留给子类来实现~


getPropertyValue和setPropertyValue是分别用于获取和设置bean的属性值的。

AbstractNestablePropertyAccessor


一个典型的实现,为其它所有使用案例提供必要的基础设施。nestable:可嵌套的,支持嵌套的

// @since 4.2
public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
  private int autoGrowCollectionLimit = Integer.MAX_VALUE;
  @Nullable
  Object wrappedObject;
  private String nestedPath = "";
  @Nullable
  Object rootObject;
  /** Map with cached nested Accessors: nested path -> Accessor instance. */
  @Nullable
  private Map<String, AbstractNestablePropertyAccessor> nestedPropertyAccessors;
  // 默认是注册默认的属性编辑器的:defaultEditors  它几乎处理了所有的Java内置类型  包括基本类型、包装类型以及对应数组类型~~~
  protected AbstractNestablePropertyAccessor() {
    this(true);
  }
  protected AbstractNestablePropertyAccessor(boolean registerDefaultEditors) {
    if (registerDefaultEditors) {
      registerDefaultEditors();
    }
    this.typeConverterDelegate = new TypeConverterDelegate(this);
  }
  protected AbstractNestablePropertyAccessor(Object object) {
    registerDefaultEditors();
    setWrappedInstance(object);
  }
  protected AbstractNestablePropertyAccessor(Class<?> clazz) {
    registerDefaultEditors();
    // 传的Clazz 那就会反射先创建一个实例对象
    setWrappedInstance(BeanUtils.instantiateClass(clazz));
  }
  protected AbstractNestablePropertyAccessor(Object object, String nestedPath, Object rootObject) {
    registerDefaultEditors();
    setWrappedInstance(object, nestedPath, rootObject);
  }
  //  parent:不能为null
  protected AbstractNestablePropertyAccessor(Object object, String nestedPath, AbstractNestablePropertyAccessor parent) {
    setWrappedInstance(object, nestedPath, parent.getWrappedInstance());
    setExtractOldValueForEditor(parent.isExtractOldValueForEditor());
    setAutoGrowNestedPaths(parent.isAutoGrowNestedPaths());
    setAutoGrowCollectionLimit(parent.getAutoGrowCollectionLimit());
    setConversionService(parent.getConversionService());
  }
  // wrappedObject:目标对象
  public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
    this.wrappedObject = ObjectUtils.unwrapOptional(object);
    Assert.notNull(this.wrappedObject, "Target object must not be null");
    this.nestedPath = (nestedPath != null ? nestedPath : "");
    // 此处根对象,若nestedPath存在的话,是可以自定义一个rootObject的~~~
    this.rootObject = (!this.nestedPath.isEmpty() ? rootObject : this.wrappedObject);
    this.nestedPropertyAccessors = null;
    this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
  }
  public final Object getWrappedInstance() {
    Assert.state(this.wrappedObject != null, "No wrapped object");
    return this.wrappedObject;
  }
  public final String getNestedPath() {
    return this.nestedPath;
  }
  // 显然rootObject和NestedPath相关,默认它就是wrappedObject
  public final Object getRootInstance() {
    Assert.state(this.rootObject != null, "No root object");
    return this.rootObject;
  }
  ... // 简单的说,它会处理.逻辑以及[0]等逻辑  [0]对应着集合和数组都可
}


此访问器将集合和数组值转换为相应的目标集合或数组,当然还解决了级联属性(嵌套属性)的问题~

需要特别注意的是:AbstractNestablePropertyAccessor这个抽象类在Spring4.2后才提供~~~


相关文章
|
2月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
3月前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
3月前
|
缓存 监控 Java
优化Spring Boot应用的数据库访问性能
优化Spring Boot应用的数据库访问性能
|
4月前
|
XML druid Java
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
45 0
|
2月前
|
缓存 Java 开发者
Spring高手之路22——AOP切面类的封装与解析
本篇文章深入解析了Spring AOP的工作机制,包括Advisor和TargetSource的构建与作用。通过详尽的源码分析和实际案例,帮助开发者全面理解AOP的核心技术,提升在实际项目中的应用能力。
23 0
Spring高手之路22——AOP切面类的封装与解析
|
2月前
|
Java Spring 开发者
Spring 框架配置属性绑定大比拼:@Value 与 @ConfigurationProperties,谁才是真正的王者?
【8月更文挑战第31天】Spring 框架提供 `@Value` 和 `@ConfigurationProperties` 两种配置属性绑定方式。`@Value` 简单直接,适用于简单场景,但处理复杂配置时略显不足。`@ConfigurationProperties` 则以类级别绑定配置,简化代码并更好组织配置信息。本文通过示例对比两者特点,帮助开发者根据具体需求选择合适的绑定方式,实现高效且易维护的配置管理。
35 0
|
2月前
|
缓存 Java 数据库连接
Spring Boot 资源文件属性配置,紧跟技术热点,为你的应用注入灵动活力!
【8月更文挑战第29天】在Spring Boot开发中,资源文件属性配置至关重要,它让开发者能灵活定制应用行为而不改动代码,极大提升了可维护性和扩展性。Spring Boot支持多种配置文件类型,如`application.properties`和`application.yml`,分别位于项目的resources目录下。`.properties`文件采用键值对形式,而`yml`文件则具有更清晰的层次结构,适合复杂配置。此外,Spring Boot还支持占位符引用和其他外部来源的属性值,便于不同环境下覆盖默认配置。通过合理配置,应用能快速适应各种环境与需求变化。
35 0
|
3月前
|
Java Spring
idea新建spring boot 项目右键无package及java类的选项
idea新建spring boot 项目右键无package及java类的选项
103 5
|
2月前
|
Java Spring
Spring Boot实战:静态资源无法访问
Spring Boot实战:静态资源无法访问
52 0
|
3月前
|
Java 数据库连接 API
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
下一篇
无影云桌面