聊聊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了,这种思维在业务开发中基本无用,但在框架设计中尤为重要~

相关文章
|
12天前
|
缓存 监控 Java
优化Spring Boot应用的数据库访问性能
优化Spring Boot应用的数据库访问性能
|
22天前
|
XML druid Java
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
26 0
|
8天前
|
Java 数据库连接 API
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
|
13天前
|
缓存 监控 Java
优化Spring Boot应用的数据库访问性能
优化Spring Boot应用的数据库访问性能
|
26天前
|
运维 Java 测试技术
Spring运维之boo项目表现层测试加载测试的专用配置属性以及在JUnit中启动web服务器发送虚拟请求
Spring运维之boo项目表现层测试加载测试的专用配置属性以及在JUnit中启动web服务器发送虚拟请求
21 3
|
26天前
|
运维 Java 关系型数据库
Spring运维之boot项目bean属性的绑定读取与校验
Spring运维之boot项目bean属性的绑定读取与校验
28 2
|
26天前
|
XML 运维 Java
Spring运维之boot项目打包jar和插件运行并且设置启动时临时属性和自定义配置文件
Spring运维之boot项目打包jar和插件运行并且设置启动时临时属性和自定义配置文件
25 1
|
1月前
|
SQL Java 数据库
Java一分钟之-Spring Data JPA:简化数据库访问
【6月更文挑战第10天】Spring Data JPA是Spring Data项目的一部分,简化了Java数据库访问。它基于JPA,提供Repository接口,使开发者能通过方法命名约定自动执行SQL,减少代码量。快速上手包括添加相关依赖,配置数据库连接,并定义实体与Repository接口。常见问题涉及主键生成策略、查询方法命名和事务管理。示例展示了分页查询的使用。掌握Spring Data JPA能提升开发效率和代码质量。
43 0
|
2月前
|
XML Java 数据格式
Spring 属性注入方式
Spring 属性注入方式
24 2
|
2月前
|
Java Spring
spring boot访问接口报500
spring boot访问接口报500
28 2