异常处理:
默认的异常处理机制:
默认规则:默认请情况下,springboot提供/error处理所有错误的映射。
对于机器客户端而言,它将生成json响应的数据,包含错误,http状态和异常消息的详细信息。对于浏览器客户端,响应一个"whilelabel"(白页)的错误视图,以html格式呈现相同的数据。有错误的状态码(status),错误的消息(message)等。
默认状态下,当发生了错误的时候,BasicErrorController会进行异常请求处理,不同的客户端以不同的方式显示错误消息:json或者html形式,但是都有相同的错误消息key-value信息。如下2张图所示:
自定义错误页面可以存放的位置:
存放位置一:资源文件resources下的public文件夹下的error文件夹里
存放位置二:资源文件resources下的templates文件夹下的error文件夹里
注意:当把自定义错误页面放到templates/error文件夹下,并命名为4xx.html或者5xx.html的时候,springboot会自动帮我们自动解析4xx/5xx页面,并遇到错误自动进行页面跳转:
修改5xx页面,让其打印错误的堆栈信息等:
代码演示:
5xx页面的部分代码:
<section>
<div class="container ">
<section class="error-wrapper text-center">
<h1><img alt="" src="images/500-error.png"></h1>
<h2>OOOPS!!!</h2>
<h3 th:text="${message}">Something went wrong.</h3>
<!--/*@thymesVar id="trace" type="java"*/-->
<p class="nrml-txt" th:text="${trace}">Why not try refreshing you page? Or you can <a href="#">contact our support</a> if the problem persists.</p>
<a class="back-btn" th:href="@{/main.html}"> 返回首页</a>
</section>
</div>
</section>
可以进行页面测试,发现Controller遇到错误,可以自动跳转到我们的4xx/5xx.html页面;
异常处理页面的源码探究:
默认的异常处理机制源码:
springboot为何默认的能自动解析error/4xx.html等页面,我们去底层看一下源代码(本质是BasicErrorController为我们设置了默认的错误页面的路径/error):
默认状态下,当发生了错误的时候,BasicErrorController根据不同的客户端以不同的方式显示错误消息:json或者html形式(json+白页进行适配响应),但是都有相同的错误消息key-value信息:
当然,我们可以在配置文件通过配置来修改错误页面的默认路径(server.servlet.path=路径值):
异常处理的自动配置原理:
以下是容器中的异常处理的几个组件:
默认的异常视图解析器(DefaultErrorViewResolver):
默认的异常视图解析器(DefaultErrorViewResolver),如何将http的错误状态码解析成我们自定义的视图名4xx,5xx:
默认的异常的属性DefaultErrorAttributes(定义错误页面中可以包含哪些属性):
我们可以在DefaultErrorAttributes类中可以找到有哪些异常的属性可以让我们在页面取出,如下图所示的一些属性。
总结各组件的作用:
异常处理步骤流程:
默认的异常处理机制生效的时候,上面的四个异常解析器都不生效。
1+3个异常处理器:其中上面的异常处理器Exception开头的是处理@ExceptionHandler注解的异常的。
Response开头的处理器是处理@ResponseStatus注解的。
异常处理的方式:
第一种,自定义错误页(将精确匹配变成4xx.html,5xx.html匹配):
然后模拟一个异常,比如能发生400的错误请求。
4xx.html部分页面代码:
<section class="error-wrapper text-center">
<h1><img alt="" src="images/404-error.png"></h1>
<!--/*@thymesVar id="status" type="java"*/-->
<h2 style="color: red" th:text="${status}">page not found</h2><!--主类我们取出错误消息-->
<!--/*@thymesVar id="message" type="java"*/-->
<h3 style="color:#2a6496 " th:text="${message}">We Couldn’t Find This Page</h3>
<a class="back-btn" th:href="@{/main.html}"> 返回首页</a>
</section>
模拟发生400错误的请求:
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Slf4j
@Controller
public class FormUpController {
@GetMapping("/dropzone")
public String todropzone(@RequestParam("a") Integer a){//前端其实没有发送此请求,模拟400错误
//int x= 10/0;测试500错误
return "form/dropzone";//转发到文件上传页面
}
}
最终效果:
第二种,使用全局异常捕捉处理(@ControllerAdvice+@ExceptionHandler):
@ControllerAdvice,是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理。最常用的就是异常处理
在Spring里,我们可以使用@ControllerAdvice来声明一些全局性的东西,最常见的是结合@ExceptionHandler注解用于全局异常的处理。
@ExceptionHandler注解标注的方法:用于捕获Controller中抛出的不同类型的异常,从而达到异常全局处理的目的;
编写一个自定义的全局异常处理类GlobalExceptionHandler,在此类上使用@ControllerAdvice+@ExceptionHandler注解:
代码演示全军异常的捕获处理:
GlobalExceptionHandler类 :
package com.fan.admin.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({ArithmeticException.class,
NullPointerException.class})
public String handleArithException(Exception e){
log.info("设置全局的异常处理机制,异常是:{}",e);
return "login";//视图地址
}
}
Controller测试:
package com.fan.admin.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Slf4j
@Controller
public class FormUpController {
//专门用来测试异常的方法:
@GetMapping("/dropzone")
public String todropzone(/*@RequestParam("a") Integer a*/){//前端其实没有发送此请求,模拟400错误
int x= 10/0;//测试500错误
return "form/dropzone";//转发到文件上传页面
}
//myFileUp
@PostMapping("/myFileUp")
public String myFileUp(
@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headImg") MultipartFile headImg,
@RequestPart("photos") MultipartFile[] photos
) throws IOException {
log.info("测试文件上传===");
//单文件上传
if(!headImg.isEmpty()){//文件不为空
String originalFilename = headImg.getOriginalFilename();
headImg.transferTo(new File("G:\\" + originalFilename) );
}
//多文件上传
if(photos.length > 0){//文件数量不为0
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("g:\\"+originalFilename));
}
}
}
//文件,
return "main";
}
}
第三种,@ResponseStatus+自定义的异常:
可以通过@ResponseStatus注解和自定义异常来配合使用返回自定义响应状态码和自定义错误信息给客户端。
自定义异常的底层原理:
然后转给BasicErrorController来处理:如下
自定义异常的用法:
自定义一个异常处理类继承RuntimeException:
Controller模拟一个异常进行测试:
代码演示UserTooManyException 自定义异常类:
package com.fan.admin.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
//使用注解设置状态码
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException {
public UserTooManyException() {
}
public UserTooManyException(String message) {
super(message);
}
}
测试:
@ResponseStatus的使用可以参考这个:https://www.cnblogs.com/lvbinbin2yujie/p/10575101.html
第四种,Spring底层的异常
第五中,自定义异常解析器处理异常:
系统为我们提供了几个异常解析器,但是如果不能满足我们,我们可以自定义异常解析器来处理我们的异常。
这种方式可以作为默认的全局异常处理规则:
添加优先级:
我们自定义的异常解析器就排到前面了:如下
测试:
异常处理扩充:
Web原生组件的注入:
Servlet组件(使用Servlet API方式):
编写一个自定义的Servlet类,并把其放到容器中,使用注解@WebServlet标记此自定义类:
代码演示:
package com.fan.admin.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(urlPatterns = "/my")//拦截的请求
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("666");
}
}
启动服务,页面输入地址测试:–>成功注入了Servlet
启动类上使用注解@ServletComponentScan扫描自定义的servlet等三大组件:
Filter组件(使用Servlet API方式):
编写一个自定义的Filter类,并把其放到容器中,使用注解@WebFilter标记此自定义类:
这里我们模拟访问一个css静态文件:
package com.fan.admin.servlet;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.WebProperties;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@Slf4j
@WebFilter(urlPatterns = {"/css/*","/images/*"})//
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter初始化完成");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("MyFilter工作");
//放行执行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
log.info("MyFilter销毁");
}
}
测试filter:
监听器组件(使用Servlet API方式):
编写一个自定义的Listener类,并把其放到容器中,使用注解@WebListener标记此自定义类:
代码演示:
package com.fan.admin.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@Slf4j//lombok的日志打印
@WebListener
public class MyServletContextListener implements ServletContextListener {
//重写初始化方法和销毁方法:
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("MyServletContextListener监听到项目初始化完成");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("MyServletContextListener监听到项目销毁");
}
}
项目启动测试:
总结使用Servlet API方式注入原生的三大组件:
使用RegistrationBean来注入web三大组件:
复用以上三个类(MyFilter,MyServlet,MyServletContextListener),将注解去掉
都需要@Bean注解
代码演示:
package com.fan.admin.servlet;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.Arrays;
@Configuration(proxyBeanMethods = true)//声明这是一个配置类
public class MyRegistConfig {
@Bean
public ServletRegistrationBean myServlet(){
return new ServletRegistrationBean(new MyServlet(),"/my","/my02");
}
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean<Filter> filterBean = new FilterRegistrationBean(new MyFilter());
filterBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
return filterBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
MyServletContextListener listener = new MyServletContextListener();
return new ServletListenerRegistrationBean(listener);
}
}
扩展:
嵌入式Servlet容器:
1.切换嵌入式的Servlet容器:
切换服务器:
测试:
2.定制Servlet容器:
定制化的常见方式:
定制化的常见方式有以下几种:
1.修改springboot的配置文件。
2.xxxCustomizer定义的。
3.编写自定义的配置类xxxConfiguration + @Bean替换,增加容器中默认组件;(比如我们自定义的视图解析器)
4.web应用实现WebMvcConfigurer,来实现定制化的web功能(重点掌握)。