顾名思义,@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、一个逻辑视图名等。此时,上传一个超大的文件有错误提示给用户。如下图:
注意可能出现链接重置问题:
Spring文件上传和连接重置问题
https://blog.csdn.net/cyan20115/article/details/106549410/
@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);
}
}
@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可以顺利解决该问题。
@GetMapping("/bookAndAuthor2")
public String bookAndbrother2(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {
return book.toString() + ">>>>>>>>>>>" + author.toString();
}
注意:@InitBinder(“a”) 表示该方法处理@ModelAttribute(“a”)对应的参数的,b同理。
在WebDataBinder对象中,还可以设置允许的字段、禁止的字段,必填字段以及验证器等。
SpringBoot中的全局异常处理。在处理异常时,可以根据实际情况返回不同的页面,但是这种异常处理方式一般用来处理应用级别的异常,有一些容器级别的错误就处理不了,例如Filter中抛出异常,使用@ControllerAdvice定义就无法处理,因此SpringBoot处理异常还有其他方式。
事实上,SpringBoot在返回错误信息时不一定返回HTML页面,而是根据实际情况返回HTML页面或者一段JSON(若开发者发起Ajax请求,则错误信息是一段JSON)。这一段HTML或JSON都能够自由定制。
SpringBoot中错误默认是由BasicErrorController类来处理的,该类中的核心方法主要有两个:
其中,errorHtml方法用来返回错误HTML页面,error用来返回错误JSON,具体返回的是HTML还是JSON,则要看请求头的Accept参数。返回JSON的逻辑很简单,返回HTML的逻辑稍微有些复杂,在errorHtml方法中,通过调用resolveErrorView方法来获取一个错误视图的ModelAndView。而resolveErrorView方法的调用最终会来到DefaultErrorViewResolver类中。
从这一段源码中可以看到,SpringBoot默认是在error目录下查找4xx、5xx的文件作为错误视图,当找不到时会回到errorHtml方法中,然后使用error作为默认的错误页面视图名,如果名为error视图也找不到,用户就会看到默认展示的两个错误页面,整个错误处理流程大致就是这样的。
若用户定义了多个错误页面,则响应码.html页面的优先级高于4xx.html、5xx.html页面的优先级,即若当前是一个404错误,则优先展示404.html而不是4xx.html;动态页面的优先级高于静态页面,即若resources/templates和resources/static下同时定义了4xx.html,则优先展示resources/templates/4xx.html。
@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;
}
}
下面的内容我看其他人都是可以显示内容,但我需要配置上自定义Error信息和自定义的Error视图才显示
如果不显示错误信息,可以添加如下配置:
2. 自定义视图
Error视图时展示给用户的页面,在BasicErrorController的errorHtml方法中调用resolveErrorView方法获取一个 ModelAndView实例。resolveErrorView方法是由ErrorViewResolver提供的,通过ErrorMvcAutoConfiguration类的源码可以看到SpringBoot默认采用的ErrorViewResolver是DefaultErrorViewResolver。
根据上述实现自定义的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;
}
}
从这段源码中可以看到,若开发者没有提供自己的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);
}
}
}
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");//表示支持的域(访问本地的那个域)
}
}
解释:
@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>
问题总结如下: