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)!