在这一节,要开始构建一个利用 Spring 2 框架的示例员工信息应用程序。要确定和编码应用程序的业务对象、生成 setter 和 getter、编写服务接口、并对类进行单元测试。应用程序开发周期的这个阶段的执行独立于 Spring 框架。
这份教程的应用程序设计方式的第一步就是 域模型分析 —— 通常叫作 “确定 POJO”。在这种情况下,POJO 代表 Plain Old Java Object。它们也代表应用程序中的 业务对象,这份教程交替使用这两个术语。
确定应用程序域的业务对象,与初学面向对象设计时所做的练习相同。目的是确定要创建的系统中的对象和它们之间的交互关系。更具体地讲,就要是发现:
- 维护状态的对象(和需要维护的状态)
- 这些对象之间的关系
- 这些对象之间的交互作用(如果有的话)
- 在应用程序中需要在这些对象上执行的操作
以在这份教程中要创建的简单服务器应用程序为例。这是个显示员工信息的系统。顾名思义,这个系统中有一个对象是公司的员工。员工信息可以很丰富,但在这个简单的应用程序中,只需要知道以下信息:
- 员工编号
- 姓名
- 中间名缩写
- 姓氏
- 分机号码
- 职务
- 教育程度
- 性别
- 工资
- 奖金
- 提成
- 地址
- 录用日期
- 出生日期
在这个域中能够确定的另一个业务对象就是代表地址的对象。地址之所以从员工信息中分离出来,是因为系统中的其他实体(如果想扩展系统的话)可能也有地址,这样就可以把所有地址放在一起了。为简单起见,假设地址对象只包含街道号码和街道名称。
在这个简单的系统中,每个员工都有一个地址,每个地址也只属于一个员工。这叫作 1 对 1 关系。其他可能的关系包括 1 对多(例如,项目与员工的关系)、多对 1 (例如员工与部门的关系)和 多对多(例如员工与 HR 福利的关系)。其他这些关系模型在 Spring 2 应用程序中都可以建立,但是超出了这份初级教程的范围。
在这个系统中,在员工和地址对象上要有以下操作:
- 创建新员工(和新地址对象)
- 删除员工(和相关的地址对象)
- 更新员工信息
- 查找公司的全部员工
- 根据员工编号查找公司的特定员工
- 根据姓氏查找公司员工
- 根据街道查找公司员工
- 查找工资超过指定数额的员工
- 查找提成超过指定数额的员工
典型情况下,可以通过考虑系统需要的用户界面和系统中需要实现的业务逻辑来确定这些操作。
这个示例中的操作特意被保持简单,以便使教程重点突出。在典型的生产场景中,很可能会有跨多个确定的对象集执行的更复杂的操作,还可能会存在对象之间的直接交互。
确定了业务对象、交互和操作之后,就可以编码和测试了。
编写的第一组对象是构成系统的 POJO。可以用自己喜欢的开发编辑器或 IDE 创建这些对象。本教程假设您采用的是 Eclipse。
请在 Eclipse 中创建名为 Spring2Tutorial 的新 Java 项目,然后创建名为 com.ibm.dw.spring2.Employee 的类,如清单 1 所示:
package com.ibm.dw.spring2;
public class Employee {
private long empid;
private String empno;
private String firstName;
private String midInitial;
private String lastName;
private String phoneNumber;
private String job;
private int educationLevel;
private char sex;
private double salary;
private double bonus;
private double commission;
private Address addr;
private Date hiredate;
private Date birthdate;
}
|
清单 1 定义了在 Employee 对象实例中保存的全部信息。这些字段全被定义成 private,所以需要创建一些 getter 和 setter 方法,以便支持对这些信息的外部访问。
用 Eclipse 生成 getter、setter 和构造函数
在 Eclipse 中生成 getter 和 setter 方法。请在编辑器中右击,并选择 Source->Generate Getter and Setters...。在图 4 所示的弹出对话框中,单击 Select All 然后再单击 OK:
图 4. 在 Eclipse 中生成 getter 和 setter

最后,必须为对象创建一些构造函数。在 Eclipse 中,只需右击并选择 Source-> Generate Constructor。然后需要编辑生成的构造函数。请看清单 2:
public Employee(String empno, String firstName, String midInitial, String lastName,
String phoneNumber, String job, int educationLevel, char sex, double salary,
double bonus, double commission, Address addr, Date hiredate, Date birthdate) {
this.empno = empno;
this.firstName = firstName;
this.midInitial = midInitial;
this.lastName = lastName;
this.phoneNumber = phoneNumber;
this.job = job;
this.educationLevel = educationLevel;
this.sex = sex;
this.salary = salary;
this.bonus = bonus;
this.commission = commission;
this.addr = addr;
this.hiredate = hiredate;
this.birthdate = birthdate;
}
public Employee() {}
|
需要添加清单 2 所示的这个小小的构造函数,因为日后在单元测试阶段要使用它。这就完成了 Employee 业务对象的编码工作。
您可能注意到清单 2 的构造函数中遗漏了 empid。后面要用这个字段来包含 JPA 生成的与 Employee 实例关联的主键。这个键由 JPA 管理,不应当被应用程序修改。现在应当从源代码删除 setter 方法 setEmpid()。
每个 Employee 对象都引用一个 Address 对象。只要 Employee 对象 保存到数据库,就需要保存 Address 对象。
Address 对象的代码如清单 3 所示,其中也包含 getter、setter 和构造函数:
package com.ibm.dw.spring2;
public class Address {
private long id;
private int number;
private String street;
public long getId() {
return id;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public Address( int number, String street) {
this.number = number;
this.street = street;
}
public Address() {}
}
|
现在就完成了域模型中 POJO 的编码,接下来需要从服务接口的角度实现程序程序的 POJO 上的操作。
|
根据前面执行的域分析,应用程序要求一组在业务对象上的操作。
把需求集合转换成代码 —— 更准确地说,是转换成 Java 接口,结果将类似于清单 4。Employee 对象上的每个操作都成为 EmployeeService 接口中的一个方法。
package com.ibm.dw.spring2;
import java.util.List;
public interface EmployeeService {
// create a new employee
public Employee save(Employee emp);
// removing an employee
public void delete(Employee emp);
// update the information on an employee
public Employee update(Employee emp);
// find all the employees in the company
public List<Employee> findAll();
// find an employee by the employee number
public List<Employee> findByEmployeeNumber(String empno);
// find an employee by his name
public List<Employee> findByEmployeeLastName(String lastName);
// find an employees living on a street
public List<Employee> findByAddressStreetName(String streetName);
// find an employee by the internal unique id
public Employee findById(long id);
// find employee over a certain salary
public List<Employee> findEmployeeWithSalaryOver(double sal);
// find employee with a certain commission income
public List<Employee> findEmployeeWithCommissionOver(double comm);
}
|
请注意在清单 4 中,代码是对域分析得到操作列表的直接转化。这个接口不包含任何特定于 Spring 的编码。在这个接口中,目前只知道需要在对象上进行 的操作,对于如何做还一无所知。很快您就会看到如何实现这个接口中的方法,但是首先要进行 POJO 单元测试。
在编码完域模型中的独立 POJO 和服务之后,即可为 POJO 编写单元测试。至此为止,仍然不需要执行任何特定于 Spring 的步骤。实际上,您可能会不加修改地使用您在其他应用程序(或同一应用程序的其他部分)中得到、经过测试的 POJO。
清单 5 显示了 POJOUnitTest.java。这个 JUnit 测试用例测试 Employee 和 Address POJO。
package com.ibm.dw.spring2;
import java.text.SimpleDateFormat;
import java.util.Date;
import junit.framework.TestCase;
public class POJOUnitTest extends TestCase {
private Employee emp1, emp2;
private Address addr1, addr2;
protected void setUp() throws Exception {
addr1 = new Address(10, "Walker Street");
addr2 = new Address();
addr2.setNumber(20);
addr2.setStreet("Walker Street");
emp1 = new Employee("0001", "Joe", "R","Smith",
"4853", "Engineer", 3, 'M',
20000.00, 0.00, 0.00,
addr1
,makeDate("08/08/2006") , makeDate("02/04/1972"));
emp2 = new Employee();
emp2.setEmpno("0002");
emp2.setFirstName("John");
emp2.setMidInitial("T");
emp2.setLastName("Lockheed");
emp2.setPhoneNumber("4333");
emp2.setAddr(addr2);
emp2.setJob("Sales");
emp2.setHiredate(makeDate("01/01/2005"));
emp2.setBirthdate(makeDate("10/8/1966"));
}
public void testEmployee() throws java.text.ParseException {
assertEquals("0001", emp1.getEmpno());
assertEquals("Joe", emp1.getFirstName());
assertEquals("R", emp1.getMidInitial());
assertEquals("Smith", emp1.getLastName());
assertEquals(10, emp1.getAddr()。getNumber());
assertEquals("Walker Street", emp1.getAddr()。getStreet());
assertEquals (makeDate("08/08/2006"),emp1.getHiredate());
assertEquals("0002", emp2.getEmpno());
assertEquals("John", emp2.getFirstName());
assertEquals("T", emp2.getMidInitial());
assertEquals("Lockheed", emp2.getLastName());
assertEquals(20, emp2.getAddr()。getNumber());
assertEquals("Walker Street", emp2.getAddr()。getStreet());
assertEquals (makeDate("01/01/2005"),emp2.getHiredate());
}
public void testAddress() {
assertEquals(10, addr1.getNumber());
assertEquals("Walker Street", addr1.getStreet());
assertEquals(20, addr2.getNumber());
assertEquals("Walker Street", addr2.getStreet());
}
private Date makeDate(String dateString) throws java.text.ParseException {
return (new SimpleDateFormat("MM/dd/yyyy"))。parse(dateString);
}
}
|
|
清单 5 中的测试代码非常易于理解。这个测试根据先构造函数、后逐个字段的方式设置 Employee 和 Address 的字段,最后对值进行检验。这些测试并未面面俱到;没有测试每个字段,但是清单 5 确实表现了如何在麻烦的容器之外对模型进行单元测试。
要在 Eclipse 内运行单元测试,请在导航器视图中右击 POJOUnitTest.java 文件,然后选择 Run as... -> JUnit Test。
Spring 中以 POJO 为中心的开发允许在容器之外独立测试业务对象。与其他方式不同,采用以 POJO 为中心的设计,可以非常迅速地执行单元测试,所以,可以更频繁地执行此类测试(例如,作为构建过程的一部分)。
