MVC 框架

在 spring 框架和 Spring Boot 中,最常用的技术就是 MVC 框架,MVC 框架会处理类似如下相同技术的需求:

1、HTTP UTL 映射到 Controller 某个方法;
2、HTTP 参数映射到 Controller 方法的参数上,比如参数映射到某个Java对象,或者上传附件映射到某个File对象上;
3、参数的校验;
4、MVC 错误处理;
5、MVC 中如何调用视图;
6、MVC 中如何序列化对象成JSON;
7、拦截器高级定制;

引入依赖
Spring Boot 集成 Spring MVC 框架并实现自动配置,只需要在 pom 中添加以下依赖即可,不需要其他任何配置。

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

Web 应用目录结构
Web的模板文件位于 resources/templates 目录下,模板文件使用的静态资源文件,如 js css 图片,存放在 resources/statis 目录下。在 MVC 中,视图名自动在 templates 目录下找到对应的模板名称,模板中使用的静态资源将在 static 目录下查找。

Java 包名结构
通常会创建如下子包名:

Controller ---
Service ---
entity ---
conf ---

使用 Controller

Spring MVC 框架不像传统的 MVC 框架那样必须继承某个基础类才能处理用户的 HTTP 请求, Spring MVC 只需要在类上声明 @Controller,标注这是一个 Controller 即可。对于用户请求,使用 @RequestMapping 映射 HTTP 请求到特定的方法处理类。

@Controller
@RequestMapping("/test)
public class HelloworldController {
    @RequestMapping("/index.html")
    public String say(Model model) {
        model.addAttribute("name","hello world");
        return "/index.html";
    }
}

如以上代码所示,@Controller 作用于类上,表示这是一个 MVC 中的 Controller。
@RequestMapping 既可以作用于方法上,也可以作用在类上。如上所示,用户如果访问 /test/index.html ,则会交给 HelloworldController.say 方法来处理。

say 方法有一个参数 Model,这是 Spring MVC 可识别的一个参数类型,用来表示 MVC 中的Model。可以在这个 Model 添加多个变量,这些变量随后可以用于视图渲染。

say 方法返回的类型是字符串,默认是视图名称。Spring Boot 的视图默认保存在 resources/templates目录下,因此,渲染的视图是 /resources/templates/index.html 模板文件。

MVC 框架有时候返回的是 JSON 字符串,如果想直接返回内容而不是视图名,则需要在方法上使用 @ResponseBody:

@RequestMapping("/index.json")
public @ResponseBody String say() {
    return "hello world";
}

ResponseBody 注解直接将返回的对象输出到客户端,如果是字符串,则直接返回;如果不是,则默认使用 Jackson 序列化成 JSON 字符串后输出。

建议在 Spring Boot 应用中,如果期望返回 JSON, URL 请求后资源后缀是 json;如果期望返回视图,URL 请求资源后缀是 html

URL 映射到方法

RequestMapping

可以使用 @RequestMapping 来映射 URL,比如 /test 到某个 Controller 类,或者是某个具体的方法,通常类上的注解 @RequestMapping 用来标注请求的路径,方法上的 @RequestMapping 注解进一步映射特定的 URL 到某个具体的处理方法。

RequestMapping 有多个属性来进一步匹配 HTTP 请求到 Controller 方法,分别是:

value, 请求的 URL 路径,支持 URL 模板、正则表达式。
method,HTTP 请求方法,有 GET、POST、PUT 等。
consumes,允许的媒体类型(Media Types),如 consumes = "application/json", 对应于请求的 HTTP 的 Content-Type。
produces, 相应的媒体类型,如 produces = "application/json",对应于 HTTP 的 Accept 字段。
params,请求参数,如 params="action=update"
headers, 请求的 HTTP 头的值,如 headers = "myHeader=myValue"

URL 路径匹配

属性 value 用于匹配一个 URL 映射,value 支持简单的表达式类匹配:

@RequestMapping(value="/get/{id}.json")
public @ResponseBody User getById(@PathVariable("id") Long id) {
    return userService.getUserById(id);
}

上边访问路径是 /get/1.json, 将调用 getById 方法,且参数 id 的值是 1 。注解 PathVariable 作用在方法参数上,用来表示参数的值来自于 URL 路径。

HTTP method 匹配

@RequestMapping 提供 method 属性来映射对应 HTTP 的请求方法,通常 HTTP 请求方法有以下内容:

GET, 用来获取 URL 对应的内容
POST,用来向服务器提交信息
HEAD,同 GET,但不返回消息体,通常用于返回 URL 对应的元信息,如过期时间等。
PUT,同 POST,用来向服务器提交信息,但语义上更像一个更新操作。同一个数据,多次 PUT 操作,也不会导致数据发生改变。POST 在语义上更类似新增操作。
DELETE, 删除对应的资源信息。
PATCH 方法,类似 PUT 方法,表示信息的局部更新。

通常对于 Web 应用,GET 和 POST 是经常使用的选项,对于 REST 接口,则会使用 PUT、DELETE 等用来从语义上进一步区分操作。

Spring 提供了简化后的 @RequestMapping, 提供了新的注解来表示 HTTP 方法:

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

consumes 和 produces
属性 consumes 意味着请求的 HTTP 头的 Content-Type 媒体类型与 consumes 的值匹配,才能调用此方法。

@GetMapping(value="/consumes/test.json",consumes="application/json")
@ResponseBody
public User forjson() {
    return userService.getUserById(11);
}

这里映射指定请求的媒体类型是 application/json,因此,此方法接受一个 AJAX 请求。如果通过浏览器直接访问,则会出错,因为通过浏览器访问,通常并没有设置 Context-Type。

为了成功调用上述Controller方法,AJAX调用必须设置 Content-Type 为 application/json

$.ajax({
    type: "get",
    url: "/consumes/test.json",
    contentType: "application/json",
    ....
})

produces属性对应于 HTTP 请求的 Accept 字段,只有匹配的上的方法才能被调用。

@GetMapping(value="/user/{userid}",produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public User getUser(@PathVariable Long userId, Model model) {
    return userService.getUserById(userId);
}

通常浏览器都会将 Accept 设置为 .

params 和 header 匹配
可以从请求参数或者 HTTP 头中提取值来进一步确定调用的方法,有以下三种形式:

如果存在参数,则通过;
如果不存在参数,则通过;
如果参数等于某一个具体值,则通过。

@PostMapping(value="/update.json",params="action=save")
@ResponseBody
public void saveUser() {
    System.out.println("call save");
}

header 和 params 一样:

@PostMapping(value="/update.json",headers="action=save")
@ResponseBody
public void saveUser() {
    System.out.println("call save");
}

方法参数

Spring 的 Controller 方法可以接受多种类型参数,比如我们看到的 path 变量,还有 MVC 的 Model,除此之外,方法还能接受以下参数。

@PathVariable, 可以将URL中的值映射到方法参数中。
Model,Spring 中通用的MVC模型,也可以使用 Map 和 ModelMap 作为渲染视图的模型。
ModelAndView, 包含了模型和视图路径的对象。
JavaBean,将HTTP参数映射到 JavaBean对象。
MultipartFile,用于处理文件上传。
@ModelAttribute 使用该注解的变量将作为 Model 的一个属性。
WebRequest 或者 NativeWebRequest,类似 Servlet Requset ,但做了一定封装。

PathVariable

注解 PathVariable 用于从请求 URL 中获取参数并映射到方法参数中

@RequestMapping(value="/get/{id}.json")
public @ResponseBody User getById(@PathVariable("id") Long id) {
    return userService.getUserById(id);
}

Model & ModelAndView
任何 MVC 框架都有一个类似 Map 结构的 Model,可以向 Model 添加视图需要的变量,Spring MVC 中的 Model 就是完成此功能的。Model 对象主要有如下方法:

Model addAttribute(String attributeName, Object attributeValue),向模型添加一个变量,attributeName 指明了变量的名称,可以在随后的视图里引用,attributeValue 代表了变量。
Model addAttribute(Object attributeValue) , 向模型添加一个变量,变量的名字就是其类名首字母小写后转为 Java 变量。

ModelAndView 对象类似 Model,但额外提供了一个视图名称。

javaBean 接受 HTTP 参数
可以通过注解 @RequestParam来进一步限定 HTTP 参数到 Controller 方法的映射关系,RequestParam 支持如下属性:

value, 指明 HTTP 参数的名称
required, boolean 类型,声明此参数是否必须有,如果 HTTP 参数里没有,则会抛出 400 错误。
defaultValue,字符类型,如果 HTTP 参数没有提供,可以指定一个默认字符串。

可以将 HTTP 参数转为 JavaBean 对象, HTTP 参数的名字对应到 POJO 的属性名:

@GetMapping(path="/update.json")
@ResponseBody
public String updateUser(User user) {
return "success";
}

通常,HTTP 提交了多个参数,Spring 支持按照前缀自动映射到不同的对象上。比如用户交了订单信息,订单信息通常包含了多个订单明细。

需要创建一个 Form 对象来接收 HTTP 提交的数据:

public class OrderPostForm {
private Order order;
private List<OrderDetail> details;
}

如上所示,以“order”为前缀的HTTP参数映射到 OrderPostForm 类的 order 属性上,以 details 属性为前缀的 HTTP 参数映射到 OrderPostForm 的 details 属性上。

<form>
订单名称 <input name="order.name"/>
订单明细一
<input name="details[0].name" />
订单明细二
<input name="details[1].name" />
</form>

则下面的 Controller 的方法可以处理这个请求

@PostMapping(path="")
@ResponseBody
public String saveOrder(OrderPostForm form) {
return "success";
}

@RequestBody 接受 JSON
Controller 方法带有 @RequestBody 注解的参数,意味着请求的 HTTP 消息体的内容是一个 JSON,需要转化为注解指定的参数类型。Spring Boot 默认使用 Jackson 来处理反序列化工作。

如果客户端发起了一个 JSON 请求,则有如下定义来接受 JSON:

@PostMapping(path="/savejsonorder.json")
@ResponseBody
public String saveOrderByJson(@ResquestBody User user) {
return user.getName();
}

这段代码能够处理客户端发起的JSON请求

>curl -XPOST 'http://127.0.0.1:8080/savejsonorder.json' -H 'Content-Type: application/json' -d '{
"name": "Hello",
"id":1
}'

MultipartFile
通过 MultipartFile来处理文件上传:

@PostMapping("/form")
@ResponseBody
public String handleFormUpload(String name, MultipartFile file) throws IOException {
if(!file.isEmpty()) {
String fileName = file.getOriginalFilename();
InputStream ins = file.getInputStream();
return "success";
}
return "failure";
}

MultipartFile 提供了一下方法来获取上传文件的信息。

  1. getOriginalFilename 获取上传文件的名字。
  2. getBytes 获取上传文件内容,转为字节数组。
  3. getInputStream 获取一个 InputStream
  4. isEmpty 上传文件内容为空,或者就没有文件上传
  5. getSize 文件上传的大小
  6. transferTo(File dest)保存上传文件到目标文件系统。

如果是同时上传多个文件,则使用 MultipartFile 数组来接受多个文件上传:

@PostMapping("/form")
@ResponseBody
public String handleFormUpload(String name, MultipartFile[] files) throws IOException {

}

这要求自HTTP请求中包含多个名为 "files" 的文件:

<form action="filesUpload.html" method="post" entype="multipart/form-data">
1
<input  type ="file" name="files" />
2
<input  type ="file" name="files" />
3
<input  type ="file" name="files" />
4
<input  type ="file" name="files" />

</form>

可以通过配置文件对Spring Boot 上传的文件进行限定,默认如下:

spring.servlet.multipart.enabled=true
spring.servlet.multipart.file-size-threshold=0
spring.servlet.multipart.location=
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.resolve-lazily=false

参数 enabled 默认为 true,即允许附件上传。
file-size-threshold 限定了当上传的文件超过一定长度时,就先先写到临时文件里。这有助于上传文件不占用过多内存,单位时 MB 或者 KB。默认是0,不限定阈值。
location 值指的是临时文件的存放目录,如果不限定,则是 Web 服务器提供的一个临时目录。
max-file-size 属性指定了单个文件的最大长度,默认是 1 MB
max-request-size 属性说明单次 HTTP 请求上传的最大长度,默认 10 M
@ModelAttribute
注解 ModelAttribute 通常作用在 Controller 的某个方法上,此方法会首先被调用,并将方法结果作为 Model的属性,然后再调用对应的 Controller 处理方法。

@ModelAttribute
public void findUserById(@PathVariable Long id, Model model) {
mode.addAttribute("user",userService,getUserById(id));
}

@GetMapping(path="/{id}/get.json")
@ResponseBody
public String getUser(Model model) {
System.out.println(model.containsAttribute("user"));
return "success";
}

对于 HTTP 请求,modelattribute/1/get.json ,会调用 findUserById 方法,取得 user, 并添加到模型里。使用 ModelAttribute 通常可以用来向一个 Controller 中需要的公共模型添加数据。

验证框架
Spring Boot 支持 JSR-303、Bean 验证框架,默认实现使用的是 Hibernate validator。在 Spring MVC 中,只需要使用 @Valid 注解在方法参数上,Spring Boot 即可对参数对象进行校验,校验结果放在 BindingResult 对象中。

JSR-303
jsr-303 是 Java 标准的验证框架,已有的实现有 Hibernate validator。JSR-303 定义了一些列注解来验证Bean 的属性,常用的有如下几种。

空检查
1、 @Null,验证对象是否为空
2、 @NotNull,验证对象不为空
3、 @NotBlank,验证字符串不为空或者不是空字符串
4、 @NotEmpty, 验证对象不为 null,或者集合不为空

长度检查
1、 @Siz(min= , max=), 验证对象长度,可支持字符串、集合;
2、 @Length, 字符串大小
数值检测
1、 @Min,验证数字是否大于指定值
2、 @Max, 验证数字是否小于等于指定的值
3、 @Digits, 验证数字是否符合指定格式,如 @Digits(integer=9,faraction=2)
4、 @Range, 验证数字是否在指定范围内,如 @Range(min=1,max=100)
其他
1、 @Email, 验证是否为邮件格式,为 null 则不做校验
2、 @Pattern, 验证String对象是否符合正则表达式的规则

public class WorkInfoForm {
@NotNull
Long id;
@Size (min=3,max=20)
String name;
@Email
String email;
}

通常不同的业务逻辑会有不同的验证逻辑,比如对于 WorkInfoForm来说,当更新的时候, id 必须不为null,但增加的时候, id必须是null。

JSR-303 定义了group概念,每个校验注解都必须支持。校验注解作用在字段上的时候,可以指定一个或者多个group,当Spring Boot校验对象的时候,也可以指定校验的上下文属于那个group。这样,只有group匹配的时候,校验注解才能生效。上面的WorkInfoForm定义id字段校验可以更改为如下内容:

public class WorkInfoForm {

// 定义一个类,更新时校验组
public interface Update{}
//定义一个类,添加时校验组
public interface Add{}

@NotNull(groups={Update.class})
@Null(groups={Add.class})
Long id;
@Size (min=3,max=20)
String name;
@Email
String email;
}

这段代码表示,当校验上下文为 Add.class 的时候,@Null 生效,id需为空才能校验通过;当校验上下文为 Update.class 的时候,@NotNull 生效,id不能为空。

MVC 中使用 @Validated
在Controller中,只需要给方法参数添加上@Validated即可触发一次校验。

@ResponseBody
@RequestMapping("/addworkinfo.html")
public void addWorkInfo(@Validated({WorkInfoForm.Add.class}) WorkInfoForm workInfo, BindingResult result) {
if(result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
FieldError error = (FieldError) list.get(0);
System.out.println(error.getObjectName()+"," + error.getField() + "," + error.getDefaultMessage());
}
}

此方法可以接受 HTTP 参数并映射到 WorkInfoForm对象,WorkInfoForm使用了@Validated注解,将触发Spring的校验,并将验证结果存放在BindingResult对象中。这里,Validated 注解使用了校验的上下文 WorkInfoForm.Add.class 因此,整个校验将按照 Add.class 来校验。

BindingResult 包含了验证结果,提供了如下方法。

hasErrors 判断验证是否通过。
getAllError 得到所有的错误信息,通常返回的是 FieldError 列表

如果 Controller 参数未提供 BindingReult 对象,则 SpringMVC 将抛出异常。

自定义校验
JSR-303 提供的大部分校验注解已经够用,也允许定制校验注解,比如在 WorkInfoForm 类中,我们新增加一个加班时间。

@WorkOverTime(max=2)
int workTime;

属性 workTime 使用了注解 @WorkOverTime,属性值超过 max 值的时候,将会验证失败。WorkOverTime 跟其他注解差不多,但提供了 @Constraint 来说明用什么类作为验证注解实现类。

@Constraint(validatedBy= {WorkOverTimeValidator.class})
@Documented
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WorkOverTime {
String message() default "加班时间过长,不能超过{max} 小时";
int max() default 5;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

@Constraint注解声明用什么类来实现验证,我们将创建一个 WorkOverTimeValidator 来进行验证。验证注解必须要提供如下信息。

message 用于创建错误信息
groups 验证规则分组,比如新增和修改的验证规则不一样,分为两个组,验证注解必须提供
payload 定义了验证的有效负荷

WorkOverTimeValidator 必须实现 ConstraintValidator 接口 initialize 方法及验证方法 isValid;

public class WorkOverTimeValidator implements ConstraintValidator<WorkOverTime, Integer> {
WorkOverTime work;
int max;
public void initialize(WorkOverTime work) {
thie.work = work;
max = work.max();
}

public boolean isValid(Integer value, ConstraintValidatorContext context) {
if(value == null) {
return true;
}
return value<max>;
}
}

WebMvcConfigurer
WebMvcConfigurer 是用来全局制定 Spring Boot 的 mvc 特性。开发者通过实现 WebMvcConfigurer 接口来配置应用的 MVC 全局特性。

@Configuration
public class MvcConfigurer implements WebMvcConfigurer {
//拦截器
public void addInterceptors (InterceptorRegistry registry) {

}

// 跨域访问配置
public void addCorsMappings(CorsRegistry registry) {

}

//格式化
public void addFormatters (FormatterRegistry registry) {

}

// URI 到视图的映射
public void addViewControllers(ViewControllerRegistry registry) {

}

}

拦截器
通过 addInterceptors方法可以设置多个拦截器,比如对特定的 URI 设定拦截器以检查用户是否登录,打印处理用户请求耗费的时间等。

public void addInterceptors(InterceptorRegistry registry) {
// 增加一个拦截器,检查会话, URL 以 admin 开头的都适用此拦截器
registry.addInterceptor(new SessionHandlerInterceptor()).addPathPatterns("/admin/**");
}

class SessionHandlerInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
User user = (User) requset.getSession().getAttribute("user");
if(user == null) {
//没有登录,重定向到 login.html
response.sendRedirect("/login.html");
return false;
}
return true;
}

publica void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// controller 方法处理完毕后,待用此方法
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) throws Exception {
// 页面渲染完毕后调用此方法,通常用来接触某些资源。
}
}

上边这段代码,我们在 MvcConfigurer 总实现了 addInterceptors 方法,并对访问地址 /admin/** 添加了一个拦截器。

拦截器,有以下三个方法需要覆盖实现

preHandle,在调用 Controller 方法前会调用此方法
postHandle,在调用Controller 方法结束后、页面渲染之前调用此方法,比如可以在这里将渲染的视图名称更改为其他视图名
afterCompletion ,页面渲染完毕后调用此方法。
跨域访问
处于安全的考虑,浏览器会禁止 AJAX 访问不同域的地址。
Spring Boot 提供了对 CORS 的支持,可以实现 addCorsMappings 接口来添加特定的配置:

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}

允许所有跨域访问,或者更为精细的控制

public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://doain2.com")
.allowedMethods("POST","GET");
}

跨域原理简单理解就是发起跨域请求的时候,浏览器会对请求域返回的响应信息检查HTTP头,如果Access-Control-Allow-Origin 包含了自身域,则表示允许访问。否则报错,这就是 allowedOrigins 的作用。

注册Controller
应用有时候没有必要为一个 URL 制定一个Controller方法,可以直接将 URI 请求转到对应的模版渲染上。可以直接通过 ViewControllerRegistry 注册一个:

public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index.html").setViewName("/index.html");
registry.addRedirectViewController("/**/*.do","/index.html");
}

视图技术
Redirect 和 Forward

Contoller 中重定向可以返回以 "redirect:" 为前缀的 URL

@RequestMapping("/order/saveorder.html")
public String saveOrder(Order order) {
Long orderId = service.addOrder(order);
return "redirect:/order/detail.html?orderId=" + orderId;
}

或者直接使用 RedirectView

RedirectView view = new RedirectView("/order/detail.html?orderid=" + orderId);

Spring MVC 也支持 foward前缀,用来在 Controller 执行完毕之后,再执行另外一个 Controller 的方法。

通用错误处理
在 Spring Boot 中, Controller 中抛出的异常默认交给了 /error 来处理,应用程序可以将 /error 映射到一个特定的 Controller 中处理来代替 Spring Boot 的默认实现,应用可以继承 AbstractErrorController 来统一处理系统的各种异常。

@Controller
public class ErrorController extends AbstractErrorController {
Log log = LogFactory.getLog(ErrorController.class);

@Autowired
ObjectMapper objectMapper;

public ErrorController() {
super(new DefaultErrorAttributes());
}

@RequestMapping("/error")
public ModelAndView getErrorPath(HttpServletRequest request, HttpServletResponse response) {
//处理异常

}
}

AbstractErrorController 提供了多个方法可以从 request 中获取错误信息,包含以下信息:

timestamp, 错误发生的时间
status 对应于 HTTP status 如 404
error, 错误消息,如 Bad Requset、Not Found
message, 详细错误信息
exception, 如果应用抛出异常
path,请求的 URI
errors, @Validated 校验错误的时候,校验结果信息放在这里。

考虑到异常信息直接显示给应用系统客户并不合适,尤其是 RuntimeException。同时,还要区分页面渲染和JSON 请求这两种不同的情况,前者应该返回一个错误页面,后者应该返回一个 JSON 结果。因此,为应用提供的统一错误处理代码:

@RequestMapping(ERROR_PATH)
public ModelAndView getErrorPath (HttpServletRequest request, HttpServletResponse response) {
Map<String,Object> model = Collections.unmodifiableMap(getErrorAttributes(request, false));
// 获取异常,有可能为空

Throwable cause = getCause(request);
int status = (Interger) model.get("status");

// 错误信息
String message = (String) model.get("message");

// 提示
String errorMessage = getErrorMessage(cause);
//后台打印日志信息
log.info(status+"," + message , cause);
response.setStatus(status);
if(!isJsonRequest(requst)) {
//error.html
ModelAndView view = new ModelAndView("/error.html");
view.addAllObjects(model);
view.addObject("errorMessage",errorMessage);
view.addObject("status",status)
view.addObject("cause",cause);
return view;
}else {
Map error = new HashMap();
error.put("success",false);
error.put("errorMessage",errorMessage);
error.put("message",message);
writeJson(response,error);
return null;

}

}

getErrorAttributes 方法是 AbstractErrorController 提供的用于获取错误信息的方法,返回一个 Map,包含的数据如下。

getCause 方法用于获取应用系统的异常,定义如下:

protected Throwable getCause(HttpServletRequest request) {
Throwable  error = (Throwable) request.getAttribute("javax.servlet.error.exception");
if(error != null) {
// MVC 有可能会封装异常成 ServletException 需要调用 getCause 获取真正的异常 
while( error instanceof ServletException && error.getCause() != null) {
error = ((ServletException ) error).getCause();
}
}
return error;
 }

getErrorMessage 方法返回一个友好的异常信息,而不是 Spring Boot 提供的 message 包含的信息:

protected String getErrorMessage(Throwable ex) {
return "服务器错误,请联系管理员";
}

通常这个友好信息很简单,类似上边。也可以根据应用系统自定义的异常进一步输出详细信息

protected String getErrorMessage(Throwable ex) {
if(ex instanceof YourApplicationException) {
return ((YourApplicationException) ex).getMessage();
}

return "服务器错误,请联系管理员";
}

isJsonRequest 方法用来区分客户端发起的是页面渲染请求还是 JSON 请求:

protected boolean isJsonRequest(HttpServletRequest request) {
String requestUri = (String) request.getAttribute("javax.servlet.error.requeset_uri");
if(requestUri != null && requestUri.endsWith(".json")) {
return true;
}else {
//也可以通过获取 HTTP 头,根据 Accept 字段是否包含 JSON 来进一步判断
// request.getHeader("Accept").contains("application.json");
return false;
}

}

@Service 和 @Transactional
到目前为止,Spring Boot 的 Controller 介绍完了,在 Spring Boot 中, Controller 调用业务逻辑处理交给了被 @Service 注解的类,这也是一个普通的 JavaBean,Controller 中可以自动注入这种 Bean,并调用其方法完成主要的业务逻辑。正如 Controller 注解经常和 @RequestMapping 搭配使用一样,@Service@Transactional 配合使用。

声明一个 Service 类

publica interface UserService {
public User getUserById(Long id);
public void updateUser(Long id, Integer type);
}

然后实现此业务接口,不要忘记增加@Service 来引起 Spring Boot 的注意,同时搭配上 @Transactional, Spring Boot 会对这样的 Bean 进行事物增强。

@Service
@Transactional // 也可以作用于放上上
public class UserServiceImpl implements UserService {
public User getUserById(Long id){
return user
}
public void updateUser(Long id, Integer type) {

}
}

当 Controller 调用 Service 方法的时候,会开启一个事物上下文,随后的调用都将处于这个事物上下文中。如果调用这个 Service 方法抛出 RuntimeException , 事物则会自动回滚。

posted @ 2018-11-11 16:40  这块显卡有点冷  阅读(301)  评论(0编辑  收藏  举报