sprinboot项目AOP详解

1.AOP基础

1.概述

AOPAspect Oriented Programming面向切面编程、面向方面编程),其实就是面向特定方法编程。

场景:案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时

实现:动态代理是面向切面编程最主流的实现。而SpringAOPSpring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

2.简单实现步骤

1.导入pom依赖:

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

2.编写AOP程序:针对于特定方法根据业务需要进行编程

@Component
@Aspect
public class TimeAspect {
    @Around("execution(* com.itheima.service.*.*(..))")
    public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long begin = System.currentTimeMillis();
        Object object = proceedingJoinPoint.proceed(); //调用原始方法运行
        long end = System.currentTimeMillis();
        log.info(proceedingJoinPoint.getSignature()+"执行耗时: {}ms", end - begin);
        return object;
    }

3.AOP核心概念

2.AOP进阶

1.通知类型

1.@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行

2.@Before:前置通知,此注解标注的通知方法在目标方法前被执行

3.@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行

4.@AfterReturning 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行

5.@AfterThrowing 异常后通知,此注解标注的通知方法发生异常后执行

ps:

@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行

@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。

@PointCut注解

该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。

2.通知顺序

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。

执行顺序:

1.不同切面类中,默认按照切面类的类名字母排序

        目标方法前的通知方法:字母排名靠前的先执行

        目标方法后的通知方法:字母排名靠前的后执行

2.@Order(数字) 加在切面类上来控制顺序

        目标方法前的通知方法:数字小的先执行

        目标方法后的通知方法:数字小的后执行

3.切入点表达式

切入点表达式:描述切入点方法的一种表达式

作用:主要用来决定项目中的哪些方法需要加入通知

常见形式:

1.切入点表达式-execution

execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

其中带 ? 的表示可以省略的部分

访问修饰符:可省略(比如: publicprotected

包名.类名: 可省略

throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

2.切入点表达式-@annotation

1.定义注解类

代码如下:

@Retention(RetentionPolicy.RUNTIME)//该注解表示该注解运行时有效
@Target(ElementType.METHOD)//该注解表示该注解只能作用方法上
public @interface mylog {
}

此代码是一个注解类,起标识作用

2.定义aop切面类

代码如下:

@Component
@Aspect
@Slf4j
public class aop {
    @Pointcut("@annotation(com.sky.annotation.mylog)")
    public void pt(){
    }
    @Before("pt()")
    public void before(){
      log.info("before执行了");
    }
}

 @Pointcut中是注解类的路径。此代码表示在方法上加入了@mylog注解的都会执行这个before前置类型的方法。

4.连接点

在通知方法上的形参就是连接点,除了around通知是ProceedingJoinPoint参数,其他四种是JoinPoint参数。

around通知中ProceedingJoinPoint参数调用代码如下:

    @Around("pt()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取目标类名
        String classname = joinPoint.getTarget().getClass().getName();
        //获取目标方法名
        String functionname = joinPoint.getSignature().getName();
        //获取目标方法的参数
        Object[] args = joinPoint.getArgs();
        //放行方法
        Object reslut = joinPoint.proceed();
        //返回返回值
        return reslut;
    }

其他四种通知JoinPoint参数调用代码如下:(以before为例)

    @Before("pt()")
    public void before(JoinPoint joinPoint){
        //获取目标类名
        String classname = joinPoint.getTarget().getClass().getName();
        //获取目标方法名
        String functionname = joinPoint.getSignature().getName();
        //获取目标方法的参数
        Object[] args = joinPoint.getArgs();
    }

3.AOP记录操作日志

1.导入对应的表结构和表对应的实体类

表结构代码如下:

-- 操作日志表
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
    operate_user int unsigned comment '操作人ID',
    operate_time datetime comment '操作时间',
    class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
    cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

实体类对应如下:

@Data//get,set方法
@NoArgsConstructor//无参构造
@AllArgsConstructor//有参构造
public class OperateLog {
    private Integer id; //ID
    private Integer operateUser; //操作人ID
    private LocalDateTime operateTime; //操作时间
    private String className; //操作类名
    private String methodName; //操作方法名
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
    private Long costTime; //操作耗时
}

操作表的mapper类代码如下:

@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
    public void insert(OperateLog log);

}

2.进行aop操作

注解类代码如下:(起标识作用)

@Retention(RetentionPolicy.RUNTIME)//该注解表示该注解运行时有效
@Target(ElementType.METHOD)//该注解表示该注解只能作用方法上
public @interface mylog {
}

记录日志适合用around通知。

代码如下:

@Component
@Aspect
@Slf4j
public class log {

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Around("@annotation(com.sky.annotation.mylog)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取当前登陆人的id
        String token = httpServletRequest.getHeader("token");
        Claims claims = JwtUtil.parseJWT("Hs256", token);
        Integer id = (Integer) claims.get("id");
        //操作时间
        LocalDateTime now = LocalDateTime.now();
        //操作类名
        String classname = joinPoint.getTarget().getClass().getName();
        //操作方法名
        String functionname = joinPoint.getSignature().getName();
        //获取方法参数
        Object[] args = joinPoint.getArgs();
        String methodparams = Arrays.toString(args);
        //获取开始时间
        long begin = System.currentTimeMillis();
        //放行
        Object reslut = joinPoint.proceed();
        //获取结束时间
        long end = System.currentTimeMillis();
        //执行耗时
        Long costTime = end - begin;
        //获取方法返回值
        String reslutvalue = JSONObject.toJSONString(reslut);

        //记录操作日志
        OperateLog operateLog = new OperateLog(null, id, now, classname, functionname, methodparams, reslutvalue, costTime);
        OperateLogMapper.insert(operateLog);

        return reslut;
    }
}

此代码获取信息填充到实体类中,再执行mapper方法操作数据库实现记录日志的功能。

最终在要记录日志的方法上加上@mylog注解即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值