zoukankan      html  css  js  c++  java
  • 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 , 事物则会自动回滚。

  • 相关阅读:
    安全编码1
    VPP tips
    VPP概述汇总
    C语言安全编码摘录
    TCP-proxy
    Scipy Lecture Notes学习笔记(一)Getting started with Python for science 1.4. Matplotlib: plotting
    Scipy Lecture Notes学习笔记(一)Getting started with Python for science 1.3. NumPy: creating and manipulating numerical data
    Scipy Lecture Notes学习笔记(一)Getting started with Python for science 1.2. The Python language
    Scipy Lecture Notes学习笔记(一)Getting started with Python for science 1.1. Python scientific computing ecosystem
    25马5跑道,求最快的五匹马的需要比赛的次数
  • 原文地址:https://www.cnblogs.com/dowhile/p/MVC-kuang-jia.html
Copyright © 2011-2022 走看看