SpringBoot——整合Web开发(二)

SpringBoot——整合Web开发(二)

      • 一、@ControllerAdvice
      • 二、自定义错误页
      • 三、CORS支持

一、@ControllerAdvice

顾名思义,@ControllerAdvice就是@Controller的增强版。@ControllerAdvice主要用来处理全局数据。一般搭配@Exceptionhadnler、@ModelAttribute以及@InitBinder使用。

1.全局异常处理
@ControllerAdvice最常见的使用场景就是全局异常处理,例如文件上传大小限制的配置,如果用户上传的文件超过了限制大小,就会抛出异常,此时可通过@ControllerAdvice结合@ExceptionHandler定义全局异常捕获机制,代码如下:
当文件超出限制时:
在这里插入图片描述

@ControllerAdvice
public class CustomExceptionHandler {
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public void uploadException(MaxUploadSizeExceededException e, HttpServletResponse response) throws IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter printWriter = response.getWriter();
        printWriter.write("上传文件大小超出限制");
        printWriter.flush();
        printWriter.close();
    }
}

只需在系统中定义CustomExceptionHandler类,然后添加@ControllerAdvice注解即可。当系统启动时,该类就会被扫描到Spring容器中,然后定义uploadException方法,在该方法上添加了@ExceptionHandler注解,其中定义的MaxUploadSizeExceededException.class表名该方法用来处理MaxUploadSizeExceededException 类型的异常。如果想让方法处理所有类型的异常,只需将MaxUploadSizeExceededException 改为Exception即可。方法的参数可以有异常实例、HttpServletResponse以及HttpServletRequest、Model等,返回值可以是一段JSON、一个ModelAndView、一个逻辑视图名等。此时,上传一个超大的文件有错误提示给用户。如下图:
SpringBoot——整合Web开发(二)_第1张图片
注意可能出现链接重置问题:

Spring文件上传和连接重置问题
https://blog.csdn.net/cyan20115/article/details/106549410/

  1. 添加全局数据
    @ControllerAdvice是一个全局数据处理组件,因此也可以在@ControllerAdvice中配置全局数据,使用@ModelAttribute注解进行配置,代码如下:
@ControllerAdvice
public class GlobalConfig {
    @ModelAttribute(value = "info")
    public Map<String,String> userinfo() {
        HashMap<String,String> map = new HashMap<>();
        map.put("username", "罗贯中");
        map.put("gender", "男");
        return map;
    }
}

在全局配置中添加userinfo方法,返回一个map,该方法有一个注解@ModelAttribute,其中的value的属性表示这条返回数据的key,而方法的返回值是返回数据的value。
此时在任意请求的Controller中,通过方法参数中的Model可以获取info的数据。

    @GetMapping("/info")
    public void getInfo(Model model) {
        Map<String, Object> mapObj = model.asMap();
        Iterator iterator = mapObj.keySet().iterator();
        while(iterator.hasNext()) {
            Object obj = iterator.next();
            Object value = mapObj.get(obj);
            System.out.println("value = " + value);
        }
    }
  1. 请求参数预处理
    @ControllerAdvice结合@InitBinder还能实现请求参数预处理,即将表单中的数据绑定到实体类上时进行一些额外处理。

SpringBoot——整合Web开发(二)_第2张图片

    @GetMapping("/bookAndAuthor")
    public String bookAndbrother(Book book, Author author) {
        return book.toString() + ">>>>>>>>>>>" + author.toString();
//        Book{name='sss', author='null', price=null, publicationData=null}
//        >>>>>>>>>>>Author{name='sss', age=0}
    }

此时参数传递时,两个实体类中的name属性会混淆,@ControllerAdvice结合@InitBinder可以顺利解决该问题。

SpringBoot——整合Web开发(二)_第3张图片

    @GetMapping("/bookAndAuthor2")
    public String bookAndbrother2(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {
        return book.toString() + ">>>>>>>>>>>" + author.toString();
    }

SpringBoot——整合Web开发(二)_第4张图片
注意:@InitBinder(“a”) 表示该方法处理@ModelAttribute(“a”)对应的参数的,b同理。
在WebDataBinder对象中,还可以设置允许的字段、禁止的字段,必填字段以及验证器等。

二、自定义错误页

SpringBoot中的全局异常处理。在处理异常时,可以根据实际情况返回不同的页面,但是这种异常处理方式一般用来处理应用级别的异常,有一些容器级别的错误就处理不了,例如Filter中抛出异常,使用@ControllerAdvice定义就无法处理,因此SpringBoot处理异常还有其他方式。
事实上,SpringBoot在返回错误信息时不一定返回HTML页面,而是根据实际情况返回HTML页面或者一段JSON(若开发者发起Ajax请求,则错误信息是一段JSON)。这一段HTML或JSON都能够自由定制。
SpringBoot中错误默认是由BasicErrorController类来处理的,该类中的核心方法主要有两个:
SpringBoot——整合Web开发(二)_第5张图片
其中,errorHtml方法用来返回错误HTML页面,error用来返回错误JSON,具体返回的是HTML还是JSON,则要看请求头的Accept参数。返回JSON的逻辑很简单,返回HTML的逻辑稍微有些复杂,在errorHtml方法中,通过调用resolveErrorView方法来获取一个错误视图的ModelAndView。而resolveErrorView方法的调用最终会来到DefaultErrorViewResolver类中。
SpringBoot——整合Web开发(二)_第6张图片SpringBoot——整合Web开发(二)_第7张图片
SpringBoot——整合Web开发(二)_第8张图片
从这一段源码中可以看到,SpringBoot默认是在error目录下查找4xx、5xx的文件作为错误视图,当找不到时会回到errorHtml方法中,然后使用error作为默认的错误页面视图名,如果名为error视图也找不到,用户就会看到默认展示的两个错误页面,整个错误处理流程大致就是这样的。

  • 简单配置
    要自定义错误页面其实很简单,提供4xx和5xx页面即可。不需要向用户展示详细的错误信息,那么可以把错误信息定义成静态页面,直接在resources/static目录下创建error目录,然后在error目录中创建错误展示页面。错误展示页面的命令规则有两种:一种是4xx.html、5xx.html;另一种直接使用响应码命名文件,例如404.html、500.html。第二种命令方式划分得更细,当出错时,不同的错误会展示不同的错误页面。 SpringBoot——整合Web开发(二)_第9张图片
    SpringBoot——整合Web开发(二)_第10张图片

若用户定义了多个错误页面,则响应码.html页面的优先级高于4xx.html、5xx.html页面的优先级,即若当前是一个404错误,则优先展示404.html而不是4xx.html;动态页面的优先级高于静态页面,即若resources/templates和resources/static下同时定义了4xx.html,则优先展示resources/templates/4xx.html。

  • 复杂配置
    上面这种配置还是不够灵活,只能定义HTML页面,无法处理JSON的定制。Spring Boot中支持Error信息的深度定制,接下来将从三个方面介绍深度定制:自定义Error数据、自定义Error视图以及完全自定义。
  1. 自定义Error数据
    自定义数据Error数据就是对返回的数据进行自定义。SpringBoot返回的Error信息分别是timestamp、error、message以及path。在BasicErrorController的errorHtml方法和error方法中,都是通过getErrorAttributes方法获取Error信息的。该方法最终会调用到DefaultErrorAttributes类的getErrorAttributes方法,而DefaultErrorAttributes类是在ErrorMvcAutoConfiguration中默认提供的。ErrorMvcAutoConfiguration类的errorAttributes方法源码如下:
    SpringBoot——整合Web开发(二)_第11张图片
    然后找到其实现方法:
    SpringBoot——整合Web开发(二)_第12张图片SpringBoot——整合Web开发(二)_第13张图片
    SpringBoot——整合Web开发(二)_第14张图片
    从这段源码中可以看出,当系统中没有提供ErrorAttributes时才会采用DefaultErrorAttributes。因此自定义错误提示时,只需要自己提供一个ErrorAttributes即可,而DefaultErrorAttributes是ErrorAttributes的子类,因此只需要继承DefaultErrorAttributes即可。SpringBoot——整合Web开发(二)_第15张图片
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
        errorAttributes.remove("error");
        errorAttributes.put("errorMsg", "对不起,您访问的页面不存在!!!");
        return errorAttributes;
    }
}

SpringBoot——整合Web开发(二)_第16张图片

下面的内容我看其他人都是可以显示内容,但我需要配置上自定义Error信息和自定义的Error视图才显示
SpringBoot——整合Web开发(二)_第17张图片
如果不显示错误信息,可以添加如下配置:
SpringBoot——整合Web开发(二)_第18张图片
2. 自定义视图
Error视图时展示给用户的页面,在BasicErrorController的errorHtml方法中调用resolveErrorView方法获取一个 ModelAndView实例。resolveErrorView方法是由ErrorViewResolver提供的,通过ErrorMvcAutoConfiguration类的源码可以看到SpringBoot默认采用的ErrorViewResolver是DefaultErrorViewResolver。SpringBoot——整合Web开发(二)_第19张图片SpringBoot——整合Web开发(二)_第20张图片
SpringBoot——整合Web开发(二)_第21张图片
根据上述实现自定义的Error视图:

@Component
public class ErrorView implements ErrorViewResolver {
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView mnv = new ModelAndView("errorpage1");
        mnv.addAllObjects(model);
        return mnv;
    }
}

SpringBoot——整合Web开发(二)_第22张图片

SpringBoot——整合Web开发(二)_第23张图片

  1. 完全自定义
    前面提供的两种自定义方式都是对BasicErrorControlle类中的某个环节进行修补。查看Error自动化配置类ErrorMvcAutoConfiguration,可以发现BasicErrorController本身只是一个默认的配置,相关源码如下:

SpringBoot——整合Web开发(二)_第24张图片
从这段源码中可以看到,若开发者没有提供自己的ErrorController,则SpringBoot提供BasicErrorController作为默认的ErrorController。因此,如果开发者需要更加灵活地对Error视图和数据进行处理,那么只需要提供自己的ErrorContrller即可。提供自己的ErrorController有两种方法:一种是实现只提供一个待实现的方法,而BasicErrorController已经实现了很多功能,因此这里选择第二种方式,即通过继承BasicErrorController来实现自己的ErrorController。具体实现如下:


package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@Component
public class myErrorController extends BasicErrorController {


/*
  public myErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        super(errorAttributes, errorProperties);
    }
*/



/*
 public myErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List errorViewResolvers) {
        super(errorAttributes, errorProperties, errorViewResolvers);
    }*/

@Autowired
    public myErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, serverProperties.getError(), errorViewResolvers);
    }


public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        // 文件上传异常 java.lang.UnsupportedOperationException
      //  Map model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        Map<String, Object> model = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        model.put("haha", "哈哈哈 你找不到吧!!!!");
      //  return modelAndView != null ? modelAndView : new ModelAndView("error", model);
        modelAndView.setViewName("error");
        modelAndView.addAllObjects(model);
        if (modelAndView != null) {
            return modelAndView ;
        } else {
            return new ModelAndView("error", model);
        }
    }



    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            body.put("haha1", "哈哈哈 你找不到吧!!!!");
            request.setAttribute("haha1", "哈哈哈,你找不到吧!!!");
            return new ResponseEntity(body, status);
        }
    }
}

SpringBoot——整合Web开发(二)_第25张图片

三、CORS支持

CORS(Cross-Origin Resource Sharing)是由W3C制定的一种跨域资源共享技术标准,其目的就是为了解决前端的跨域请求。在JAVAEE开发中,最常见的前端跨域请求解决方案是JSONP,但是JSONP支持GET请求,这是一个很大的缺陷,而CORS则支持多种HTTP请求方法。
JSONP教程
Jsonp(JSON with Padding)是json的一种“使用模式”,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。为什么我们从不同的域(网站)访问数据需要一个特殊的技术(JSONP)呢?这是因为同源策略。同源策略,它是由Netscape提出的一个著名的安全策略,现在所有支持JavaScript的浏览器都会使用这个策略。

  • 全局配置
@Configuration
public class crossOrginConfig implements WebMvcConfigurer {
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/book1/**")//允许请求路径(本地)
                .allowedHeaders("*")
                .allowedMethods("*")
                .maxAge(1800)
                .allowedOrigins("http://localhost:8082");//表示支持的域(访问本地的那个域)
    }
}

解释:

  • 全局配置需要自定义实现WebMvcConfigurer接口,然后实现接口中的addCorsMappings方法。
  • 在addCorsMappings方法中,addMapping表示对哪种格式的请求路径进行跨域处理,allowedHeaders表示允许的请求头,默认允许所有的请求头信息;allowedMethods表示允许的请求方法,默认是GET、POST和HEAD;*表示支持所有的请求方法;maxAge表示探测请求的有效期;allowedOrigins表示支持的域。
  • 局部配置
    8080:
    @RequestMapping("/book2")
    @ResponseBody
    @CrossOrigin(value = "http://localhost:8082",maxAge = 1800, allowedHeaders = "*")
    public String getBookInfo2(String name) {
        return "receview:" + name;
    }

页面统一:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
    <script src="https://cdn.staticfile.org/jquery/2.0.0/jquery.min.js">script>
head>

<body>
<div id="contentDiv">div>
<input type="button" value="提交数据" onclick="getData()"/>
body>
<script>
    function getData() {
        alert("getData");
        $.ajax({
            url:'http://localhost:8080/book2'
            ,data: {name:'三国演义'}
            ,contentType: 'application/json;charset=utf-8'
            ,dataType:'json'
            ,success: function(msg) {
                console.log("msg==================" + msg);
                $("#contentDiv").html(msg);
            },error: function (msg) {
                console.log(msg);
                console.log("errro==================" + msg);
            }
        });
    }
script>
html>

问题总结如下:

  1. 如果返回json,需要配置json转换器
  2. java.lang.IllegalArgumentException: Content-Type cannot contain wildcard type ‘*’
    参考链接:https://blog.csdn.net/mjlfto/article/details/103888497
  3. 关于请求出现 Status Code: 406 Not Acceptable
    状态码406:HTTP协议状态码的一种,表示无法使用请求的内容特性来响应请求的网页。说白了就是后台的返回结果前台无法解析就报406错误。

你可能感兴趣的:(Spring,spring,boot,前端,java)