SpringAop是什么?

简单介绍:

AOP:Aspect Oriented Programming (面向切面编程、面向方面编程),其实就是面向特定方法编程。

场景:

比如现在有一个需求,我要统计每一个业务方法的耗时时长,

我们只需在业务方法的前面获取一个开始时间,在方法的后面再获取一个结束时间,然后将两时间相减就能得出耗时时长。

这样做当然没问题,但是在一个项目中,业务方法是很多的,如果每次都这样操作,那工作量是非常大的,我们需要换一种方式实现。

我们就可以用到springAop技术

核心:在不惊动原始代码的基础上增强功能。

我们需要统计业务方法的耗时,可以创建一个模板方法,将记录耗时时长的公共的代码放入模板方法。

                                     

原始方法就是业务方法,面向这种特定方法的编程就是面向切面编程。

这个模板方法中定义的逻辑就是创建出来的代理对象方法的逻辑。

实现:

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

编写AOP程序:

针对特定方法根据业务需要进行编程

我们需要创建一个切面类,在类中定义模板方法,并把类交给spring管理

@Component
@Aspect //当前类为切面类
@Slf4j
public class TimeAspect {

    @Around("execution(* com.itheima.service.*.*(..))") 
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        //记录方法执行开始时间
        long begin = System.currentTimeMillis();

        //执行原始方法
        Object result = pjp.proceed();

        //记录方法执行结束时间
        long end = System.currentTimeMillis();

        //计算方法执行耗时
        log.info(pjp.getSignature()+"执行耗时: {}毫秒",end-begin);

        return result;
    }
}

pjp.proceed() 执行原始方法,原始方法可能有返回值,所以最后要把返回值retrun。通过pjp.getSignature() 可以获取原始方法的名称,这样就可以具体知道是哪个方法耗时的时间。

在@Around注解中有个excution表达式,表示对哪些方法进行增强,* com.itheima.service.*.*(..),

* 表示方法的返回值  ,后面的是包名,在任意接口的任意方法上执行。(..)表示方法的形参也是任意的。


AOP核心概念:

①连接点:JointPoint ,可以被AOP控制的方法(暗含方法执行时的相关信息)。

②通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)。

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

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

⑤目标对象:Target,通知所应用的对象

AOP执行流程:

定义好切入点表达式后,在程序运行时,SpringAOP会自动地基于动态代理技术为目标对象生成一个对应的代理对象,也就是上图的DeptServiceProxy对象, 在对象中已经做了对业务方法的增强。此时,再进行service注入的时候,spring就不会注入原始的目标对象,而是注入生成出来的代理对象,此代理对象已经对业务方法做了增强的处理。


AOP进阶:
Spring中AOP的通知类型:
  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行

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

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

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

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

注意:

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

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

五种通知类型代码演示:
@Slf4j
@Component
@Aspect
public class MyAspect1 {

    //切入点方法(公共的切入点表达式)
    @Pointcut("execution(* com.itheima.service.*.*(..))")
    private void pt(){

    }

    //前置通知(引用切入点)
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info("before ...");

    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();
        //原始方法在执行时:发生异常
        //后续代码不在执行

        log.info("around after ...");
        return result;
    }

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

    //返回后通知(程序在正常执行的情况下,会执行的后置通知)
    @AfterReturning("pt()")
    public void afterReturning(JoinPoint joinPoint){
        log.info("afterReturning ...");
    }

    //异常通知(程序在出现异常的情况下,执行的后置通知)
    @AfterThrowing("pt()")
    public void afterThrowing(JoinPoint joinPoint){
        log.info("afterThrowing ...");
    }
}

通知顺序:

默认按照切面类的类名字母排序:

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

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

一般我们通过@Order注解可以控制通知的执行顺序

@Slf4j
@Component
@Aspect
@Order(2)  //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
public class MyAspect2 {
    //前置通知
    @Before("execution(* com.itheima.service.*.*(..))")
    public void before(){
        log.info("MyAspect2 -> before ...");
    }

    //后置通知 
    @After("execution(* com.itheima.service.*.*(..))")
    public void after(){
        log.info("MyAspect2 -> after ...");
    }
}
切入点表达式:

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

它的常见形式有两种:

①execution(……):

根据方法的签名来匹配

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

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

  • 访问修饰符:可省略(比如: public、protected)

  • 包名.类名: 可省略

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

示例:

@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")

②@annotation(……) :

根据注解匹配

自定义注解:Log           一般我们记录操作日志需要用到  自定义注解 + 环绕通知

//自定义注解
@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;  // 是否保存响应的参数
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(value = LogAspect.class)            // 通过Import注解导入日志切面类到Spring容器中
public @interface EnableLogAspect {  //这个注解可以添加到启动类上,程序启动时可以扫描到LogAspect这个切面类
}


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

    @Autowired
    private AsyncOperLogService asyncOperLogService;

    @Around("@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 ;
    }
}

连接点:

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型

  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

@Slf4j
@Component
@Aspect
public class MyAspect7 {

    @Pointcut("@annotation(com.itheima.anno.MyLog)")
    private void pt(){}
   
    //前置通知
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");
    }
    
    //后置通知
    @Before("pt()")
    public void after(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");
    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //获取目标类名
        String name = pjp.getTarget().getClass().getName();
        log.info("目标类名:{}",name);

        //目标方法名
        String methodName = pjp.getSignature().getName();
        log.info("目标方法名:{}",methodName);

        //获取方法执行时需要的参数
        Object[] args = pjp.getArgs();
        log.info("目标方法参数:{}", Arrays.toString(args));

        //执行原始方法
        Object returnValue = pjp.proceed();

        return returnValue;
    }
}

相关推荐

  1. SpringAOP和AspectJ有什么关系 ?

    2024-02-20 19:20:03       19 阅读
  2. SpringAOP的实现原理

    2024-02-20 19:20:03       31 阅读
  3. datalist 什么?以及作用什么

    2024-02-20 19:20:03       18 阅读
  4. Spring什么??IOC又什么??

    2024-02-20 19:20:03       9 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-20 19:20:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-20 19:20:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-20 19:20:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-20 19:20:03       20 阅读

热门阅读

  1. Sql Server 视图

    2024-02-20 19:20:03       32 阅读
  2. K8s Deployment挂载ConfigMap权限设置

    2024-02-20 19:20:03       38 阅读
  3. 练习:鼠标类设计之2_类和接口

    2024-02-20 19:20:03       31 阅读
  4. 基于python+django+vue.js开发的健身房管理系统

    2024-02-20 19:20:03       24 阅读
  5. 某movie搜索接口

    2024-02-20 19:20:03       27 阅读
  6. 深入理解C语言中的联合体(union)

    2024-02-20 19:20:03       21 阅读
  7. js之事件循环

    2024-02-20 19:20:03       27 阅读
  8. Qt之QChar类的字符判断

    2024-02-20 19:20:03       23 阅读
  9. Qt标准对话框设置

    2024-02-20 19:20:03       29 阅读
  10. 算法训练营day31,贪心算法5

    2024-02-20 19:20:03       30 阅读