业务处理
4.业务处理
本篇主要内容如下:
- 前端渲染
- 后端渲染
- session 存值(自学)
- 拦截器
- 统一异常处理
- 文件上传
- Lombook(了解)
- spring mvc 处理请求过程(自学)
- ssm 项目整合
前端渲染
前端渲染指后台响应一般由 json 字符串的方式返回,再由专业前端开发工程师渲染前端页面,是现在前后端分离开发项目主要技术手段。在前后端分离开发中后端使用 @ResponseBody 注解将返回的对象以 json 格式传给前端,而不是寻找响应视图。@ResponseBody 加在方法上,则该方法返回的对象序列化成 json 字符串,如果 @ResponseBody 加在控制器类上,则该类的全部方法都返回 json 字符串,可简写为 @RestController。通过源码可以看到 @RestController 是 @Controller 与 @ResponseBody 的复合注解。
@ResponseBody
@RequestMapping("/user")
public User testJson(User user){
return user;
}注意:当实体包含时间时,会自动转换为该时间的 long 类型。如果需要按照指定格式序列化成 json 使用 @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") 注解,其中的 timezone ="GMT+8" 表示东八区。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
String value() default "";
}练习:(默写 30 分钟)
1.使用全 html 前端渲染的方式完成 jquery 章节的员工系统练习。
参考代码:
控制器:
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;@Controller
public class IndexController {
private List<Employee> list = new ArrayList<>();@RequestMapping("add") @ResponseBody public Map add(@Validated Employee employee) { list.add(employee); Map<String, Object> map = new HashMap<>(); map.put("success", true); return map; } @RequestMapping("list") @ResponseBody public Map list() { Map<String, Object> map = new HashMap<>(); map.put("success", true); map.put("data", list); return map; } @RequestMapping("delete") @ResponseBody public Map delete(@RequestParam Integer code) { Map<String, Object> map = new HashMap<>(); map.put("success", true); for (int i = 0; i < list.size(); i++) { Employee emp = list.get(i); if (emp.getCode().equals(code)) { list.remove(i); } } return map; } @RequestMapping("update") @ResponseBody public Map update(@Validated Employee employee) { Map<String, Object> map = new HashMap<>(); map.put("success", true); for (int i = 0; i < list.size(); i++) { Employee emp = list.get(i); if (emp.getCode().equals(employee.getCode())) { list.set(i, employee); } } return map; }
}
后端渲染
传统模式还是使用 jsp 渲染页面,通过客户端浏览器调用对应路径的接口,该接口会将数据存入 request 然后转发到对应的 jsp 渲染页面,浏览器收到的是已经渲染好的页面。在 spring MVC 中使用模型视图 ModelAndView 即可指明转发的视图和携带的数据。
public ModelAndView testModelAndView(){
ModelAndView modelAndView = new ModelAndView("index");
//添加模型数据到 ModelAndView 中.
modelAndView.addObject("time", new Date());
return modelAndView;
}2.模型视图 Map 也可以完成在 request 域中存入数据,在对应转发的 jsp 上也能获取数据。但是由于所写代码可读性差,不建议使用。在实际开发中,如果不想由对象指定视图可以使用 Model 或 ModeMap 取代 Map 在 request 中存入数据。
public String testMap(Map<String, Object> map){
map.put("names", Arrays.asList("Tom", "Jerry", "Mike"));
return "index";
}练习: (默写 30 分钟)
1.创建项目 mydemo,并从提交的作业中找到 jquery 章节的员工管理系统练习,将内容用 jsp 后端渲染 jstl 重新实现一下。
提示:使用后端渲染就不要使用 jquery。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/bootstrap.css">
</head>
<body>
<form class="container" action="${pageContext.request.contextPath}/${user!=null?"update":"add"}" method="post">
<table class="table table-bordered table-striped" border="1" cellspacing="0">
<thead>
<tr class="info">
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
<th>电话</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tableData">
<c:forEach items="${list}" var="u">
<tr>
<td>${u.code}</td>
<td>${u.name}</td>
<td>${u.age}</td>
<td>${u.tel}</td>
<td>
<a class="btn btn-warning btn-sm" href="${pageContext.request.contextPath}/update/${u.code}">修改</a>
<a class="btn btn-danger btn-sm" href="${pageContext.request.contextPath}/delete/${u.code}">删除</a>
</td>
</tr>
</c:forEach>
<tr>
<td><input name="code" value="${user.code}"></td>
<td><input name="name" value="${user.name}"></td>
<td><input name="age" value="${user.age}"></td>
<td><input name="tel" value="${user.tel}"></td>
<td>
<button class="btn btn-info btn-sm" type="submit">保存</button>
<a class="btn btn-default btn-sm" href="${pageContext.request.contextPath}/index">取消</a>
</td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
控制器:
@Controller
public class PublicController {
List<User> list = new ArrayList<>();@GetMapping("index") public String path(ModelMap map) { map.addAttribute("list", list); return "index"; } @PostMapping("add") public String add(User user) { list.add(user); return "redirect:/index"; } @PostMapping("update") public String update(User user) { for (int i = 0; i < list.size(); i++) { if (list.get(i).getCode().equals(user.getCode())) { list.set(i, user); } } return "redirect:/index"; } @GetMapping("update/{id}") public String update(@PathVariable Integer id, ModelMap map) { list.forEach(o -> { if (o.getCode().equals(id)) { map.addAttribute("user", o); } }); map.addAttribute("list", list); return "index"; } @GetMapping("delete/{id}") public String delete(@PathVariable Integer id) { for (int i = 0; i < list.size(); i++) { if (list.get(i).getCode().equals(id)) { list.remove(i); } } return "redirect:/index"; }
}
session 存值(自学)
在默认情况下,ModelMap 中的属性作用域是 request 级别是,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享 ModelMap 中的属性,必须将其属性转存到 session 中,这样 ModelMap 的属性才可以被跨请求访问。
spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注 @SessionAttributes 注解来实现的。
@Controller
@SessionAttributes("loginUser")
public class IndexController {@RequestMapping(value="/login",method=RequestMethod.POST)
public String login(String username,String password,Model model) {
User u = userService.login(username, password);
model.addAttribute("loginUser", u);
return "redirect:/user/users";
}@SessionAttributes 参数
- names:这是一个字符串数组。里面应写需要存储到 session 中数据的可能名称。
- types:根据指定参数的类型,将模型中对应类型的参数存储到 session 中 。
- value:其实和 names 功能是一样的。
@SessionAttribute 是用于获取已经存储的 session 数据,并且作用在方法的层面上。一般放在方法参数上。
拦截器
拦截器和先前学习的过滤器用法相似,都是用来做权限验证的,但是该组件属于 spring mvc (过滤器属于 tomcat),定义拦截器比过滤器要简单很多,只需要实现 HandlerInterceptor 接口,并实现相应的方法即可。其中方法返回 Boolean 类型的结果,若返回值为 true,则继续调用后续的拦截器和目标方法。若返回值为 false,则不会再调用后续的拦截器和目标方法。
public class FirstInterceptor implements HandlerInterceptor{
/**
* 该方法在目标方法之前被调用.
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("[FirstInterceptor] preHandle");
return true;//true 表示放行 false 表示不放行
}/**
* 调用目标方法之后, 但渲染视图之前.
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[FirstInterceptor] postHandle");
}/**
* 渲染视图之后被调用. 释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[FirstInterceptor] afterCompletion");
}
}
在 spring mvc 配置文件中配置一个或多个拦截器,同时还可以配置拦截器的拦截路径(mapping)和放行路径(exclude-mapping),spring 使用 ant 风格的路径。在开发中需要用到字符串字符串匹配时使用:AntPathMatcher。
AntPathMatcher matcher = new AntPathMatcher();
String path = "/test/a/b";
String pattern = "/test/";
boolean isMatch=matcher.match(pattern, path);- ?(匹配单个字符)
- *(匹配除/外任意字符)
- /(匹配任意多个目录)。
配置拦截器如下:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/"/>
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/register"/>
<mvc:exclude-mapping path="/css/"/>
<mvc:exclude-mapping path="/js/"/>
<bean class="cn.hxzy.controller.FirstInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>练习:
1.在注册、登录、退出逻辑中在合适的位置使用重定向和 session,完成整个业务逻辑,并使用拦截器控制权限,未登录时不能访问首页 index。
参考代码:
配置拦截器:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/"/>
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/register"/>
<mvc:exclude-mapping path="/css/"/>
<mvc:exclude-mapping path="/js/"/>
<bean class="cn.hxzy.controller.FirstInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
控制器:
@Controller
public class PublicController {
List<User> list = new ArrayList<>();@GetMapping("{pathName}") public String open(@PathVariable String pathName) { return pathName; } @PostMapping("login") public String login(@Validated(User.Login.class) User user, HttpSession session) { for (User u : list) { if (u.getLoginName().equals(user.getLoginName()) && u.getLoginPassword().equals(user.getLoginPassword())) { session.setAttribute("userInfo", u); return "redirect:/index"; } } System.out.println(user); return "login"; } @PostMapping("register") public String register(@Validated(User.Register.class) User user) { user.setId(list.size() + 1); list.add(user); return "redirect:/login?msg=1"; } @GetMapping("logout") public String logout(HttpSession session) { session.invalidate(); return "redirect:/login"; }
}
拦截器
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if (request.getSession().getAttribute("userInfo") != null) {
return true;
}
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}统一异常处理
在项目开发中,免不了出现各种大大小小的异常,spring mvc 提供一套非常完善的异常处理机制,以至于我们在项目中,所有的异常都可以往外抛出,最后由我们的异常处理类来完成异常处理。异常处理分为局部异常处理、全局异常处理和返回 json 的异常处理。
1.处理局部异常(在 Controller 内)
@ExceptionHandler
public ModelAndView exceptionHandler(Exception ex){
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
System.out.println("in testExceptionHandler");
return mv;
}
@RequestMapping("error")
public String error(){
int i = 5/0;
return "hello";
}
2.处理全局异常(在 ControllerAdvice 中)
@ControllerAdvice
public class MyControllerAdvice {
@ExceptionHandler
public ModelAndView exceptionHandler(Exception ex){
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
System.out.println("in testControllerAdvice");
return mv;
}
}3.返回 json 字符串的异常处理
@ResponseBody
@ControllerAdvice
public class MyControllerAdvice {@ExceptionHandler public Map exceptionHandler(Exception ex){ Map map=new HashMap(); map.put("exception", ex.getMessage()); System.out.println("in testExceptionHandler"); return map; }
}
文件上传
在 spring mvc 中想要完成文件上传需要配置 MultipartResolver,MultipartResolver 是一个接口,它的实现类分为 CommonsMultipartResolver 类和 StandardServletMultipartResolver 类。
其中 CommonsMultipartResolver 使用 commons Fileupload 来处理 multipart 请求,所以在使用时,必须要引入相应的 commons-fileupload 依赖;而 StandardServletMultipartResolver 是基于 Servlet 3.0 来处理 multipart 请求的,所以不需要引用其他依赖,但是必须使用支持 Servlet 3.0 的容器才可以,以 tomcat 为例,从 Tomcat 7.0.x 的版本开始就支持 Servlet 3.0 了。
1.配置 CommonsMultipartResolver
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>配置 CommonsMultipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设定默认编码 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 设定文件上传的最大值为5MB,510241024 -->
<property name="maxUploadSize" value="5242880"></property>
</bean>2.配置 StandardServletMultipartResolver
<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>这里并没有配置文件大小等参数,这些参数的配置在 web.xml 中
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<multipart-config>
<!-- 最大上传文件大小(配置不能限制) -->
<max-file-size>297152</max-file-size>
<!-- 最大请求大小(配置不能限制) -->
<max-request-size>4194304</max-request-size>
</multipart-config>
</servlet>控制器接收文件使用 MultipartFile 对象接收。
@PostMapping(value = "/upload")
public void index2(String desc, MultipartFile file) throws IOException {
File file1 = new File("C://" + file.getOriginalFilename());
file.transferTo(file1);
}OriginalFilename 原始的文件名。
通过文件名获取类型(带点)
String type = fileName.indexOf(".") != -1 ? fileName.substring(fileName.lastIndexOf(".")) : null;其他和 jsp Servlet 章节一样,同时也支持 ajax 上传。
Lombok(了解)
IntelliJ IDEA 是一款非常优秀的集成开发工具,功能强大,而且插件众多。lombok 是开源的代码生成库,是一款非常实用的小工具,我们在编辑实体类时可以通过 lombok 注解减少 getter、setter 等方法的编写,在更改实体类时只需要修改属性即可,减少了很多重复代码的编写工作。本文小编只介绍 IntelliJ IDEA 中 lombok 插件的安装和配置以及简单的使用方法。
1.使用 lombok 需要安装 lombok 插件。
2.添加 maven 依赖。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>3.在实体上通过 @Data 注解完成。
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private String password;
}
4.编译后后的类文件
public class User {
private Integer id;
private String name;
private String password;public User() { } public Integer getId() { return this.id; } public String getName() { return this.name; } public String getPassword() { return this.password; } public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } public boolean equals(Object o) { if (o == this) { return true; } else if (!(o instanceof User)) { return false; } else { User other = (User)o; if (!other.canEqual(this)) { return false; } else { label47: { Object this$id = this.getId(); Object other$id = other.getId(); if (this$id == null) { if (other$id == null) { break label47; } } else if (this$id.equals(other$id)) { break label47; } return false; } Object this$name = this.getName(); Object other$name = other.getName(); if (this$name == null) { if (other$name != null) { return false; } } else if (!this$name.equals(other$name)) { return false; } Object this$password = this.getPassword(); Object other$password = other.getPassword(); if (this$password == null) { if (other$password != null) { return false; } } else if (!this$password.equals(other$password)) { return false; } return true; } } } protected boolean canEqual(Object other) { return other instanceof User; } public int hashCode() { int PRIME = true; int result = 1; Object $id = this.getId(); int result = result * 59 + ($id == null ? 43 : $id.hashCode()); Object $name = this.getName(); result = result * 59 + ($name == null ? 43 : $name.hashCode()); Object $password = this.getPassword(); result = result * 59 + ($password == null ? 43 : $password.hashCode()); return result; } public String toString() { return "User(id=" + this.getId() + ", name=" + this.getName() + ", password=" + this.getPassword() + ")"; }
}
spring mvc 处理请求过程(自学)
第一步:用户发起 request 请求,请求至 DispatcherServlet 前端控制器
第二步:DispatcherServlet 前端控制器请求 HandlerMapping 处理器映射器查找 Handler。DispatcherServlet:前端控制器,相当于中央调度器,各各组件都和前端控制器进行交互,降低了各各组件之间耦合度。
第三步:HandlerMapping 处理器映射器,根据 url 及一些配置规则(xml配置、注解配置)查找 Handler,将 Handler 返回给 DispatcherServlet 前端控制器
第四步:DispatcherServlet 前端控制器调用适配器执行 Handler,有了适配器通过适配器去扩展对不同 Handler 执行方式(比如:原始 servlet 开发,注解开发)
第五步:适配器执行 Handler。Handler 是后端控制器,当成模型。
第六步:Handler 执行完成返回 ModelAndView。ModelAndView:springmvc 的一个对象,对 Model 和 view 进行封装。
第七步:适配器将 ModelAndView 返回给 DispatcherServlet
第八步:DispatcherServlet 调用视图解析器进行视图解析,解析后生成 view 视图解析器根据逻辑视图名解析出真正的视图。 View:springmvc 视图封装对象,提供了很多 view 如:jsp、freemarker、pdf、excel。。。
第九步:ViewResolver 视图解析器给前端控制器返回 view
第十步:DispatcherServlet 调用 view 的渲染视图的方法,将模型数据填充到 request 域 。
第十一步:DispatcherServlet 向用户响应结果( jsp 页面、json 数据。。。。)
或者可以这样说:
Spring主要也是通过DispatcherServlet实现了Servlet这个接口,又叫前端控制器,来自前端的请求会先到达这里,它负责到后台去匹配合适的handler。DispatcherServlet的主要工作流程如下:
前端请求到达DispatcherServlet。
前端控制器请求HandlerMappering 查找Handler。
如果查找到存在的处理器,进一步去调用service和dao层
返回结果再到controller层,渲染具体的视图,返回结果给页面。
ssm 项目整合
1.添加 spring mvc(spring-webmvc:5.2.12.RELEASE、jackson-databind:2.12.3)、spring(spring-context、spring-aspects、spring-jdbc):5.2.12.RELEASE 和 mybatis(mybatis:3.4.6、mybatis-spring:1.3.2、mysql-connector-java:5.1.38、logback-classic:1.2.3) 依赖。
2.在 web.xml 中配置启动 spring 容器的监听(ContextLoaderListener)及 spring 的配置文件位置(context-param)。配置 spring mvc 的 DispatcherServlet 和 CharacterEncodingFilter。
3.在 springmvc 配置文件中配置开启注解驱动(mvc:annotation-driven)、静态资源处理(mvc:default-servlet-handler)、组件扫描(context:component-scan)、视图解析(InternalResourceViewResolver)。
4.在 spring 配置文件中配置包扫描(context:component-scan)、 mybatis 的数据源(DriverManagerDataSource)、mybatis session 工厂(SqlSessionFactoryBean)、接口查找配置类(MapperScannerConfigurer)、事务配置(DataSourceTransactionManager)、事务注解驱动(tx:annotation-driven)
注意:
在 spring 包扫描中排除 @Controller 与 @ControllerAdvice,同理 spring mvc 包扫描中只包含这两个注解。
练习:
1.使用 ssm 框架完成和后端渲染技术完成宠物表(pet)的增删改查操作。数据库至少包含如下几个字段:id、image、name、weight。
参考代码:
@Controller
public class PetController {
@Resource
private PetService petService;@GetMapping({"list", "/"}) public String selectOne(ModelMap map) { map.addAttribute("petList", petService.queryAllByLimit(0, 100000000)); return "list"; } @GetMapping({"add"}) public String add() { return "edit"; } @GetMapping({"delete/{id}"}) public String delete(@PathVariable Integer id) { petService.deleteById(id); return "redirect:/list"; } @GetMapping({"update/{id}"}) public String update(@PathVariable Integer id, ModelMap map) { map.addAttribute("pet", petService.queryById(id)); return "edit"; } @PostMapping({"update/{id}"}) public String update(Pet pet, MultipartFile file) throws IOException { setImage(pet, file); System.out.println(pet); petService.update(pet); return "redirect:/list"; } @Value("${fileUploadPath}") private String fileUploadPath; @PostMapping("add") public String add(Pet pet, MultipartFile file) throws IOException { setImage(pet, file); petService.insert(pet); return "redirect:/list"; } private void setImage(Pet pet, MultipartFile file) throws IOException { if (!file.isEmpty()) { String uuid = UUID.randomUUID().toString().replaceAll("-", ""); String filename = file.getOriginalFilename(); String type = filename.substring(filename.lastIndexOf(".")); file.transferTo(new File(fileUploadPath + "img/" + uuid + type)); pet.setImage("/img/" + uuid + type); } }
}
素材管理系统
资源管理系统在用户未登录时支持图片素材上传,前台可查看全部资源素材。
前台需求:
1.手动上传文件
2.提供 ajax 与 base64 接口和上传说明
3.分页展示全部资源
后台需求:
1.RBAC(Role-Based Access Control )基于角色的访问控制系统。
2.资源素材管理


项目考查:办公 OA 系统(审批系统,后台管理系统,财务报销系统,客户关系管理系统 crm,地产房源销售系统,进销存系统)(3 天)
1.挑选合适的后台模板,参考示例项目中的 OA 系统完成开发。
2.书写符合阿里巴巴代码规范的业务逻辑完成,普通用户和超级管理员页面的相关业务逻辑。
3.使用 java 的面向对象的开发思想,分层结构 controller,service,dao,entity,interceptor 等。
4.设计好相关权限,未登录的用户不能访问任何页面,不同角色只能访问对应的页面。
5.在相关不符合现实逻辑的情况下可以修改页面逻辑。
6.具体功能上可参照如下,但总体功能不低于 6 个。

最后提交内容:
1.专业视频(从启动 tomcat 开始 )
2.项目源码及数据库脚本
3.专业 ppt
4.部署好项目的 tomcat

浙公网安备 33010602011771号