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

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

DirectFieldAccessor


它继承自AbstractNestablePropertyAccessor,所以它肯定也就可以处理级联属性和集合数组值了。(请注意,在Spring4.2之后支持,之前是不支持的~


// @since 2.0   出现得可比父类`AbstractNestablePropertyAccessor`要早哦~~~注意:父类的构造函数都是protected的
public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
  // 缓存着每个字段的处理器FieldPropertyHandler
  // ReflectionUtils.findField()根据String去找到Field对象的
  private final Map<String, FieldPropertyHandler> fieldMap = new HashMap<>();
  public DirectFieldAccessor(Object object) {
    super(object);
  }
  // 这个构造器也是protected 的  所以若你想自己指定nestedPath和parent,你可以继承此类~~~
  protected DirectFieldAccessor(Object object, String nestedPath, DirectFieldAccessor parent) {
    super(object, nestedPath, parent);
  }
  ...
  // 实现父类的抽象方法,依旧使用DirectFieldAccessor去处理~~~
  @Override
  protected DirectFieldAccessor newNestedPropertyAccessor(Object object, String nestedPath) {
    return new DirectFieldAccessor(object, nestedPath, this);
  }
  // 字段field属性处理器,使用内部类实现PropertyHandler ~~~
  private class FieldPropertyHandler extends PropertyHandler {
    private final Field field;
    // 从此处可以看出`DirectFieldAccessor`里的field默认都是可读、可写的~~~~
    public FieldPropertyHandler(Field field) {
      super(field.getType(), true, true);
      this.field = field;
    }
    ...
  }
}


它的功能是直接操作Bean的属性值,而代替使用get/set方法去操作Bean。它的实现原理就是简单的field.get(getWrappedInstance())和field.set(getWrappedInstance(), value)等。

它处理级联属性的大致步骤是:


  1. 遇上级联属性,先找出canonicalName
  2. 根据此canonicalName调用其field.get()拿到此字段的值~
  3. 若不为null(有初始值),那就继续解析此类型,循而往复即可~



PropertyAccessor使用Demo


本文以DirectFieldAccessor为例,介绍属性访问器PropertyAccessor的使用~


注备两个普通的JavaBean。苹果Apple:


@ToString
public class Apple {
    private String color;
    // 复杂类型
    private Size size = new Size(); // 苹果的尺寸。 存在级联
    private String[] arrStr = new String[1];
    private List<String> listStr = new ArrayList<>();
    private Map<Integer, String> map = new HashMap<>();
    // 更为复杂的类型
    private List<List<String>> listList = new ArrayList<>();
    private List<Map<Integer, String>> listMap = new ArrayList<>();
    public Apple() {
        super();
        listList.add(new ArrayList<>());
        listMap.add(new HashMap<>());
    }
}


尺寸Size:


@ToString
public class Size {
    private Integer height;
    private Integer width;
}


Apple属性丰富,并且统一都没有提供get/set方法。使DirectFieldAccessor直接的属性访问器给其赋值:


    public static void main(String[] args) {
        Apple apple = new Apple();
        PropertyAccessor accessor = new DirectFieldAccessor(apple);
        // 设置普通属性
        accessor.setPropertyValue("color", "红色");
        // 设置嵌套属性(注意:此处能够正常work是因为有= new Size(),
        // 否则报错:Value of nested property 'size' is null 下同~)
        accessor.setPropertyValue("size.height", 10);
        // 设置集合/数组属性
        accessor.setPropertyValue("arrStr[0]", "arrStr");
        accessor.setPropertyValue("arrStr[1]", "arrStr1"); // 注意:虽然初始化时初始化过数组了,但是仍以此处的为准
        accessor.setPropertyValue("listStr[0]", "listStr");
        accessor.setPropertyValue("listStr[0]", "listStr1"); // 如果角标index一样,后面覆盖前面的
        // 虽然listStr是String的List,但是反射绕过了泛型  可以set进去,但一get就报错~~~需要注意这一点
        //accessor.setPropertyValue("listStr[0]", new Size());
        //accessor.setPropertyValue("listStr[1]", 20);
        //System.out.println(apple.getListStr().get(0)); //Cannot convert value of type 'com.fsx.bean.Size' to required type 'java.lang.String'
        // 设置Map:key只能是数值才行,否则是不好使的~~~~
        //accessor.setPropertyValue("map['aaa']","myValue1"); //Caused by: java.lang.NumberFormatException: For input string: "aaa"
        accessor.setPropertyValue("map[1]", "myValue2");
        // 设置listList这种集合里的集合
        accessor.setPropertyValue("listList[0][0]", "listList00");
        accessor.setPropertyValue("listList[0][1]", "listList01");
        //accessor.setPropertyValue("listList[1][0]","listList10"); //IndexOutOfBoundsException: Index: 1, Size: 1
        //accessor.setPropertyValue("listList[1][1]","listList11"); //IndexOutOfBoundsException: Index: 1, Size: 1
        // 设置listMap这种集合里面放Map
        accessor.setPropertyValue("listMap[0][0]", "listMap00");
        //accessor.setPropertyValue("listMap[0]['myKey']","listMapkey"); //For input string: "myKey"
        // =========打印输出
        System.out.println(apple); //Apple(color=红色, size=Size(height=10, width=null), arrStr=[arrStr, arrStr1], listStr=[listStr1], map={1=myValue2}, listList=[[listList00, listList01]], listMap=[{0=listMap00}])
    }


从结果中是能够看出来的,使用DirectFieldAccessor能够正确完成属性赋值。这使用DirectFieldAccessor作为实现的话有几点使用小细节需要注意:


若是级联属性、集合数组等复杂属性,初始值不能为null

使用它给属性赋值无序提供get、set方法(侧面意思是:它不会走你的get/set方法逻辑)

当然若你希望null值能够被自动初始化也是可以的,请设值:accessor.setAutoGrowNestedPaths(true);这样数组、集合、Map等都会为null时候给你初始化(其它Bean请保证有默认构造函数)


在实际开发中,DirectFieldAccessor使用的场景相对较少,但有个典型应用是Spring-Data-Redis有使用DirectFieldAccessor来获取属性值~~~


若我们开发中只是单纯的想直接获取属性值,不妨可以使用它,形如这样:new DirectFieldAccessor(client).getPropertyValue("redisURI")非常的方便~~~



当设置属性值时,少不了两样东西:


  1. 属性访问表达式:如listMap[0][0]
  2. 属性值:


ProperyValue对象就是用来封装这些信息的。如果某个值要给赋值给bean属性,Spring都会把这个值包装成ProperyValue对象。


PropertyTokenHolder的作用是什么?


这个类的作用是对属性访问表达式的细化和归类。比如这句代码:


.setPropertyValue("listMap[0][0]", "listMapValue00"); 


这句代码的含义是:为Apple的成员变量listMap的第0个元素:即为Map。然后向该Map里放入键值对:0(key)和listMapValue00(value)。所以listMap[0][0]一个属性访问表达式,它在PropertyTokenHolder类里存储如下:


  1. canonicalName:listMap[0][0]:代表整个属性访问表达式
  2. actualName:listMap:仅包含最外层的属性名称
  3. keys:[0, 0]:数组的长度代表索引深度,各元素代表索引值


由于每个部分各有各的作用,所以就事先分解好,包装成对象,避免重复分解。


总结


本文介绍了PropertyAccessor属性访问器,并且以DirectFieldAccessor来直接操作Bean且提供了使用Demo。

通过本文的学习,能给你开辟一条新思路来操作JavaBean,而不仅仅只是通过get/set了,这种思维在业务开发中基本无用,但在框架设计中尤为重要~

相关文章
|
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事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
下一篇
无影云桌面