8.统一异常处理 + 统一记录日志

目录

1.统一异常处理

2.统一记录日志


1.统一异常处理


在 HomeController 类中添加请求方法(服务器发生异常之后需要统一处理异常,记录日志,然后转到 500 页面,需要人工处理重定向到 500 页面,提前把 500 页面请求访问配置)

java 复制代码
    @RequestMapping(path = "/error", method = RequestMethod.GET)
    public String getErrorPage() {
        return "/error/500";
    }

在 controller 类下新建 advice 包,创建 ExceptionAdvice 类:

  • 添加注解 @ControllerAdvice 统一处理异常:此时这个组件会扫面所有的 Bean,做一个限制 @ControllerAdvice(annotations = Controller.class):这个组件只去扫面带有 Controller 注解的 Bean
  • 添加所有方法处理所有错误情况:添加注解 @ExceptionHandler 表示这个方法处理所有异常的方法------ @ExceptionHandler({Exception.class})
  • 通常参数为 Exception e, HttpServletRequest request, HttpServletResponse response
  • 注入日志,并且把异常记录日志,想要把异常非常详细的栈的信息记录:遍历栈的信息得到的是数组(每次遍历得到一条异常信息,打印日志)
  • 然后给浏览器响应(重定向到错误页面)
  • 这时候还需要判断这个请求是普通请求还是异步请求:浏览器访问服务器可能是普通请求(希望返回网页,然后重定向到 500)也可能是异步请求(希望返回 JSON,不可以返回到页面 HTML)
  • **通过 request.getHeader("x-requested-with") 获取请求返回 String:如果返回值等于 "XMLHttpRequest"则表示为异步请求,这个请求是以 XML 的形式访问,希望返回 XML,只有异步请求才希望返回 XML(普通请求返回 HTML)**然后响应字符串("application/plain;charset=utf-8":向浏览器返回普通字符串,可以是 JSON 格式,需要人为的将字符串转化为 JS 对象),获取输出流输出JSON字符串;如果是普通请求,重定向到错误页面(获取项目访问路径 + "/error" 路径)
java 复制代码
package com.example.demo.controller.advice;

import com.example.demo.util.CommunityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

//添加注解 @ControllerAdvice 统一处理异常:此时这个组件会扫面所有的 Bean,
//做一个限制 @ControllerAdvice(annotations = Controller.class):这个组件只去扫面带有 Controller 注解的 Bean
@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);

    //添加所有方法处理所有错误情况:添加注解 @ExceptionHandler 表示这个方法处理所有异常的方法
    @ExceptionHandler({Exception.class})
    public void handleException(Exception e, HttpServletRequest request,
                                HttpServletResponse response) throws IOException {
        //注入日志,并且把异常记录日志,想要把异常非常详细的栈的信息记录
        // 遍历栈的信息得到的是数组(每次遍历得到一条异常信息,打印日志)
        logger.error("服务器发生异常: " + e.getMessage());
        for (StackTraceElement element : e.getStackTrace()) {
            logger.error(element.toString());
        }

        //然后给浏览器响应(重定向到错误页面)
        //这时候还需要判断这个请求是普通请求还是异步请求:浏览器访问服务器可能是普通请求(希望返回网页,然后重定向到 500)
        // 也可能是异步请求(希望返回 JSON,不可以返回到页面 HTML)
        //通过 request.getHeader("x-requested-with") 获取请求返回 String:如果返回值等于 "XMLHttpRequest"则表示为异步请求,
        // 这个请求是以 XML 的形式访问,希望返回 XML,只有异步请求才希望返回 XML(普通请求返回 HTML)
        String xRequestedWith = request.getHeader("x-requested-with");
        if ("XMLHttpRequest".equals(xRequestedWith)) {
            // 然后响应字符串("application/plain;charset=utf-8":向浏览器返回普通字符串
            //可以是 JSON 格式,需要人为的将字符串转化为 JS 对象)
            response.setContentType("application/plain;charset=utf-8");
            //获取输出流输出JSON字符串;如果是普通请求,重定向到错误页面(获取项目访问路径 + "/error" 路径)
            PrintWriter writer = response.getWriter();
            writer.write(CommunityUtil.getJSONString(1, "服务器异常!"));
        } else {
            response.sendRedirect(request.getContextPath() + "/error");
        }
    }
}

2.统一记录日志

  • 可不可以利用控制器通知统一处理?控制器通知在发生异常时可以统一处理,而统一记录日志并不是发生异常才记录
  • 可不可以使用拦截器?拦截器也是针对控制器做处理,而记录日志并不是只有在控制器中记录(目前我们对业务组件做日志记录)
  • 使用传统方法:把记录日志的内容封装到组件中,在不同的业务组件(service)方法中调用(比如在一开始记录日志,在写也业务方法之前记录日志)。这种方法也有弊端:业务组件方法是处理业务,但是在之前添加日志需求(不是业务需求,是系统需求),所以在业务方法中耦合了系统需求是有弊端的,例如现在系统需求发生变化,不想在业务之前记录日志,而是在之后记录日志,改动比较大,很麻烦
  • 解决问题的最好方法:将记录日志(系统需求)单独实现------使用 AOP,单独定义一个组件,不和业务组件发生直接关系,将业务组件通用逻辑封装在这个组件中

对于 AOP 的知识可以阅读之前学习的 AOP 博客:Spring AOP-CSDN博客

在 pom.xml 添加依赖:

XML 复制代码
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

对所有业务组件记录日志(在业务组件一开始记录日志),新建 aspect 包创建 ServiceLogAspect 类(存放切面主键):

  • 添加注解 @Component、@Aspect
  • 实例化 Logger
  • 声明切点:所有业务组件都去处理
  • 使用前置通知在业务组件一开始记录日志
  • 记录格式:用户[1.2.3.4],在[xxx],访问了[com.example.demo.service.xxx()].
  • 用户 ip 通过 request 获取,获取 request:RequestContextHolder.getRequestAttributes();
  • 拼接时间:new Date,然后实例化
  • 访问某个类某个方法(类名 + 方法名):给方法添加 JoinPoint 连接点参数,连接点指代程序植入的目标方法
  • 最后再进行全部拼接
java 复制代码
package com.example.demo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;

@Component
@Aspect
public class ServiceLogAspect {
    //实例化 Logger
    private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);

    //声明切点:所有业务组件都去处理
    @Pointcut("execution(* com.example.demo.service.*.*(..))")
    public void pointcut() {

    }
    //使用前置通知在业务组件一开始记录日志
    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        //记录格式:用户[1.2.3.4],在[xxx],访问了[com.example.demo.service.xxx()].

        //用户 ip 通过 request 获取,获取 request:RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes attributes =(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String ip = request.getRemoteHost();
        //拼接时间:new Date,然后实例化
        String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        //访问某个类某个方法(类名 + 方法名):给方法添加 JoinPoint 连接点参数,连接点指代程序植入的目标方法
        String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        //全部拼接
        logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));
    }
}
相关推荐
练习时长一年1 小时前
AopAutoConfiguration源码阅读
java·spring boot·intellij-idea
百思可瑞教育3 小时前
Git 对象存储:理解底层原理,实现高效排错与存储优化
大数据·git·elasticsearch·搜索引擎
HeyZoeHey4 小时前
Mybatis执行sql流程(一)
java·sql·mybatis
Q_Q19632884757 小时前
python的电影院座位管理可视化数据分析系统
开发语言·spring boot·python·django·flask·node.js·php
TT哇7 小时前
@[TOC](计算机是如何⼯作的) JavaEE==网站开发
java·redis·java-ee
青川入梦7 小时前
MyBatis极速通关上篇:Spring Boot环境搭建+用户管理实战
java·开发语言·mybatis
33255_40857_280598 小时前
掌握分页艺术:MyBatis与MyBatis-Plus实战指南(10年Java亲授)
java·mybatis
蚰蜒螟9 小时前
Spring 和 Lettuce 源码分析 Redis 节点状态检查与失败重连的工作原理
java·redis·spring
Runing_WoNiu9 小时前
Redis主从架构、哨兵模式及集群比较
数据库·redis·架构
悟纤10 小时前
Spring Boot 实用小技巧:多级缓存(Caffeine + Redis)- 第545篇
spring boot·后端·spring