AOP~面向切面编程介绍

AOP基础

概述

  • AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),面向特定方法的编程。

  • 动态代理是面向切面编程最主流的实现。

  • SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

入门程序

  • 需求

    • 统计各个业务层方法的执行耗时

  • 步骤

    • 导入依赖

    • <!--        AOP依赖-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
    • 编写代码

    • package com.testpeople.aop;
      
      import lombok.extern.slf4j.Slf4j;
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.springframework.stereotype.Component;
      
      @Component
      @Slf4j
      @Aspect //AOP类
      public class TimeAspect {
      
      
          @Around("execution(* com.testpeople.service.*.*(..))") //切入点表达式
          public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
              //1.记录开始时间
              long begin = System.currentTimeMillis();
              //2.调用原始方法运行
              Object result = joinPoint.proceed();
              //3.记录结束时间
              long end = System.currentTimeMillis();
      
              //日志
              log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",end-begin);
      
              return result;
      
          }
      
      }
    • 效果

  


优势

  • 场景

    • 记录日志

    • 权限控制

    • 事务管理

  • 优势

    • 代码无侵入

    • 减少重复代码

    • 提高开发效率

    • 维护方便

核心概念

  • 连接点

    • JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)

  • 通知

    • Advice,指哪些重复的逻辑,也就是共性功能(体现为一个方法)

  • 切入点

    • PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用

  • 切面

    • Aspect,描述通知与切入点的对应关系(通知+切入点)

  • 目标对象

          Target,通知所应用的对象

图解

流程

  • 进行开发后,使用的是代理对象,而不是实际对象。

AOP进阶

通知类型

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

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

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

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

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

测试

package com.testpeople.aop;


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class MyAspect {

    @Before("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
    public void before(){

        log.info("前置通知");

    }

    @Around("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        log.info("环绕通知"+"before");

        Object result = joinPoint.proceed();

        log.info("环绕通知"+"after");

        return result;

    }

    @After("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
    public void after(){
        log.info("后置通知");

    }

    @AfterReturning("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
    public void afterReturning(){
        log.info("后置返回通知");
    }

    @AfterThrowing("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
    public  void afterThrowing(){

        log.info("后置异常通知");
    }



}

效果

注意

  • @Around:环绕通知需要自己调用 ProceedingJoinPoint.proceed();

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

tips

  • 切入点表达式抽取

package com.testpeople.aop;


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class MyAspect {

    @Pointcut("execution(* com.testpeople.service.impl.DeptServiceImpl.*(..))")
    private void pt(){

    }
    //这个方法可以改修订范围,然后被别的包引用。


    @Before("pt()")
    public void before(){

        log.info("前置通知");

    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        log.info("环绕通知"+"before");

        Object result = joinPoint.proceed();

        log.info("环绕通知"+"after");

        return result;

    }

    @After("pt()")
    public void after(){
        log.info("后置通知");

    }

    @AfterReturning("pt()")
    public void afterReturning(){
        log.info("后置返回通知");
    }

    @AfterThrowing("pt()")
    public  void afterThrowing(){

        log.info("后置异常通知");
    }



}

通知顺序

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

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

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

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

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

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

  • 概述

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

  • 作用

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

  • 常见形式

    • execution(...);根据方法的签名来匹配

  • 特殊符号

  • 如果匹配两个方法,可以使用“||”连接。

  • Tips

  • @annotation(...);根据注解匹配

    • 新建注解

    • package com.testpeople.aop;
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      @Retention(RetentionPolicy.RUNTIME) //合适生效(运行是)
      @Target(ElementType.METHOD)//作用到哪里(方法)
      public @interface MyLog {
      
      
      }
    • 在需要添加方法上添加注解,更换切入点表达式。

    • @Around("@annotation(com.testpeople.aop.MyLog)")

连接点

  • 开发

  • @Around("@annotation(com.testpeople.aop.MyLog)")
    public Object testJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
    
        //1.获取 目标对象的类名
        String className = joinPoint.getTarget().getClass().getName();
        log.info(className);
        //2.获取 目标方法的方法名
        String methodName = joinPoint.getSignature().getName();
        log.info(methodName);
        //3.获取 目标方法运行时传入的参数
        Object[] methodArgs = joinPoint.getArgs();
        log.info(Arrays.toString(methodArgs));
        //4.放行 目标方法执行
        Object result = joinPoint.proceed();
    
        //5.获取 目标方法的返回值
        log.info(result.toString());
    
        return result;//此处可以改变函数的返回结果(添加其他方法)
    }

效果

AOP案例(操作日志记录功能)

需求

  • 将案例中 增、删、改相关的接口的操作日志记录到数据库表中

  • 日志信息~操作人、操作时间、执行方法的全类名、执行方法、方法运行时的参数、返回值、方法执行的时长。

思路

步骤

  • 准备

    • 在案例中引入AOP的依赖

    • <!--        AOP依赖-->
      
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
    • 设计好数据库表

    • # 操作日志记录表
      create table operate_log(
          id int unique 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 '操作日志记录表';
    • 设计好实体类

    • package com.testpeople.pojo;
      
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      import java.time.LocalDateTime;
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      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接口

    • package com.testpeople.mapper;
      
      import com.testpeople.pojo.OperateLog;
      import org.apache.ibatis.annotations.Delete;
      import org.apache.ibatis.annotations.Insert;
      import org.apache.ibatis.annotations.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})")
          void insert(OperateLog operateLog);
      
      }
    • 编码

      • 自定义注解

      • package com.testpeople.anno;
        
        import java.lang.annotation.ElementType;
        import java.lang.annotation.Retention;
        import java.lang.annotation.RetentionPolicy;
        import java.lang.annotation.Target;
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.METHOD)
        public @interface Log {
        }
      • 定义切面类,完成记录操作日志的逻辑

      • package com.testpeople.aop;
        
        import com.alibaba.fastjson.JSONObject;
        import com.testpeople.mapper.OperateLogMapper;
        import com.testpeople.pojo.OperateLog;
        import com.testpeople.utils.JwtUtils;
        import lombok.extern.slf4j.Slf4j;
        import org.aspectj.lang.ProceedingJoinPoint;
        import org.aspectj.lang.annotation.Around;
        import org.aspectj.lang.annotation.Aspect;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Component;
        
        import javax.servlet.http.HttpServletRequest;
        import java.time.LocalDateTime;
        import java.util.Arrays;
        
        @Slf4j
        @Component
        @Aspect
        public class LogAspct {
        
            @Autowired
            private HttpServletRequest request;
        
            @Autowired
            private OperateLogMapper operateLogMapper;
        
            @Around("@annotation(com.testpeople.anno.Log)")
            public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
        
                //操作人ID~ 当前员工ID
                //获取请求头中的jwt令牌中的员工ID
                String jwt = request.getHeader("token");
                //解析
                Integer operateUser = (Integer) JwtUtils.parseJwt(jwt).get("id");
                //操作时间
                LocalDateTime operateTime = LocalDateTime.now();
                //类名
                String className = joinPoint.getTarget().getClass().getName();
                //方法名
                String methodName = joinPoint.getSignature().getName();
                //方法参数
                Object[] args = joinPoint.getArgs();
                String methodParams = Arrays.toString(args);
        
                //记录时间
                long begin = System.currentTimeMillis();
        
                //调用原始目标方法运行
                Object result = joinPoint.proceed();
                
                //记录结束时间
                
                long end = System.currentTimeMillis();
                
                //方法返回值
                String returnValue = JSONObject.toJSONString(result);
                //耗时
                long costTime = end - begin;
        
        
                //记录操作日志
        
                OperateLog operateLog = new OperateLog();
        
                operateLog.setOperateUser(operateUser);
                operateLog.setOperateTime(operateTime);
                operateLog.setClassName(className);
                operateLog.setMethodName(methodName);
                operateLog.setMethodParams(methodParams);
                operateLog.setReturnValue(returnValue);
                operateLog.setCostTime(costTime);
        
                operateLogMapper.insert(operateLog);
        
                log.info("AOP记录操作日志 {}",operateLog.toString()+"\n");
        
                return result;
        
            }
        
        
        }
      • 给有需求的类添加@Log注解

效果

注意

  • 获取当前用户

    • 从request中获取token 在token中提取当前用户id


以上是对SpringBoot框架中的AOP相关的介绍以及简单的使用,通过一个操作记录日志功能进行练习。多点关注、多点爱。(以上知识点笔记来自于小编学习黑马程序员的课程所记录)

项目地址

admin_web_project: 黑马程序员项目javaWebjavaWeb开发学习仓库,前后端分离项目前端Vue后端springboot数据库Mysql

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值