SpringBoot配置事务、统一异常处理以及日志记录【项目实现】

发布于:2023-01-04 ⋅ 阅读:(699) ⋅ 点赞:(0)

一、配置事务

为了避免在项目运行过程中,代码出现异常导致数据错误。我们需要在项目的服务层配置事务。事务即一段代码要么同时成功,要么同时失败。

SpringBoot默认开启@Transactional注解,Spring容器会自动扫描@Transactional修饰的方法和类。当注解在类上的时候意味着此类的所有public方法都是开启事务的。被注解的方法都成为一个事务整体,同一个事务内共享一个数据库连接,所有操作同时发生。如果在事务内部执行过程中发生了异常,则事务整体会自动回滚。我们在service层的所有类上方添加@Transactional注解即可配置事务:

具体代码示例:

@Service
@Transactional
public class AdminService {

}

二、统一异常处理

当代码抛出异常后,网页上展示的500错误码比较难看,我们可以对项目进行统一异常处理,跳转到用户体检较好的500页面。

SpringBoot项目中,当程序出现了异常,SpringBoot会使用自带的BasicErrorController对象处理异常。该处理器会默认跳转到/resources/templates/error.html页面,我们只需编写error.html页面即可进行统一异常处理:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>首页</title>
    <th:block th:replace="/backstage/common_resources::common_css"/>
    <th:block th:replace="/backstage/common_resources::common_js"/>
</head>
<body class="hold-transition skin-purple sidebar-mini">
<div class="error-page">
    <h2 class="headline text-red">500</h2>
    <div class="error-content">
        <h3><i class="fa fa-warning text-red"></i> Oops! 页面程序有错误.</h3>
    </div>
</div>
</body>
</html>

在引入页面之前需要引入Thymeleaf依赖

三、记录日志

在后台代码运行的过程中,我们要对每一次操作进行日志记录,一方面通过日志可以发现代码的缺陷,另一方面可以追踪内部人员的操作记录。
SpringBoot默认使用Logback组件作为日志管理,首先在/resources下添加Logback配置文件logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定义日志文件的存储地址-->
    <property name="LOG_HOME" value="${catalina.base}/logs/"/>

    <!-- 控制台输出 -->
    <appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 日志输出编码 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            </pattern>
        </layout>
    </appender>

    <!-- 按照每天生成日志文件 -->
    <appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/server.%d{yy99-MM-dd}.log</FileNamePattern>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <!--格式化输出:%d表示时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            </pattern>
        </layout>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 日志输出级别 -->
    <root level="info">
        <appender-ref ref="Stdout"/>
        <appender-ref ref="RollingFile"/>
    </root>
</configuration>

此时我们可以在代码中使用Logger对象打印日志,但如果每个方法都添加日志打印的代码,需要维护大量代码,最好的方式是使用AOP技术,将所有的控制器方法作为切点,在方法执行完成后自动执行打印日志的代码。

①编写日志实体类


@Data
public class Log {
    private String url; // 访问的路径
    private Date visitTime; // 访问时间
    private String username; // 访问者
    private String ip; // 访问ip
    private int executionTime; // 访问时长
    private String exceptionMessage; // 异常信息
}

②在pom中添加AOP依赖


<!-- AOP -->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

③编写日志AOP类

package com.zhang.travel.aop;

import com.zhang.travel.bean.Log;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

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


@Component
@Aspect
public class LogAop {
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private final static Logger logger = LoggerFactory.getLogger(LogAop.class);


    @Pointcut("execution(* com.zhang.travel.controller.backstage.*.*(..))")
    public void pointCut(){}


    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) {
        // 记录访问时间
        Date date = new Date();
        request.setAttribute("visitTime",date);
    }


    @After("pointCut()")
    public void doAfter(){
        Log log = new Log();


        Date visitTime = (Date) request.getAttribute("visitTime"); // 访问时间
        Date now = new Date();
        int executionTime = (int)(now.getTime() - visitTime.getTime()); // 访问时长
        String ip = request.getRemoteAddr(); // 访问ip
        String url = request.getRequestURI();// 访问路径
        // 拿到security中的User对象
        Object user = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (user instanceof User){
            String username = ((User)user).getUsername();
            log.setUsername(username);
        }
        log.setExecutionTime(executionTime);
        log.setUrl(url);
        log.setIp(ip);
        log.setVisitTime(visitTime);


        logger.info(log.toString());
    }


    @AfterThrowing(pointcut = "pointCut()",throwing = "ex")
    public void afterThrowing(Throwable ex){
        Log log = new Log();


        Date visitTime = (Date) request.getAttribute("visitTime"); // 访问时间
        Date now = new Date();
        int executionTime = (int) (now.getTime() - visitTime.getTime()); // 访问时长
        String ip = request.getRemoteAddr(); // 访问ip
        String url = request.getRequestURI(); // 访问路径
        // 拿到Security中的User对象
        Object user = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (user instanceof User){
            String username = ((User) user).getUsername();
            log.setUsername(username);
        }
        log.setExecutionTime(executionTime);
        log.setUrl(url);
        log.setIp(ip);
        log.setVisitTime(visitTime);


        // 异常信息
        String exMessage = ex.getMessage();
        log.setExceptionMessage(exMessage);


        logger.info(log.toString());
    }
}