随便在百度上搜索 “java bean 规范” 大致能得到如下几条:
- JavaBean 类必须是一个公共类,并将其访问属性设置为 public ,如: public class user{ …}
- JavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,例如:public User() {…}
- 一个 javaBean 类不应有公共实例变量,类变量都为 private ,如: private int id;
- javaBean 属性是具有 getter/setter 方法的成员变量。如果属性类型为 boolean 类型,那么读方法的格式可以是 get 或 is。
例如名为 abc 的 boolean 类型的属性,它的读方法可以是 getAbc(),也可以是 isAbc(); - 一般 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 的下拉框里发现了这种写法
第一次看到的时候真的是感觉醍醐灌顶,但后来发现这就是个坑……
用 mybatis 的时候一切都好好的,每次看到那种啰嗦的写法的时候总觉得自己是无比的高明,
直到有一天我用了 commons-dbutils……
所有获取到的数据都会变成这样!
一开始以为是环境出了问题取不到数据,于是重建一个项目,一个个的排除,结果一无所获。
折腾了半天,最后发现问题出在 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 方法获取的。
查看这个 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 的时候也很难意识到这个问题。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于