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

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

随便在百度上搜索 “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 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3198 引用 • 8215 回帖 • 1 关注

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    345 引用 • 742 回帖
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    180 引用 • 408 回帖 • 489 关注
  • 安全

    安全永远都不是一个小问题。

    203 引用 • 818 回帖
  • Spark

    Spark 是 UC Berkeley AMP lab 所开源的类 Hadoop MapReduce 的通用并行框架。Spark 拥有 Hadoop MapReduce 所具有的优点;但不同于 MapReduce 的是 Job 中间输出结果可以保存在内存中,从而不再需要读写 HDFS,因此 Spark 能更好地适用于数据挖掘与机器学习等需要迭代的 MapReduce 的算法。

    74 引用 • 46 回帖 • 568 关注
  • GitBook

    GitBook 使您的团队可以轻松编写和维护高质量的文档。 分享知识,提高团队的工作效率,让用户满意。

    3 引用 • 8 回帖 • 1 关注
  • abitmean

    有点意思就行了

    37 关注
  • PWA

    PWA(Progressive Web App)是 Google 在 2015 年提出、2016 年 6 月开始推广的项目。它结合了一系列现代 Web 技术,在网页应用中实现和原生应用相近的用户体验。

    14 引用 • 69 回帖 • 177 关注
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    146 引用 • 972 回帖
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 120 关注
  • 服务

    提供一个服务绝不仅仅是简单的把硬件和软件累加在一起,它包括了服务的可靠性、服务的标准化、以及对服务的监控、维护、技术支持等。

    41 引用 • 24 回帖 • 3 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    88 引用 • 139 回帖 • 1 关注
  • Vditor

    Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React 和 Angular。

    367 引用 • 1844 回帖 • 4 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    730 引用 • 1280 回帖 • 4 关注
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 57 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    66 引用 • 114 回帖 • 198 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 55 关注
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 6 关注
  • V2Ray
    1 引用 • 15 回帖
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    37 引用 • 157 回帖 • 1 关注
  • CodeMirror
    2 引用 • 17 回帖 • 161 关注
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    133 引用 • 1124 回帖 • 115 关注
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 812 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 30 关注
  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 786 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 547 关注
  • Jenkins

    Jenkins 是一套开源的持续集成工具。它提供了非常丰富的插件,让构建、部署、自动化集成项目变得简单易用。

    54 引用 • 37 回帖