13.5.5 员工管理

处理员工的EmployeeController代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package org.fkit.hrm.controller;

import java.util.List;
import org.fkit.hrm.domain.Dept;
import org.fkit.hrm.domain.Employee;
import org.fkit.hrm.domain.Job;
import org.fkit.hrm.service.HrmService;
import org.fkit.hrm.util.tag.PageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

/**
* @Description: 处理员工请求控制器 <br>
*/
@Controller
public class EmployeeController
{
/**
* 自动注入hrmService
*/
@Autowired
@Qualifier("hrmService")
private HrmService hrmService;

/**
* 处理查询请求
* @param pageIndex 请求的是第几页
* @param String job_id 职位编号
* @param String dept_id 部门编号
* @param employee 模糊查询参数
* @param Model model
*/
@RequestMapping(value = "/employee/selectEmployee")
public String selectEmployee(Integer pageIndex, Integer job_id,
Integer dept_id, @ModelAttribute Employee employee, Model model)
{
// 模糊查询时判断是否有关联对象传递,如果有,创建并封装关联对象
this.genericAssociation(job_id, dept_id, employee);
// 创建分页对象
PageModel pageModel = new PageModel();
// 如果参数pageIndex不为null,设置pageIndex,即显示第几页
if (pageIndex != null)
{
pageModel.setPageIndex(pageIndex);
}
// 查询职位信息,用于模糊查询
List<Job> jobs = hrmService.findAllJob();
// 查询部门信息 ,用于模糊查询
List<Dept> depts = hrmService.findAllDept();
// 查询员工信息
List<Employee> employees = hrmService.findEmployee(employee, pageModel);
// 设置Model数据
model.addAttribute("employees", employees);
model.addAttribute("jobs", jobs);
model.addAttribute("depts", depts);
model.addAttribute("pageModel", pageModel);
// 返回员工页面
return "employee/employee";

}

/**
* 处理添加员工请求
* @param String flag 标记, 1表示跳转到添加页面,2表示执行添加操作
* @param String job_id 职位编号
* @param String dept_id 部门编号
* @param Employee employee 接收添加参数
* @param ModelAndView mv
*/
@RequestMapping(value = "/employee/addEmployee")
public ModelAndView addEmployee(String flag, Integer job_id,
Integer dept_id, @ModelAttribute Employee employee, ModelAndView mv)
{
if (flag.equals("1"))
{
// 查询职位信息
List<Job> jobs = hrmService.findAllJob();
// 查询部门信息
List<Dept> depts = hrmService.findAllDept();
// 设置Model数据
mv.addObject("jobs", jobs);
mv.addObject("depts", depts);
// 返回添加员工页面
mv.setViewName("employee/showAddEmployee");
} else
{
// 判断是否有关联对象传递,如果有,创建关联对象
this.genericAssociation(job_id, dept_id, employee);
// 添加操作
hrmService.addEmployee(employee);
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/employee/selectEmployee");
}
// 返回
return mv;

}

/**
* 处理删除员工请求
* @param String ids 需要删除的id字符串
* @param ModelAndView mv
*/
@RequestMapping(value = "/employee/removeEmployee")
public ModelAndView removeEmployee(String ids, ModelAndView mv)
{
// 分解id字符串
String[] idArray = ids.split(",");
for (String id : idArray)
{
// 根据id删除员工
hrmService.removeEmployeeById(Integer.parseInt(id));
}
// 设置客户端跳转到查询请求
// mv.setView(new RedirectView("/hrmapp/employee/selectEmployee"));
// mv.setViewName("forward:/employee/selectEmployee");
mv.setViewName("redirect:/employee/selectEmployee");
// 返回ModelAndView
return mv;
}

/**
* 处理修改员工请求
* @param String flag 标记,1表示跳转到修改页面,2表示执行修改操作
* @param String job_id 职位编号
* @param String dept_id 部门编号
* @param Employee employee 要修改员工的对象
* @param ModelAndView mv
*/
@RequestMapping(value = "/employee/updateEmployee")
public ModelAndView updateEmployee(String flag, Integer job_id,
Integer dept_id, @ModelAttribute Employee employee, ModelAndView mv)
{
if (flag.equals("1"))
{
// 根据id查询员工
Employee target = hrmService.findEmployeeById(employee.getId());
// 需要查询职位信息
List<Job> jobs = hrmService.findAllJob();
// 需要查询部门信息
List<Dept> depts = hrmService.findAllDept();
// 设置Model数据
mv.addObject("jobs", jobs);
mv.addObject("depts", depts);
mv.addObject("employee", target);
// 返回修改员工页面
mv.setViewName("employee/showUpdateEmployee");
} else
{
// 创建并封装关联对象
this.genericAssociation(job_id, dept_id, employee);
System.out.println("updateEmployee -->> " + employee);
// 执行修改操作
hrmService.modifyEmployee(employee);
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/employee/selectEmployee");
}
// 返回
return mv;
}

/**
* 由于部门和职位在Employee中是对象关联映射, 所以不能直接接收参数,需要创建Job对象和Dept对象
*/
private void genericAssociation(Integer job_id, Integer dept_id,
Employee employee)
{
if (job_id != null)
{
Job job = new Job();
job.setId(job_id);
employee.setJob(job);
}
if (dept_id != null)
{
Dept dept = new Dept();
dept.setId(dept_id);
employee.setDept(dept);
}
}
}

单击左侧菜单员工管理下面的添加员工命令,跳转到添加员工界面
输入需要添加的员工信息,单击”添加”按钮,若添加成功则跳转到员工査询界面,显示所有员工信息。
选中职位、性别、所属部门,输入姓名、身份证号码、手机等,单击”搜索”按钮可以完成模糊査询功能。选择每一行最后一列的”操作”按钮,可以进入修改页面,对选中的员工进行修改操作。选择每一行第一列的复选框,单击”删除”按钮,则可以对选中的员工进行删除操作。

13.5.4 职位管理

处理职位的JobController代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package org.fkit.hrm.controller;

import java.util.List;
import org.fkit.hrm.domain.Job;
import org.fkit.hrm.service.HrmService;
import org.fkit.hrm.util.tag.PageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class JobController
{
/**
* 自动注入UserService
*/
@Autowired
@Qualifier("hrmService")
private HrmService hrmService;

/**
* 处理/login请求
*/
@RequestMapping(value = "/job/selectJob")
public String selectJob(Model model, Integer pageIndex,
@ModelAttribute Job job)
{
System.out.println("selectJob -->> " + job);
PageModel pageModel = new PageModel();
if (pageIndex != null)
{
pageModel.setPageIndex(pageIndex);
}
/** 查询用户信息 */
List<Job> jobs = hrmService.findJob(job, pageModel);
model.addAttribute("jobs", jobs);
model.addAttribute("pageModel", pageModel);
return "job/job";

}

/**
* 处理删除职位请求
* @param String ids 需要删除的id字符串
* @param ModelAndView mv
*/
@RequestMapping(value = "/job/removeJob")
public ModelAndView removeJob(String ids, ModelAndView mv)
{
// 分解id字符串
String[] idArray = ids.split(",");
for (String id : idArray)
{
// 根据id删除职位
hrmService.removeJobById(Integer.parseInt(id));
}
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/job/selectJob");
// 返回ModelAndView
return mv;
}

/**
* 处理添加请求
* @param String flag 标记, 1表示跳转到添加页面,2表示执行添加操作
* @param Job job 要添加的职位对象
* @param ModelAndView mv
*/
@RequestMapping(value = "/job/addJob")
public ModelAndView addJob(String flag, @ModelAttribute Job job,
ModelAndView mv)
{
if (flag.equals("1"))
{
// 设置跳转到添加页面
mv.setViewName("job/showAddJob");
} else
{
// 执行添加操作
hrmService.addJob(job);
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/job/selectJob");
}
// 返回
return mv;
}

/**
* 处理修改职位请求
* @param String flag 标记, 1表示跳转到修改页面,2表示执行修改操作
* @param Job job 要修改部门的对象
* @param ModelAndView mv
*/
@RequestMapping(value = "/job/updateJob")
public ModelAndView updateDpet(String flag, @ModelAttribute Job job,
ModelAndView mv)
{
if (flag.equals("1"))
{
// 根据id查询部门
Job target = hrmService.findJobById(job.getId());
// 设置Model数据
mv.addObject("job", target);
// 设置跳转到修改页面
mv.setViewName("job/showUpdateJob");
} else
{
// 执行修改操作
hrmService.modifyJob(job);
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/job/selectJob");
}
// 返回
return mv;
}
}

单击左侧菜单职位管理下面的添加职位命令,跳转到添加职位界面。
输入需要添加的职位名称和详细描述,单击“添加”按钮,若添加成功则跳转到职位査询界面,显示所有职位信息。

  • 输入职位名称,单击”搜索”按钮可以完成模糊查询功能。
  • 选择每一行最后一列的”操作”按钮,可以进入修改页面,对选中的职位进行修改操作。
  • 选择每一行第一列的复选框,单击”删除”按钮,则可以对选中的职位进行删除操作。

13.5.3 部门管理

处理部门的DeptContro11er代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package org.fkit.hrm.controller;

import java.util.List;
import org.fkit.hrm.domain.Dept;
import org.fkit.hrm.service.HrmService;
import org.fkit.hrm.util.tag.PageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class DeptController
{
/**
* 自动注入UserService
*/
@Autowired
@Qualifier("hrmService")
private HrmService hrmService;

/**
* 处理/login请求
*/
@RequestMapping(value = "/dept/selectDept")
public String selectDept(Model model, Integer pageIndex,
@ModelAttribute Dept dept)
{
System.out.println("selectDept -->>");
System.out.println("pageIndex = " + pageIndex);
System.out.println("dept = " + dept);
PageModel pageModel = new PageModel();
System.out.println("getPageIndex = " + pageModel.getPageIndex());
System.out.println("getPageSize = " + pageModel.getPageSize());
System.out.println("getRecordCount = " + pageModel.getRecordCount());
if (pageIndex != null)
{
pageModel.setPageIndex(pageIndex);
}
/** 查询用户信息 */
List<Dept> depts = hrmService.findDept(dept, pageModel);
model.addAttribute("depts", depts);
model.addAttribute("pageModel", pageModel);
return "dept/dept";

}

/**
* 处理删除部门请求
* @param String ids 需要删除的id字符串
* @param ModelAndView mv
*/
@RequestMapping(value = "/dept/removeDept")
public ModelAndView removeDept(String ids, ModelAndView mv)
{
// 分解id字符串
String[] idArray = ids.split(",");
for (String id : idArray)
{
// 根据id删除部门
hrmService.removeDeptById(Integer.parseInt(id));
}
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/dept/selectDept");
// 返回ModelAndView
return mv;
}

/**
* 处理添加请求
* @param String flag 标记, 1表示跳转到添加页面,2表示执行添加操作
* @param Dept dept 要添加的部门对象
* @param ModelAndView mv
*/
@RequestMapping(value = "/dept/addDept")
public ModelAndView addDept(String flag, @ModelAttribute Dept dept,
ModelAndView mv)
{
if (flag.equals("1"))
{
// 设置跳转到添加页面
mv.setViewName("dept/showAddDept");
} else
{
// 执行添加操作
hrmService.addDept(dept);
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/dept/selectDept");
}
// 返回
return mv;
}

/**
* 处理修改部门请求
* @param String flag 标记, 1表示跳转到修改页面,2表示执行修改操作
* @param Dept dept 要修改部门的对象
* @param ModelAndView mv
*/
@RequestMapping(value = "/dept/updateDept")
public ModelAndView updateDpet(String flag, @ModelAttribute Dept dept,
ModelAndView mv)
{
if (flag.equals("1"))
{
// 根据id查询部门
Dept target = hrmService.findDeptById(dept.getId());
// 设置Model数据
mv.addObject("dept", target);
// 设置跳转到修改页面
mv.setViewName("dept/showUpdateDept");
} else
{
// 执行修改操作
hrmService.modifyDept(dept);
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/dept/selectDept");
}
// 返回
return mv;
}
}

单击左侧菜单部门管理下面的添加部门命令,跳转到添加部门界面。
输入需要添加的部门名称和详细描述,单击添加按钮,若添加成功则跳转到部门查询界面,显示所有部门信息。

输入部门名称,单击搜索按钮可以完成模糊査询功能
选择每一行最后一列的操作按钮,可以进入修改页面,对选中的部门进行修改操作
选择每一行第一列的复选框,单击删除按钮,则可以对选中的部门进行删除操作

13.5.2 用户管理

本系统的所有JSP页面都放在WB-INF/jsp目录下,登录页面是loginForm.jsp。用户提交登录请求后,用户输入的登录名、密码被提交到UserControllerlogin方法, UserController将会根据请求参数决定呈现哪个视图资源。
处理用户的UserControlller代码如下:

UserController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package org.fkit.hrm.controller;

import java.util.List;
import javax.servlet.http.HttpSession;
import org.fkit.hrm.domain.User;
import org.fkit.hrm.service.HrmService;
import org.fkit.hrm.util.common.HrmConstants;
import org.fkit.hrm.util.tag.PageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

/**
* 处理用户请求控制器
*/
@Controller
public class UserController
{
/**
* 自动注入UserService
*/
@Autowired
@Qualifier("hrmService")
private HrmService hrmService;

/**
* 处理登录请求
* @param String loginname 登录名
* @param String password 密码
* @return 跳转的视图
*/
@RequestMapping(value = "/login")
public ModelAndView login(@RequestParam("loginname") String loginname,
@RequestParam("password") String password, HttpSession session,
ModelAndView mv)
{
// 调用业务逻辑组件判断用户是否可以登录
User user = hrmService.login(loginname, password);
if (user != null)
{
// 将用户保存到HttpSession当中
session.setAttribute(HrmConstants.USER_SESSION, user);
// 客户端跳转到main页面
mv.setViewName("redirect:/main");
} else
{
// 设置登录失败提示信息
mv.addObject("message", "登录名或密码错误!请重新输入");
// 服务器内部跳转到登录页面
mv.setViewName("forward:/loginForm");
}
return mv;

}

/**
* 处理查询请求
* @param pageIndex 请求的是第几页
* @param employee 模糊查询参数
* @param Model model
*/
@RequestMapping(value = "/user/selectUser")
public String selectUser(Integer pageIndex, @ModelAttribute User user,
Model model)
{
System.out.println("user = " + user);
PageModel pageModel = new PageModel();
if (pageIndex != null)
{
pageModel.setPageIndex(pageIndex);
}
/** 查询用户信息 */
List<User> users = hrmService.findUser(user, pageModel);
model.addAttribute("users", users);
model.addAttribute("pageModel", pageModel);
return "user/user";

}

/**
* 处理删除用户请求
* @param String ids 需要删除的id字符串
* @param ModelAndView mv
*/
@RequestMapping(value = "/user/removeUser")
public ModelAndView removeUser(String ids, ModelAndView mv)
{
// 分解id字符串
String[] idArray = ids.split(",");
for (String id : idArray)
{
// 根据id删除员工
hrmService.removeUserById(Integer.parseInt(id));
}
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/user/selectUser");
// 返回ModelAndView
return mv;
}

/**
* 处理修改用户请求
* @param String flag 标记, 1表示跳转到修改页面,2表示执行修改操作
* @param User user 要修改用户的对象
* @param ModelAndView mv
*/
@RequestMapping(value = "/user/updateUser")
public ModelAndView updateUser(String flag, @ModelAttribute User user,
ModelAndView mv)
{
if (flag.equals("1"))
{
// 根据id查询用户
User target = hrmService.findUserById(user.getId());
// 设置Model数据
mv.addObject("user", target);
// 返回修改员工页面
mv.setViewName("user/showUpdateUser");
} else
{
// 执行修改操作
hrmService.modifyUser(user);
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/user/selectUser");
}
// 返回
return mv;
}

/**
* 处理添加请求
* @param String flag 标记, 1表示跳转到添加页面,2表示执行添加操作
* @param User user 要添加用户的对象
* @param ModelAndView mv
*/
@RequestMapping(value = "/user/addUser")
public ModelAndView addUser(String flag, @ModelAttribute User user,
ModelAndView mv)
{
if (flag.equals("1"))
{
// 设置跳转到添加页面
mv.setViewName("user/showAddUser");
} else
{
// 执行添加操作
hrmService.addUser(user);
// 设置客户端跳转到查询请求
mv.setViewName("redirect:/user/selectUser");
}
// 返回
return mv;
}

/**
* 处理注销退出请求
* @param String flag 标记, 1表示跳转到添加页面,2表示执行添加操作
* @param User user 要添加用户的对象
* @param ModelAndView mv
*/
@RequestMapping(value = "/logout")
public ModelAndView logout(ModelAndView mv, HttpSession session)
{
// 注销session
session.invalidate();
// 跳转到登录页面
mv.setViewName("redirect:/loginForm");
return mv;
}
}

部署hrmapp这个Web应用,在浏览器中输入如下URL来测试应用:

1
http://localhost:8080/hrmapp

Spring MVC会跳转到初始登录页面loginForm.jsp
输入正确的登录名"admin"、密码”123456“,若登录成功,则跳转到人事管理系统界面。
单击左侧菜单”用户管理“下面的”添加用户“命令,跳转到”添加用户“界面。

输入需要添加的用户姓名状态登录名密码,单击”添加“按钮,若添加成功则跳转用户查询界面,显示所有用户信息。

  • 输入用户名、用户状态,单击”搜索”按钮可以完成**模糊查询**功能。
  • 选择每一行最后一列的”操作”按钮,可以进入修改页面,对选中的用户进行修改操作,
  • 选择每一行第一列的复选框,单击”删除”按钮,则可以对选中的用户进行删除操作。

13.5 实现Web层

前面部分已经实现了本应用的所有中间层,系统的所有业务逻辑组件也都部署在Spring容器中了。接下来应该为应用实现Web层了。通常而言,系统的控制器和JSP在起设计。因为当JSP页面发出请求后,该请求被控制器接收,然后控制器负责调用业务逻辑组件来处理请求。从这个意义上来说,控制器是JSP页面和业务逻辑组件之间的纽带。

13.5.1 控制器的处理顺序

对于使用Spring MVC的应用而言,控制器实际上由两个部分组成:系统的核心控制器DispatcherServlet和业务控制器Controller:

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID"
version="3.1">
<!-- 配置spring核心监听器,默认会以 /WEB-INF/applicationContext.xml作为配置文件 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- contextConfigLocation参数用来指定Spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
<!-- 定义Spring MVC的前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springmvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 让Spring MVC的前端控制器拦截所有请求 -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 编码过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- jsp的配置 -->
<jsp-config>
<jsp-property-group>
<!-- 配置拦截所有的jsp页面 -->
<url-pattern>*.jsp</url-pattern>
<!-- 可以使用el表达式 -->
<el-ignored>false</el-ignored>
<!-- 不能在页面使用java脚本 -->
<scripting-invalid>true</scripting-invalid>
<!-- 给所有的jsp页面导入要依赖的库,tablib.jsp就是一个全局的标签库文件 -->
<include-prelude>/WEB-INF/jsp/taglib.jsp</include-prelude>
</jsp-property-group>
</jsp-config>
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

springmvc-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 自动扫描该包,SpringMVC会将包下用了@controller注解的类注册为Spring的controller -->
<context:component-scan
base-package="org.fkit.hrm.controller" />

<!-- 设置默认配置方案 -->
<mvc:annotation-driven />

<!-- 使用默认的Servlet来响应静态文件 -->
<mvc:default-servlet-handler />

<!-- 定义Spring MVC的拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截所有请求 -->
<mvc:mapping path="/*" />
<!-- 自定义判断用户权限的拦截类 -->
<bean class="org.fkit.hrm.interceptor.AuthorizedInterceptor" />
</mvc:interceptor>
</mvc:interceptors>

<!-- 视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />

<!-- 文件上传下载 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 上传文件大小上限,单位为字节(10MB) -->
<property name="maxUploadSize">
<value>10485760</value>
</property>
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding">
<value>UTF-8</value>
</property>
</bean>
</beans>

springmvc-config.xml文件中配置了一个拦截器,用于判断用户是否登录,如果其没有登录,则用户不能访问网站,跳回登录页面重新登录。

AuthorizedInterceptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package org.fkit.hrm.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.fkit.hrm.domain.User;
import org.fkit.hrm.util.common.HrmConstants;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
* 判断用户权限的Spring MVC的拦截器
*/
public class AuthorizedInterceptor implements HandlerInterceptor
{
/** 定义不需要拦截的请求 */
private static final String[] IGNORE_URI ={"/loginForm", "/login", "/404.html"};

/**
* 该方法需要preHandle方法的返回值为true时才会执行。 该方法将在整个请求完成之后执行,主要作用是用于清理资源。
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception exception)
throws Exception
{

}

/**
* 这个方法在preHandle方法返回值为true的时候才会执行。 执行时间是在处理器进行处理之
* 后,也就是在Controller的方法调用之后执行。
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler, ModelAndView mv)
throws Exception
{

}

/**
* preHandle方法是进行处理器拦截用的,该方法将在Controller处理之前进行调用,
* 当preHandle的返回值为false的时候整个请求就结束了。
* 如果preHandle的返回值为true,则会继续执行postHandle和afterCompletion。
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception
{
/** 默认用户没有登录 */
boolean flag = false;
/** 获得请求的ServletPath */
String servletPath = request.getServletPath();
/** 判断请求是否需要拦截 */
for (String s : IGNORE_URI)
{
if (servletPath.contains(s))
{
flag = true;
break;
}
}
/** 拦截请求 */
if (!flag)
{
/** 1.获取session中的用户 */
User user = (User) request.getSession()
.getAttribute(HrmConstants.USER_SESSION);
/** 2.判断用户是否已经登录 */
if (user == null)
{
/** 如果用户没有登录,跳转到登录页面 */
request.setAttribute("message", "请先登录再访问网站!");
request.getRequestDispatcher(HrmConstants.LOGIN)
.forward(request, response);
return flag;
} else
{
flag = true;
}
}
return flag;
}
}

下面通过用例来介绍控制层的实现。

13.4.4 部署业务逻辑组件

applicationContext.xml中增加<context:component-scan/>元素来扫描Spring的相关注解类,就可以通过@Controller@ServiceSpring提供的注解将Java类配置成SpringBean,<context:component-scan/>元素的base-package属性用于指定扫描哪个包(包含子包)下面的Java类.

1
2
3
<!-- 扫描org.fkit包下面的java文件,有Spring的相关注解的类,则把这些类注册为Spring的bean -->
<context:component-scan
base-package="org.fkit.hrm" />

3.18 本章小结

本章介绍了Spring MVC的常用注解,包括@Controller@RequestMapping两个最重要的注解和Spring MVC的常用注解。其中,@Controller注解用于指示Spring类的实例是一个控制器;@RequestMapping注解用来指示一个请求处理方法;@RequestParam等注解用于参数绑定.
接着介绍了Spring MVC中的数据转换,包括重要的数据转换接口HttpMessageConverterJSON格式数据以及XML格式数据的转换。其中,JSON格式的数据转换是目前项目开发中最为常用的转换手段,非常重要。
最后,介绍了Spring MVC的异常处理。 Spring MVC中的异常处理有两种方式:使用简单异常处理器SimpleMappingExceptionResolver和使用@ExceptionHandler注解和@ControllerAdvice注解。使用注解处理异常相对接口来说更加灵活。
本章介绍的注解都是在Spring MVC中使用较多的注解,读者一定要好好掌握第4章将重点介绍Spring MVC的表单标签库。

3.17.5 @RestControlleradvice注解

org.springframework.web.bind.annotation.RestControlleradvice注解本身使用@ControllerAdvice@ResponseBody注解。使用了@RestControllerAdvice注解的类会被看作一个ControllerAdvice,而该类中所有使用@ExceptionHandler注解的方法都默认使用@ResponseBody注解。
@RestControllerAdvice注解的源代码如下:

1
2
3
4
5
6
7
8
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice{
//....
}

示例 @RestControllerAdvice注解的使用

创建一个RestControllerAdviceTest项目,所有文件和配置基本和3.17.3节的RestControllerAdviceTest项目一致。

GlobalExceptionHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.fkit.controller;

import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* @RestController注解本身使用@ControllerAdvicer@ResponseBody注解。
* 使用了@RestControllerAdvice注解的类会被看作一个ControllerAdvicer,
* 而该类中所有使用@ExceptionHandler注解的方法都默认使用了的@ResponseBody注解。
*/
@RestControllerAdvice
public class GlobalExceptionHandler
{
// 处理OrderException自定义异常
@ExceptionHandler(value = OrderException.class)
// 默认使用了的@ResponseBody注解,会将方法返回的Map转换为JSON数据发送给浏览器
public Object OrderErrorHandler(Exception e) throws Exception
{
// 创建返回对象Map并设置属性,会被@ResponseBody注解转换为JSON返回
Map<String, Object> map = new HashMap<>();
map.put("code", 100);
map.put("message", e.getMessage());
map.put("data", "请求失败");
return map;
}
}

GlobalExceptionHandler使用了@RestControllerAdvice注解,该类会被看成一个ControllerAdvice,同时该类中所有使用@ExceptionHandler注解的方法都默认使用了@ResponseBody注解, OrderErrorHandler方法会将Map集合数据转换成JSON格式并返回客户端。
测试结果和ControllerAdviceTest项目的测试结果一致,此处不再赘述。

3.17.4 @ControllerAdvice注解

org.springframework.web.bind.annotation.Controlleradvice注解是Spring3.2提供的新注解,它是一个控制器增强功能注解。该注解源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Target(ElementType.TYPERE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice
{
@AliasFor("basePackages")
String[] value() default{};
@AliasFor("value")
String[] basePackages() default{};
Class<?>[] basePackageClasses() default{};
Class<?>[] assignableTypes() default{};
Class<? extends Annotation>[] annotations() default{}
}

该注解使用@Component注解,也就是说可以使用<context: component-scan>扫描该注解。 Spring官方文档说明,扫描到@Controlleradvice注解之后,会将@ControllerAdvice注解修饰的类的内部使用@ExceptionHandler@InitBinder@ModelAttribute注解的方法应用到所有的请求处理方法上。
在实际开发中@ExceptionHandler注解的功能最强大,另外两个用处不大。

示例 @ControllerAdvice处理异常

新建一个项目ControllerAdviceTest,加入所需的jar文件,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>异常处理示例</title>
<script type="text/javascript" src="js/jquery-1.11.0.min.js"></script>
<script type="text/javascript">
$(function() {
$("#search").click(
function() {
$.post("${pageContext.request.contextPath}/search", null,
function(data) {
// 处理异常
if (data.message) {
alert("与服务器交互出现异常:" + data.message);
} else {
// 获取服务器响应,显示所有订单信息

}
}, "json");
});
})
</script>
</head>
<body>
<br>
<a href="find">@ControllerAdvice异常处理</a>
<hr>
<button id="search">查询订单(返回JSON)</button>
</body>
</html>

需要注意id=" search"的按钮,使用jQuery进行异步査询订单时,如果抛岀异常,则获取返回的JSON数据并提示错误

BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.fkit.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class BookController
{
@GetMapping("/find")
public String find() throws Exception
{
// 除零异常,当前类中没有写异常处理函数,
// 也没有通过继承得到异常处理函数
// 这个异常将由@ControllerAdvice注解的类来处理
@SuppressWarnings("unused")
int i = 5 / 0;
return "success";
}
}

BookController处理"find"请求,在请求处理方法中简单地模拟了一个异常。 BookController中并没有@ExceptionHandler注解修饰的方法,抛出的异常会由@Controlleradvice注解修饰的类中的@ExceptionHandle注解修饰的方法进行处理。

OrderException.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package org.fkit.controller;

public class OrderException extends RuntimeException
{
private static final long serialVersionUID = 6857573209047095609L;
public OrderException()
{
super();
}

public OrderException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace)
{
super(message, cause, enableSuppression, writableStackTrace);
}
// 重载方法
public OrderException(String message, Throwable cause)
{
super(message, cause);
}

public OrderException(String message)
{
super(message);
}

public OrderException(Throwable cause)
{
super(cause);
}
}

OrderException是一个自定义异常类型,继承自RuntimeException.

OrderController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.fkit.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class OrderController
{
@PostMapping("/search")
public String search() throws Exception
{
try
{
// 除零异常
@SuppressWarnings("unused")
int i = 5 / 0;
return "success";
} catch (Exception e)
{
e.printStackTrace();
// 抛出异常,将由@ControllerAdvice注解的类进行异常处理
throw new OrderException("订单查询失败!");
}
}
}

OrderController处理"search"请求,在请求处理方法中简单地模拟了一个异常,被捕捉后抛出OrderException自定义异常类型。

GlobalExceptionHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.fkit.controller;

import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

// GlobalExceptionHandler类使用了@ControllerAdvice注解来修饰,
// 其会被<context:component-scan>扫描,
// 这使得该类中使用@ExceptionHandler注解修饰的方法都被应用到所有请求处理方法上
// 也就是所有请求处理方法抛出的异常都将由该类中对应的@ExceptionHandler注解修饰的方法处理.
@ControllerAdvice
public class GlobalExceptionHandler
{
// 处理Exception类型异常
@ExceptionHandler(value = Exception.class)
public ModelAndView globalErrorHandler(Exception e) throws Exception
{
ModelAndView mav = new ModelAndView();
mav.addObject("ex", e);
mav.setViewName("error");
return mav;
}
// 处理OrderException自定义异常
@ExceptionHandler(value = OrderException.class)
// 返回的结果将会被封装成JSON数据,并返回给客户端
@ResponseBody
public Object OrderErrorHandler(Exception e) throws Exception
{
// 创建返回对象Map并设置属性,会被@ResponseBody注解转换为JSON返回
Map<String, Object> map = new HashMap<>();
map.put("code", 100);
map.put("message", e.getMessage());
map.put("data", "请求失败");
return map;
}
}

GlobalExceptionHandler类使用了@ControllerAdvice注解来修饰,则该类会被<context: component-scan>扫描,该类中使@ExceptionHandler注解修饰的方法将被应用到所有请求处理方法上。
GlobalExceptionHandler类中定义了两个方法:

  • 第一个方法globalErrorHandler使用@ExceptionHandler注解修饰,value=Exception.class属性表示该方法处理所有Exception类型的异常,处理方式和之前一致,将异常信息对象保存到Model,并返回异常处理页面error.jsp;
  • 第二个方法OrderErrorHandler使用@ExceptionHandler注解修饰,value=OrderException.class表示该方法处理OrderException自定义类型的异常,此处的处理方式和之前不同,这里创建一个Map对象保存信息并返回,由于方法使用了@ResponseBody注解,返回的Map对象会被转成JSON数据。

部署ControllAdviceTest这个Web应用,在浏览器中输入如下URL来测试应用:

1
http://localhost:8080/ControllerAdviceTest/
  • 单击"@ControllerAdvice异常处理"超链接,发送"find"请求, BookControllerfind()方法处理请求,抛出异常,异常被@ControllerAdvice注解修饰的GlobalExceptionHandler类中@ExceptionHandler(value = Exception.class)注解修饰的globalErrorHandler方法捕获,处理之后跳转到error.jsp页面.
  • 再次请求index.jsp页面,单击"查询订单(返回JSON)"按钮,发送"search"请求,OrderControllersearch()方法处理请求,抛出自定义异常OrderException,该异常被@ControllerAdvice注解修饰的GlobalExceptionHandler类中@ExceptionHandler(value = OrderException.class)注解修饰的OrderErrorHandler方法捕获,返回JSON信息.

示例 @ExceptionHandler处理异常

新建一个项目ExceptionHandlerTest,加入所需的jar文件,示例代码如下:

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>异常处理示例</title>
</head>
<body>
<a href="test">@ExceptionHandler处理异常</a>
<br>
<a href="login">UserController:使用从父类继承来的异常处理方法</a>
<br>
<a href="find">BookController:使用从父类继承来的异常处理方法</a>
<br>
</body>
</html>

index.jsp中有3个超链接,分别用于测试@ExceptionHandler异常处理和使用父级Controller异常处理。

TestController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.fkit.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class TestController
{
@GetMapping("/test")
public String test() throws Exception
{
// 模拟异常,调用本类中定义的异常处理方法
@SuppressWarnings("unused")
int i = 5 / 0;
return "success";
}
// 在异常抛出的时候,Controller会使用@ExceptionHandler注解的方法去处理异常
// value=Exception.class表示处理所有的Exception类型异常。
@ExceptionHandler(value = Exception.class)
public ModelAndView testErrorHandler(Exception e)
{
ModelAndView mav = new ModelAndView();
mav.setViewName("error");
mav.addObject("ex", e);
return mav;
}
}

TestControllertest()方法是index.jsp页面的超链接"@ExceptionHandler处理异常“的请求处理方法,模拟了一个除数不能为0的异常。
testErrorHandler()方法使用了@ExceptionHandler注解, value = Exception.class表示处理所有的Exception类型异常。TestController类抛出异常的时候,会使用@ExceptionHandler注解的方法去处理异常,而不会直接抛给浏览器testErrorHandler()方法将捕捉到的异常对象保存到ModelAndView当中,传递到JSP页面。

error.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试@ExceptionHandler注解</title>
</head>
<body>
<h3>异常处理页面</h3>
抛出异常信息:${requestScope.ex.message}
</body>
</html>

部署ExceptionHandlerTest这个Web应用,在浏览器中输入如下URL来测试应用:

1
http://localhost:8080/ExceptionHandlerTest/

单击"@ExceptionHandler处理异常“超链接,发送"test"请求,TestControllertest()方方法在处理请求抛出异常,异常会被同一类中的@ExceptionHandler注解修饰的testErrorHandler方法捕获,处理之后跳转到error.jsp页面。

更好一点的做法

基于Controller@ExceptionHandler注解方法在进行异常处理时,对于每个Controller都需要写@ExceptionHandler注解的异常处理方法,在实际开发当中这非常烦琐。可以写一个父类,在父类中完成@ExceptionHandler注解的异常处理方法,所有的Controller继承这个父类,则所有的Controller就都拥有了@ExceptionHandler注解的异常处理方法。

BaseExceptionController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.fkit.controller;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
public class BaseExceptionController
{
// 表示这是一个异常处理方法
// value = Exception.class表示处理的异常类型为Exception
// 也就是处理所有的异常
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(Exception e) throws Exception
{
ModelAndView mav = new ModelAndView();
// 设置模型数据
mav.addObject("ex", e);
// 设置视图
mav.setViewName("error");
return mav;
}
}

BaseController作为父类,定义了一个@ExceptionHandler注解修饰的方法。

UserController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.fkit.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class UserController extends BaseExceptionController
{
@GetMapping("/login")
public String login(String username) throws Exception
{
if (username == null)
{
// 调用本类的异常处理方法
throw new NullPointerException("用户名不存在!");
}
return "success";
}
}

UserController继承BaseController,如果抛出异常,将使用父类的ExceptionHandler注解修饰的方法处理异常。

BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.fkit.controller;

import java.sql.SQLException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class BookController extends BaseExceptionController
{
@GetMapping("/find")
public String find() throws Exception
{
try
{
// 除零异常,调用继承得到的异常处理方法
@SuppressWarnings("unused")
int i = 5 / 0;
return "success";
} catch (Exception e)
{
throw new SQLException("查找图书信息失败!");
}
}
}

BookController继承BaseController,如果抛出异常,将使用父类的@ExceptionHandler注解修饰的方法处理异常.
再次部署Exception2Test这个Web应用,在浏览器中输入如下URL来测试应用,单击"UserController:父级Controller异常处理“超链接,发送login请求,异常处理之后跳转到error.jsp页面.浏览器显示内容如下:

1
2
异常处理页面
抛出异常信息:用户名不存在!

再次请求index.jsp页面,单击BookController:使用从父类继承来的异常处理方法超链接,发送"find"请求,异常处理之后跳转到error.jsp页面.此时浏览器显示内容如下:

1
2
异常处理页面
抛出异常信息:查找图书信息失败!