在 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 提供了一下方法来获取上传文件的信息。
- getOriginalFilename 获取上传文件的名字。
- getBytes 获取上传文件内容,转为字节数组。
- getInputStream 获取一个 InputStream
- isEmpty 上传文件内容为空,或者就没有文件上传
- getSize 文件上传的大小
- 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
, 事物则会自动回滚。