java bean 规范中没有明说的一个小坑

本贴最后更新于 2278 天前,其中的信息可能已经渤澥桑田

随便在百度上搜索 “java bean 规范” 大致能得到如下几条:

  1. JavaBean 类必须是一个公共类,并将其访问属性设置为 public ,如: public class user{ …}
  2. JavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,例如:public User() {…}
  3. 一个 javaBean 类不应有公共实例变量,类变量都为 private ,如: private int id;
  4. javaBean 属性是具有 getter/setter 方法的成员变量。如果属性类型为 boolean 类型,那么读方法的格式可以是 get 或 is。
    例如名为 abc 的 boolean 类型的属性,它的读方法可以是 getAbc(),也可以是 isAbc();
  5. 一般 JavaBean 属性以小写字母开头,驼峰命名格式,相应的 getter/setter 方法是 get/set 接上首字母大写的属性名。

实际上呢还有一条没有明说的,平时大家都是那么做的,无意识中规避掉了,但实际上是强制要求的规则:
set 方法返回值必须为 void!!!

比如说我们有一个 java bean 类 B,它看起来是这样的

public class B { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { ****this.name = name; } }

一般来说我们会这样初始化 bean:

B b=new B(); b.setId(1); b.setName("aoeui");

这样写的弊端非常明显,一旦属性多起来就要写很多个 b.setXxx(),而变量名较长的时候手写就非常麻烦了。一般来说我们
会通过插件来自动生成这块代码。那有没有什么其他办法呢?在 effective java 第二章第二条中提到了,我们可以使用构建器
来初始化。于是我们可以将代码改成这样:

public class B { private final int id; private final String name; //getter ... public static class Builder { private int id; private String name; public Builder() {} public Builder id(int val) { id = val; return this; } public Builder name(String val) { name = val; return this; } public B builder() { return new B(this); } } private B(Builder builder) { id = builder.id; name = builder.name; } }

然后我们就可以这样初始化一个类了:

B b=new Builder().id(1).name("aoeui").builder();

这样子得到的是一个只读的 java bean,这种写法不但简化了他的初始化操作,而且我们还得到了一个不可变对象。
如果只是想要一个普通的 java bean 的话把属性上的 final 修饰符去掉,然后添加上 setter 方法就行了。
但是这个写法也有问题,虽然调用的时候简单了,但是写这个类的时候却变得更加麻烦了。一般的插件都没有提供
这种格式的 java bean 类生成,如果不去自己写个代码生成器的话就得给每个 bean 类手写构建器,这显然是不可接受的。
那么有没有更简单的方法呢?
有,那就是将 setter 的返回值设置为类型本身,在 setter 方法的末尾直接返回 this:

public class B { private Integer id; private String name; public Integer getId() { return id; } public B setId(Integer id) { this.id = id; return this; } public String getName() { return name; } public B setName(String name) { this.name = name; return this; } }

偶然间在 idea 的代码生成器的 setter template 的下拉框里发现了这种写法

imagepng

第一次看到的时候真的是感觉醍醐灌顶,但后来发现这就是个坑……
用 mybatis 的时候一切都好好的,每次看到那种啰嗦的写法的时候总觉得自己是无比的高明,
直到有一天我用了 commons-dbutils……

imagepng

所有获取到的数据都会变成这样!
一开始以为是环境出了问题取不到数据,于是重建一个项目,一个个的排除,结果一无所获。
折腾了半天,最后发现问题出在 dbutils 的 BeanProcessor 类的 propertyDescriptors 中。
dbutils 在使用结果集创建对象的时候,首先会用无参构造器创建一个空对象,然后用 setter 方法把值填进去。
而这些 setter 方法的信息就是从 propertyDescriptors 中得到的

private PropertyDescriptor[] propertyDescriptors(Class<?> c) throws SQLException { // Introspector caches BeanInfo classes for better performance ****BeanInfo beanInfo = null; try { beanInfo = Introspector.getBeanInfo(c); } catch (IntrospectionException e) { throw new SQLException( "Bean introspection failed: " + e.getMessage()); } return beanInfo.getPropertyDescriptors(); }

这就是 dbutils 和 mybatis 结果不一样的原因。
dbutils 并不是使用反射取获取 setter 方法,而是通过 java.beans.Introspector 这个类的 getBeanInfo 方法获取的。

imagepng

查看这个 beanInfo 的状态可以发现,根本就没有 setter 方法!
可以看出 java bean 的规范非常严格,仅仅因为返回值是 void 就认为这个类没有 setter 方法,即使返回值并不是方法签名的一部分。
也就是说我们这个类只是一个普通的 POJO 类,并不是一个通用的 java bean 类。

至于说哪种写法好嘛,我觉得后面这种链式调用("method chaining”或者"fluent interface")风格的 api 显然更好,更加的易读。
这种设计风格也正在变得流行,但是这种风格和 bean 规范不兼容,并且也很可能导致现有的库和框架失效失效
(没有记错的话 spring 是遵循了 bean 规范的)。还有一个比较容易出错的地方是这和 java8 的 Consumer接口是有冲突的,
如果打算在接受该接口作为参数的地方以方法引用的方式使用了一个实践了这种风格的 pojo 类的话。

所以还是老老实实按照标准的来吧,虽然说写法看起来是比较繁琐与啰嗦,但是 java bean 毕竟一个应用非常广泛的规范。
没有办法保证项目中用到的组件在使用 bean 类时会通过反射去获取方法。如果按照这种写法来,万一哪里用到了
Introspector.getBeanInfo 这个方法,出了问题 debug 起来就非常困难了,习惯了这种写法后,下意识的会认为
这里没有问题,毕竟在其他地方都用的好好的。

至于说标准写法有什么好处嘛,也确实是有一个,那就是语义十分的清晰,我们可以很明显的认识到这是在操作同一个对象。
链式调用并没有保证说返回的是对象自身还是一个新的对象,因为在 pojo 中大多数情况是返回 this,但是在很多其他用
途的地方返回的是一个新的对象(比如 java8 的 stream api 中),甚至可以故意返回一个当前对象有同样类型且属性的值
也完全相同的新对象来迷惑使用者!也就是说保不准有人这么写(在有多参数构造器的情况下):

public B id(int id) { return new B(id); }

你以为你设置了值,但是每次返回的都是不同的对象,这样链式调用就失效了,而 debug 的时候也很难意识到这个问题。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3195 引用 • 8215 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Typecho

    Typecho 是一款博客程序,它在 GPLv2 许可证下发行,基于 PHP 构建,可以运行在各种平台上,支持多种数据库(MySQL、PostgreSQL、SQLite)。

    12 引用 • 67 回帖 • 445 关注
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    335 引用 • 324 回帖
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    53 引用 • 40 回帖
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    24879 引用 • 102381 回帖
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    28 引用 • 197 回帖 • 28 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 3 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 646 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    172 引用 • 516 回帖
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 176 关注
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    34 引用 • 467 回帖 • 758 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    89 引用 • 1239 回帖 • 415 关注
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    5 引用 • 16 回帖 • 1 关注
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖 • 1 关注
  • 禅道

    禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。

    6 引用 • 15 回帖 • 33 关注
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    239 引用 • 224 回帖 • 1 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 347 关注
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 432 关注
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖 • 3 关注
  • Follow
    4 引用 • 12 回帖 • 9 关注
  • 浅吟主题

    Jeffrey Chen 制作的思源笔记主题,项目仓库:https://github.com/TCOTC/Whisper

    1 引用 • 28 回帖 • 2 关注
  • 开源中国

    开源中国是目前中国最大的开源技术社区。传播开源的理念,推广开源项目,为 IT 开发者提供了一个发现、使用、并交流开源技术的平台。目前开源中国社区已收录超过两万款开源软件。

    7 引用 • 86 回帖
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖 • 3 关注
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    62 引用 • 289 回帖
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 77 关注
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 1 关注
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    7 引用 • 27 回帖 • 1 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 176 关注