1 异常处理
1.1 基本介绍
- 默认情况下,Spring Boot提供 /error 处理所有错误的映射,也就是说当出现错误时,SpringBoot 底层会请求转发到 /error 这个映射
- 比如使用浏览器访问不存在的接口(路径映射),响应一个"whitelabel"错误视图,以HTML格式呈现给用户
- SpringBoot底层默认由 DefaultErrorViewResolver 处理错误。底层会先在指定的目录找相应状态码的错误页面,如果找不到,4开头的状态码就会找4xx.html,5开头的状态码就会找5xx.html
1.2 自定义异常页面
1.2.1 说明
(1)如果想要映射一个404的错误页面,需要放在目录 resources/public/error/404.html
(2)如果使用的是模板引擎,比如Thymeleaf,就放在 resources/templates/error/404.html
(3)返回给错误页面的数据格式如下,可通过相应的取值表达式取出
1.2.2 应用实例
(1)我使用的是 Thymeleaf 模板引擎,所以将页面放在resources/templates/error目录下
404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<h1>404 Not Fount</h1>
<a href="#" th:href="@{/}">返回主页面</a><br/>
状态码<p th:text="${status}"></p><br/>
错误信息<p th:text="${error}"></p>
</div>
</body>
</html>
(2)启动项目,通过浏览器访问一个不存在的页面进行测试,效果如下
1.3 全局异常
1.3.1 说明
(1)@ControllerAdvice + @EcxceptionHandler 处理全局异常
(2)底层是 ExceptionHandlerExceptionResolver 支持的
(3)全局异常优先级高于默认异常
1.3.2 应用实例
需求说明:演示全局异常使用,当发生算术异常ArithmeticException时,不使用默认异常机制匹配的xxx.html,而是显示全局异常机制指定的错误页面
(1)新建软件包 exception,在该软件包下创建 GlobalExceptionHandler.java 作为全局异常处理器
package org.wwj.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.HandlerMethod;
import java.util.logging.Handler;
/**
* @ControllerAdvice 标识一个全局异常处理器,并注入到spring容器中
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
// 这里全局处理算术异常
// 1. Exception e: 表示异常发生后,传递的异常对象
// 2. Model model: 可以将异常信息,放入model,传递给显示页面
// 3. HandlerMethod method:表示异常发生的方法
@ExceptionHandler(ArithmeticException.class)
public String handleAritException(Exception e, Model model, HandlerMethod method){
log.info("异常信息={}", e.getMessage());
log.info("异常发生的方法={}", method);
// 将异常信息放入到model中,会被自动放入到request域中
model.addAttribute("msg", e.getMessage());
return "/error/global";
}
}
对应 resources/templates/error/global.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>全局异常</title>
</head>
<body bgcolor="#CED3FE">
<div style="text-align: center">
<h1>全局异常/错误 发生了</h1><br/>
异常、错误信息:<h1 th:text="${msg}"></h1><br/>
<a href="#" th:href="@{/}">返回主页面</a>
</div>
</body>
</html>
(2)在 controller 类中增加方法模拟算术异常
package org.wwj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyErrorController {
// 模拟服务器内部错误
@GetMapping("/err")
public String err() {
int i = 10 / 0; // 算术异常
return "manage";
}
}
(3)启动项目,浏览器输入 localhost://8080/error 进行测试,效果如下
1.4 自定义异常
1.4.1 说明
(1)如果Spring Boot提供的异常不能满足开发需求,就可以自定义异常
(2)@ResponseStatus + 自定义异常
(3)底层是 ResponseStatusExceptionResolver,底层调用response.sendError(statusCode,resolvedReason);
(4)当抛出自定义异常后,就会按照默认异常处理机制,根据状态码,去匹配使用 xxx.html 显示
1.4.2 应用实例
需求说明:自定义一个异常 AccessException,当用户访问某个无权访问的路径时,抛出该异常,并显示自定义异常状态码
(1)在 exception 包下创建 AccessException.java 作为自定义异常
package org.wwj.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
// value = HttpStatus.FORBIDDEN: 表示发生 AccessException 异常时,通过http协议返回的状态码是403
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public class AccessException extends RuntimeException{
// 构造器
public AccessException(String message) {
super(message);
}
// 显示声明无参构造器
public AccessException() {
}
}
(2)在controller类中增加发生处理请求,并抛出异常
// 编写方法,模拟AccessException异常
@GetMapping("err2")
public String err2(String name){
// 如果用户不是tom,就认为无权访问
if (! "tom".equals(name)){
throw new AccessException();
}
return "manage";
}
(3)启动项目,浏览器输入 localhost:8080/err2?name=wwj 进行测试,效果如下
2 注入 Servlet、Filter、Listener
2.1 基本介绍
(1)考虑到实际开发业务非常复杂和兼容,Springboot支持将 Servlet、Filter、Listener 注入Spring容器,成为spring bean
(2)也就是说Spring Boot 开放了和原生WEB组件(Servlet、Filter、Listener )的兼容
2.2 应用实例1-使用注解方式注入
2.2.1 @WebServlet 注入Servlet
(1)创建软件包servlet,在该包下创建Servlet_
package org.wwj.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;
/**
* 1. 通过继承 HttpServlet 来开发原生的Servlet
* 2. @WebServlet 标识将 Servlet_ 对象/bean注入到容器
* 3. urlPatterns = {"/servlet01","/servlet02"},对servlet配置了url-pattern
* 4. 注入的原生的Servlet_,不会被spring-boot的拦截器拦截。
* 因为如果请求由原生的Servlet_处理就不会经过spring boot中的servlet
* 5. 对于开发的原生的Servlet,需要在主程序上使用 @ServletComponentScan 指定要扫描的原生Servlet,
* 才会注入到spring容器
*/
@WebServlet(urlPatterns = {"/servlet01","/servlet02"})
public class Servlet_ extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello,Servlet_!");
}
}
(2)在主程序上添加 @ServletComponentScan
package org.wwj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.ApplicationContext;
// 扫描
@ServletComponentScan(basePackages = "org.wwj.servlet")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext ioc = SpringApplication.run(Application.class, args);
System.out.println("hello");
}
}
(3)启动程序,浏览器输入 localhost:8080/servlet01 进行测试,效果如下
2.2.2 @WebFilter 注入Filter
package org.wwj.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 开发Filter并注入
* 1. @WebFilter 表示Filter_是一个过滤器,并注入容器
* 2. urlPatterns = {"/css/*"} 表示当请求 /css/目录资源的时候,会经过该过滤器
*/
@Slf4j
@WebFilter(urlPatterns = {"/css/*"})
public class Filter_ implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("--Filter_init--");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("--Filter_doFilter--");
// 为了方便观察过滤器处理的资源,输出一个uri
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
log.info("过滤器处理的uri={}", httpServletRequest.getRequestURI());
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
log.info("--Filter_destroy--");
}
}
注意:
- 过滤器配置的urlPatterns 也会经过Spring-Boot拦截器
- 在servlet匹配全部是 /*,在Spring-Boot是/**
2.2.3 @WebListener 注入Listener
package org.wwj.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@Slf4j
@WebListener
public class Listener_ implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("Listener_ contextInitialized 项目初始化ok~");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("Listener_ contextDestroyed 项目销毁ok~");
}
}
2.3 应用实例2-使用RegistrationBean方式注入
将注入组件的注解全部注销,接下来使用RegistrationBean方式注入
创建 RegisterConfig_,java 作为配置类
package org.wwj.config;
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 org.wwj.servlet.Filter_;
import org.wwj.servlet.Listener_;
import org.wwj.servlet.Servlet_;
import java.util.Arrays;
/**
* @Configuration 标识一个配置类
*/
@SuppressWarnings("all")
@Configuration
public class RegisterConfig_ {
// 注入Servlet
@Bean
public ServletRegistrationBean servlet_(){
// 创建原生Servlet对象
Servlet_ servlet = new Servlet_();
// "/servlet01", "/servlet02" 就是注入Servlet的url-pattern
return new ServletRegistrationBean<>(servlet, "/servlet01", "/servlet02");
}
// 注入Filter
@Bean
public FilterRegistrationBean filter_(){
// 创建原生Filter对象
Filter_ filter = new Filter_();
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(filter);
// 设置filter的url-pattern
filterRegistrationBean.setUrlPatterns(Arrays.asList("/css/*"));
return filterRegistrationBean;
}
// 注入Listener
@Bean
public ServletListenerRegistrationBean listener_(){
// 创建原生Listener对象
Listener_ listener = new Listener_();
return new ServletListenerRegistrationBean(listener);
}
}