SpringBoot 框架学习笔记(八):异常处理 和 Servlet、Filter、Listener三大组件注入spring

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);
    }
}
  • 11
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值