Java Bean、EJB、Spring Bean、POJO、BO、DTO、PO、VO、DO和DAO

Java Bean、EJB、Spring Bean、POJO、BO、DTO、PO、VO、DO和DAO
0

JavaBean

根据维基百科上的介绍[1]以及Shaun Abram的一篇博文[2],一个JavaBean通常是一个有以下3种特点的公共Java类:

  1. 有一个无参的构造方法(默认构造方法);
  2. 所有属性都是private的,类外部需要通过public的getter和setter来访问属性;
  3. 实现了Serializable接口。

JavaBeans规范[3]中指出,JavaBeans是一种能在开发工具中可视化地编辑的可重用的软件组件,它需要有一系列的属性,可能有一些事件。在JavaBeans规范的第7章中规定了一个JavaBean的属性应该是private的,类外部应该通过pubilc的getter和setter方法来访问属性。在JavaBeans规范的第2章中有规定一个JavaBean需要是可序列化的,以便传输和持久化JavaBean的状态。但是JavaBeans规范中没有规定一个JavaBean必须有一个无参的构造方法。

JavaBeans规范发布于1997年,所以JavaBeans规范其实主要是围绕Java Applet来设计的。Java Applet中的GUI组件会有属性(例如按钮的名称)和事件(例如输入框的输入事件),GUI组件的状态可能需要在服务器端和客户端之间传输,因此JavaBeans的定义中除了属性还包括了事件,而且也规定了JavaBeans需要可序列化。但是现在许多的Java开发是在纯服务器端软件的领域,服务器应用上的如数据库连接这样的对象或者一些实现业务逻辑的对象是不应该在电脑之间传输的,所以我认为这些对象就不适用传统的JavaBeans的定义,至少不适用可序列化这个特性。

EJB

EJB是企业级JavaBeans(Enterprise JavaBeans)的英文缩写。根据EJB规范[4],一个企业级Bean(Enterprise Bean)有以下特性:

  1. 通常包含操作企业数据的业务逻辑;
  2. 由容器在运行时管理;
  3. 用户需要通过容器访问企业级Bean;
  4. 能在部署时根据运行环境定制;
  5. 能通过注解或XML在编译或部署时指定其中使用的一些配置信息(可配置);
  6. 只使用了EJB规范中规定的服务的企业级Bean能在任意EJB容器中使用(可移植);
  7. 企业级Bean可以不需要重新编译就被封装在一个企业级应用中。

EJB规范中还指出,一个企业级Bean可以是有状态的,也可以是无状态的;可以实现业务逻辑,也可以代表一个持久化的实体。由此可见,EJB和JavaBeans其实是有挺大区别的,可以说一个EJB并不一定是一个JavaBean。一个EJB也不一定有无参构造方法和实现Serializable接口。

EJB规范中也没有规定一个企业级Bean的属性必须是private的,要通过public的getter和setter来访问。不过我们基于类的封装性和降低类之间的耦合的考虑,通常还是会遵循将属性设为private,为其写public的getter和setter这样的设计。

Spring Bean

根据Spring Framework的官方文档[5],在Spring中由Spring IoC容器管理的构成应用主干的对象就是bean。(注:IoC是控制反转 Inverse of Control 的英文缩写。)Spring beans都是由Spring IoC容器根据XML配置文件或注解等方式来实例化、组装和管理的。

我认为,Spring beans和EJB比较类似,它们通常都是数据库连接、事务管理器、消息中间件连接、Session管理器、数据访问对象、业务逻辑服务之类的,只是Spring beans是由Spring IoC容器管理的,EJB是由EJB容器管理的。Spring beans同样不一定有无参构造方法和实现Serializable接口。

POJO

POJO是简单的传统的Java对象(Plain Old Java Object)的英文缩写,另有Plain Ordinary Java Object、Pure Old Java Object等说法,最早由Martin Fowler、Rebecca Parsons和Josh MacKenzie提出。[6][7] 结合Spring文档中对POJO的介绍[8],我们可以知道,POJO是一种尽量不依赖任何第三方库、框架甚至JavaEE规范的实现的Java对象,它应该尽量不继承任何类、不实现任何接口、不包含任何与第三方库或框架相关的注解。

由上述介绍我们可以发现,POJO和JavaBeans、EJB和Spring beans都没有必然的联系。负责GUI组件的继承java.awt.Component的JavaBeans、实现JavaEE规范中的接口的EJB、实现Spring框架中的接口的Spring beans或者使用了Spring框架中的注解的Spring beans都不是POJO。POJO的定义中同样没有规定它有怎样的构造方法和怎样的属性。

因为一个POJO不依赖任何第三方库和框架,它的可维护性和可移植性会更强,开发人员开发与这个POJO相关的功能时,不必考虑它依赖的第三方库或框架的实现,可以更专注于这个POJO本身的业务功能。第三方库或框架升级的时候,不必对POJO进行修改。POJO给了开发人员充分的灵活性,开发人员可以选择将一个POJO应用在任意一个框架中(例如Spring或者Struts、Hibernate或者MyBatis),或者选择不应用在框架中。

BO

BO是业务对象(Business Object)的英文缩写。通常认为,业务对象是用于描述业务逻辑中的对象,但业务对象不依赖具体实现。如果换一种实现方式,例如从关系式数据库迁移到非关系式数据库,或者从单点系统改成一个分布式系统,一个业务对象类需要发生改变的话,那这个业务对象类的设计就是不合理的。

业务对象中的属性应该与业务人员、需求人员、客户理解的一致。例如一个用户信息类中,开发人员可能会在里面设置一个”创建时间“字段,但对于业务人员来说,用户没有“创建时间”,只有“注册时间”,那么用户业务对象中的字段就应该叫做“注册时间”而非“创建时间”。又例如,多数互联网应用,删除一个实体的时候并不会从数据库中物理删除,而是在数据库记录中将该实体的记录的状态字段改为“已删除”,这种“已删除”状态的对象可以是下文说的PO或者DTO,但是一个BO。

又例如,用关系式数据库时,通常会用一个中间表/中间对象来帮助描述多对多关系,我认为这种多对多关系对象(通常是一个PO)就不能算是业务对象。

我觉得,由于业务对象是业务人员,通常业务对象只是在需求或者实现设计中出现,很少人会在代码里写一个纯粹的BO类,即使写BO类,也是作为下文所说的DTO、PO或者VO的基类或组成部分。

DTO

DTO是数据传输对象(Data Transfer Object)的英文缩写。

DTO通常是一个JavaBean(按照有无参构造方法、属性都为private、属性通过public的getter/setter来访问、实现Serializable接口的定义)。DTO也通常是一个POJO,因为要考虑其在交互的系统之间的可移植性。

PO

PO是持久化对象(Persistent Object)的英文缩写。通常我们对PO的理解就跟百度百科[9]中说的一样:一个PO类与一个数据库表对应,一个PO与数据库表中的一行对应。PO通常是也一个JavaBean(按照有无参构造方法、属性都为private、属性通过public的getter/setter来访问、实现Serializable接口的定义)。PO可以是一个POJO,也可能含有JPA规范中定义的一些注解(例如@Entity、@Table@Column、@Id等)。

虽然在很多架构设计中,DTO和PO等对象没有作区分,都放在model包或者entity包中,我认为一个结构清晰的架构应该对PO与DTO等其他对象作区分,因为存储在数据库中的对象与其他业务对象还是有一些区别的。例如数据库对象通常会有创建时间、创建者的用户ID、最后一次修改时间、最后一次修改者的用户ID、状态、数据库自增ID等字段,但这些信息很多是不需要甚至不应该暴露给用户或者其他系统的,即不应该出现在DTO等其他对象中的。

而在对象关系映射(Object/Relation Mapping,简称ORM)框架Hibernate中,上述含义的PO有三种状态:持久化对象状态(也简称PO)、值对象(Value Object,简称VO)状态、游离(Detached)状态。根据Hibernate的文档[10],在Hibernate中,与一个Hibernate session绑定的广义PO是一个Hibernate PO,对一个Hibernate PO作修改后,对Hibernate session做flush或close操作时,Hibernate session会将修改后的Hibernate PO的状态持久化到数据库中。开发者手动将Hibernate PO从Hibernate session中解除绑定(detach)之后,该PO就进入游离状态。游离状态的PO可以重新与一个Hibernate session绑定而重新变为持久化对象状态。

VO

VO有两种含义,一种是值对象(Value Object)的英文缩写,另一种是展现层对象(View Object)的英文缩写。

对于值对象,上文有提到在Hibernate中,值对象是广义PO的一种状态。在Hibernate中,除了持久化对象状态和游离状态的广义PO都是值对象。

值对象也有另一种定义,即所有用于存储数据的对象(如PO和DTO)都是值对象。

展现层对象,又可称“视图对象”,是对应一个客户端页面或者组件中数据的对象。展现层对象跟DTO的结构很相似,都有一些private的属性及其public的getter/setter,因为它们本质上都是用来承载传输的数据,DTO通常用于跨应用传输数据,而展现层对象用于业务逻辑层和客户端页面之间传输数据。对于要不要将DTO和展现层对象合并在一起,下文中推荐的博文有详细的讨论,这里不再赘述。

DO

DO有两种含义,一种是数据对象(Data Object)的英文缩写,另一种是领域对象(Domain Object)的英文缩写。

阿里巴巴的《Java开发手册》中的DO用的就是数据对象这个概念,它的含义跟PO的含义是一样的(一个DO类与一个数据库表对应,一个DO与数据库表中的一行对应)。

而领域对象是领域驱动设计(Domain Driven Design)中的一个概念。对领域驱动设计的解释,推荐大家参考一下阿里的盒马技术团队的文章。对领域对象的具体解释,推荐大家参考一下《领域驱动设计系列文章——浅析VO、DTO、DO、PO的概念、区别和用处》《浅析VO、DTO、DO、PO的概念、区别和用处》这两篇博文。

DAO

DAO是数据访问对象(Data Access Object)的英文缩写。DAO是对数据库具体实现细节的封装、对数据库访问方法的抽象。[11] DAO通常需要依赖注入容器为其注入数据库连接对象之类的对象,因此DAO通常是一个EJB或者是Spring bean。

再啰嗦两句

我认为,上文所说的BO、DTO、PO、VO(展现层对象)、DO和DAO,其作用、功能、职责都是有区别的,为了一个软件工程的结构清晰、软件的部件的功能明确,为了最大程度的软件的可拓展性、可移植性和可维护性,应该将这些对象分别放在不同的包(package)中,不要将这些对象混淆或合并在一起使用,虽然这样会使首次开发时的工作量增加。

参考资料

[1] DropDeadGorgias, Fvdham, JimmyShelter, 等.JavaBeans[M/OL].https://en.wikipedia.org/wiki/JavaBeans, 引用于2018-09-05 10:11

[2] Shaun Abram. JavaBeans vs Spring beans vs POJOs[J/OL]. http://www.shaunabram.com/beans-vs-pojos/, 引用于2018-09-05 10:16

[3] Sun Microsystems, JavaBeans™ API specification Version 1.01-4[S/OL], 1997-08-08:9. http://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/, 引用于2018-09-05 10:53

[4] Sun Microsystems, JSR-000220 Enterprise JavaBeans v.3.0 Final Release[S/OL], 2006-05-08:30-35. http://download.oracle.com/otndocs/jcp/ejb-3_0-fr-eval-oth-JSpec/, 引用于2018-09-05 11:19

[5] Spring Source, Core Technologies[M/OL], 2018-07-26. https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-introduction, 引用于2018-09-05 12:24

[6] Chronist, WikiFan04, Graham87, 等. Plain old Java object[M/OL]. https://en.wikipedia.org/wiki/Plain_old_Java_object, 引用于2018-09-05 12:40

[7] chunchill. 理解POCO[J/OL], 2011-01-08. http://www.cnblogs.com/shineqiujuan/archive/2011/01/08/1930911.html, 引用于2018-09-05 12:47

[8] Spring Source. Understanding POJOs[M/OL]. https://spring.io/understanding/POJO, 引用于2018-09-05 12:54

[9] 匿名. (持久对象 (persistent object))[M/OL]. https://baike.baidu.com/item/Po/6446468, 引用于2018-09-05 18:27

[10] hibernate.org. Working with objects[M/OL]. http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/objectstate.html, 引用于2018-09-06 15:49

[11] Anonymous. Data access object[M/OL]. https://en.wikipedia.org/wiki/Data_access_object, 引用于2018-09-05 14:34