自学内容网 自学内容网

SpringBoot整合AOP

一、环境测试,小白AOP业务操作

1.添加依赖

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

2. 编写注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {// 自定义操作日志记录注解

    public String title() ;// 模块名称
    public OperatorType operatorType() default OperatorType.MANAGE;// 操作人类别
    public int businessType() ;     // 业务类型(0其它 1新增 2修改 3删除)
    public boolean isSaveRequestData() default true;   // 是否保存请求的参数
    public boolean isSaveResponseData() default true;  // 是否保存响应的参数
    
}

3.OperatorType

public enum OperatorType {// 操作人类别
    OTHER,// 其他
    MANAGE,// 后台用户
    MOBILE// 手机端用户
}

4.定义切面

@Aspect
@Component
@Slf4j
public class LogAspect {            // 环绕通知切面类定义

    @Around(value = "@annotation(sysLog)")  //这里的sysLog是参数,是下面一行Log sysLog,是不是迷迷糊糊地
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint , Log sysLog) {
        String title = sysLog.title();
        log.info("LogAspect...doAroundAdvice方法执行了"+title);
        System.out.println("LogAspect...doAroundAdvice方法执行了"+title);
        Object proceed = null;
        try {
            proceed = joinPoint.proceed();              // 执行业务方法
        } catch (Throwable e) {                         // 代码执行进入到catch中,业务方法执行产生异常
            throw new RuntimeException(e);
        }
        return proceed ;                                // 返回执行结果
    }
}

注意:在微服务开发过程中,LogAspect 要确保被spring扫描到,因为SpringBoot启动时,仅仅扫描当前包和当前包的子包

5.使用

@Log(title = "图书查询",opertorype = OperatorType.MANAGE,businussType = 0)
    public R getPage(@RequestParam(defaultValue = "1") Integer page,
                     @RequestParam(defaultValue = "5") Integer pageSize,
                     @RequestParam(defaultValue = "") String name)
    {
        PageHelper.startPage(page,pageSize);
        List<Book> list = bookDao.page(name);
        PageInfo<Book> bookPageInfo = new PageInfo<>(list);
        PageDto pageDto = new PageDto();
        pageDto.setTotal(bookPageInfo.getTotal());
        pageDto.setRows(bookPageInfo.getList());
        return R.success(bookPageInfo);
    }

以上代码是小白学习aop的例子,也是很多培训机构视频教学的例子,然而跟着一些机构的视频,根本学不到实际开发aop是如何运用的,皮毛而已,请继续往下浏览

二、保存日志到数据库,实际开发AOP业务操作

1.日志数据表结构

常见的记录日志标的结构如下

CREATE TABLE `sys_oper_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
  `title` varchar(50) DEFAULT '' COMMENT '模块标题',
  `business_type` varchar(20) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
  `method` varchar(100) DEFAULT '' COMMENT '方法名称',
  `request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
  `operator_type` varchar(20) DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
  `oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
  `dept_name` varchar(50) DEFAULT '' COMMENT '部门名称',
  `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
  `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
  `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
  `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
  `status` int DEFAULT '0' COMMENT '操作状态(0正常 1异常)',
  `error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
  `oper_time` datetime DEFAULT NULL COMMENT '操作时间',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb3 COMMENT='操作日志记录';

2.实体类

@Data
public class SysOperLog extends BaseEntity {
private String title;// 模块标题
private String method;// 方法名称
private String requestMethod;// 请求方式
private String operatorType;// 操作类别(0其它 1后台用户 2手机端用户)
    private Integer businessType;// 业务类型(0其它 1新增 2修改 3删除)
private String operName;// 操作人员
private String operUrl;// 请求URL
private String operIp;// 主机地址
private String operParam;// 请求参数
private String jsonResult;// 返回参数
private Integer status;// 操作状态(0正常 1异常)
private String errorMsg;// 错误消息
}
@Data
public class BaseEntity implements Serializable {
    private Long id;
    private Date createTime;
    private Date updateTime;
    private Integer isDeleted;
}

3.日志工具类

3.1环绕通知分析

在环绕通知之前:要创建日志对象,
环绕通知前置:日志对象中这些应该set掉,

title,method,requestMethod,operatorType,businessType,operName,operUrl,operIp,operParam

环绕通知后置:

jsonResult,status,errorMsg

3.2工具类

public class LogUtil {

    //操作执行之后调用(环绕通知后置)
    public static void afterHandlLog(Log sysLog, Object proceed,
                                     SysOperLog sysOperLog, int status ,
                                     String errorMsg) {
        if(sysLog.isSaveResponseData()) {
            sysOperLog.setJsonResult(JSON.toJSONString(proceed));
        }
        sysOperLog.setStatus(status);
        sysOperLog.setErrorMsg(errorMsg);
    }

    //操作执行之前调用(环绕通知前置)
    public static void beforeHandleLog(Log sysLog,
                                       ProceedingJoinPoint joinPoint,
                                       SysOperLog sysOperLog) {

        // 设置操作模块名称
        sysOperLog.setTitle(sysLog.title());
        sysOperLog.setOperatorType(sysLog.operatorType().name());

        // 获取目标方法信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature() ;
        Method method = methodSignature.getMethod();
        sysOperLog.setMethod(method.getDeclaringClass().getName());

        // 获取请求相关参数
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        sysOperLog.setRequestMethod(request.getMethod());
        sysOperLog.setOperUrl(request.getRequestURI());
        sysOperLog.setOperIp(request.getRemoteAddr());

        // 设置请求参数 只有POst、Get可以带参数
        if(sysLog.isSaveRequestData()) {
            String requestMethod = sysOperLog.getRequestMethod();
            if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
                String params = Arrays.toString(joinPoint.getArgs());
                sysOperLog.setOperParam(params);
            }
        }
        sysOperLog.setOperName(AuthContextUtil.get().getUserName());
    }
}

3.3 切面

package com.atguigu.spzx.common.log.aspect;
@Aspect
@Component
@Slf4j
public class LogAspect {            // 环绕通知切面类定义

    @Autowired
    private AsyncOperLogService asyncOperLogService ;

    @Around(value = "@annotation(sysLog)")
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint , Log sysLog) {

        // 构建前置参数
        SysOperLog sysOperLog = new SysOperLog() ;

        LogUtil.beforeHandleLog(sysLog , joinPoint , sysOperLog) ;

        Object proceed = null;
        try {
            proceed = joinPoint.proceed();
            // 执行业务方法
            LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 0 , null) ;
            // 构建响应结果参数
        } catch (Throwable e) {                                 // 代码执行进入到catch中,
            // 业务方法执行产生异常
            e.printStackTrace();                                // 打印异常信息
            LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 1 , e.getMessage()) ;
            throw new RuntimeException();
        }

        // 保存日志数据
        asyncOperLogService.saveSysOperLog(sysOperLog);

        // 返回执行结果
        return proceed ;
    }
}

3.4 日志service serviceImpl

public interface AsyncOperLogService {// 保存日志数据
    public abstract void saveSysOperLog(SysOperLog sysOperLog) ;
}
@Service
public class AsyncOperLogServiceImpl implements AsyncOperLogService {

    @Autowired
    private SysOperLogMapper sysOperLogMapper;

    @Async      // 异步执行保存日志操作
    @Override
    public void saveSysOperLog(SysOperLog sysOperLog) {
        sysOperLogMapper.insert(sysOperLog);
    }
    
}

3.5 日志mapper

@Mapper
public interface SysOperLogMapper {
    public abstract void insert(SysOperLog sysOperLog);
}

3.6 日志xml

 <insert id="insert" >
        insert into sys_oper_log (
            id,
            title,
            method,
            request_method,
            operator_type,
            oper_name,
            oper_url,
            oper_ip,
            oper_param,
            json_result,
            status,
            error_msg
        ) values (
                     #{id},
                     #{title},
                     #{method},
                     #{requestMethod},
                     #{operatorType},
                     #{operName},
                     #{operUrl},
                     #{operIp},
                     #{operParam},
                     #{jsonResult},
                     #{status},
                     #{errorMsg}
                 )
    </insert>

4 测试结果

在这里插入图片描述

5. 事务失效

不加@Log注解事务可以进行回滚,但是加上该注解以后事务就会失效。
Spring的事务控制是通过aop进行实现的,在框架底层会存在一个事务切面类,当业务方法产生异常以后,事务切面类感知到异常以后事务进行回滚。
当系统中存在多个切面类的时候,Spring框架会按照**@Order注解的值对切面进行排序,@Order的值越小优先级越高,@Order的值越大优先级越低。优先级越高的切面类越优先执行,当我们没有给切面类指定排序值的时候,我们自定义的切面类的优先级和aop切面类的优先级相同,那么此时事务切面类的优先级要高于自定义切面类**,那么切面类的执行顺序如下所示:
在这里插入图片描述
当在自定义切面类中对异常进行了捕获,没有将异常进行抛出,那么此时事务切面类是感知不到异常的存在,因此事务失效。

解决方案一:使用@Order注解提高自定义切面类的优先级

解决方案二:在自定义切面类的catch中进行异常的抛出


原文地址:https://blog.csdn.net/m0_51492817/article/details/142741922

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!