Spring学习笔记--SpringAOP应用

Spring学习笔记–SpringAOP应用

AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、日志代码、事务控制代码、性能监控代码。

AOP相关术语

在这里插入图片描述
上图描述的就是未采⽤AOP思想设计的程序,当我们红⾊框中圈定的⽅法时,会带来⼤量的重复劳动。程序中充斥着⼤量的重复代码,使我们程序的独⽴性很差。⽽下图中是采⽤了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运⽤动态代理技术,在运⾏期对需要使⽤的业务逻辑⽅法进⾏增强。
在这里插入图片描述

AOP术语
  • Joinpoint连接点:它是指那些可以用于把曾庆代码加入到业务主线中的点,那么由上图中我们可以看出这些点指的就是方法。在方法执行的前后通过代理技术加入增强的代码。在Spring框架AOP思想的技术实现中,也只支持方法类型的连接点。(方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点),每个方法都有连接点,连接点是一种候选点。
  • Pointcut切入点:它是指那些已经把增强代码加入到业务主线进来之后的连接点。由上图中,我们看出表现层transfer方法就只是连接点,因为判断访问权限的功能并没有对其曾强。指定AOP思想想要影响的具体方法是那些。
  • Advice通知/增强:它是指切面类中用于提供增强功能的方法。并且不同的方法增强的时机也是不一样的,比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等,那么这些就是通知的类型。其分类由:前置通知、后置通知、异常通知、最终通知、环绕通知,增强分为两个层次,一个是横切逻辑,一个是方位点(在某一些连接点加入横切逻辑,那么这些连接点称为方位点)
  • Target目标对象:它是指代理的目标对象。即被代理对象。
  • Proxy代理:它是指一个类被AOP织入增强后,产生的代理类。即代理对象。
  • Weaving织入:它是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而Aspect采用编译期和类装载期织入
  • Aspect切面:它是指增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是切面类。例如,事务切面,它里面定义的方法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。我们前面的案例中TrasnactionManager就是一个切面。切面=切入点+增强 // 切入点(锁定方法)+横切逻辑+方位点(锁定时机)
    众多的概念,目的就是为了锁定要在哪个地方插入什么横切逻辑代码

Spring中AOP代理选择

Spring 实现AOP思想使⽤的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现
任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过
我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。两种代理模式之前文章有过介绍->代理模式

Spring中AOP的配置方式

Spring的AOP配置中,也和IOC配置一样,支持三类配置方式:

  • 使用XML配置
  • 使用XML+注解组合配置
  • 使用纯注解配置

Spring中AOP实现

需求:横切逻辑代码是打印⽇志,希望把打印⽇志的逻辑织⼊到⽬标⽅法的特定位置(service层transfer方法)
引入aop坐标:

	<!--spring aop的jar包支持-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
     <!--第三方的aop框架aspectj的jar-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.13</version>
    </dependency>

纯XML模式

在applicationContext.xml添加AOP配置

  1. 添加aop约束头:
<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
"></bean>
  1. 配置aop的过程其实就是把aop的相关术语实现
<!--进行aop相关的xml配置,配置aop的过程其实就是把aop相关术语落地-->
    <!--横切逻辑bean-->
    <bean id="logUtils" class="com.lagou.edu.utils.LogUtils"></bean>
    <!--使用config标签表明开始aop配置,在内部配置切面aspect-->
    <!--aspect = 切入点(锁定方法)+方位点(锁定方法中的特殊时机)+横切逻辑-->
    <aop:config>
        <aop:aspect id="LogAspect" ref="logUtils">
            <!--切入点锁定我们该兴趣的方法,使用aspectj语法表达式-->
            <aop:pointcut id="pt1" expression="execution(public void com.lagou.edu.service.impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String , int))"/>
            <!--方位信息-->
            <aop:before method="beforeMethod" pointcut-ref="pt1"></aop:before>
            <aop:after-returning method="afterMethod" pointcut-ref="pt1"></aop:after-returning>
            <aop:after-throwing method="exceptionMethod" pointcut-ref="pt1"></aop:after-throwing>
            <aop:after method="successMethod" pointcut-ref="pt1"></aop:after>
         </aop:aspect>
    </aop:config>
细节
  • 关于切入点表达式
    上述配置实现了对 TransferServiceImpl 的updateAccountByCardNo ⽅法进⾏增强,在其执⾏之前,输出了记录⽇志的语句。这⾥⾯,我们接触了⼀个⽐较陌⽣的名称:切⼊点表达式,它是做什么的呢?我们往下看。
    概念及作用
    切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分。
    关于AspectJ
    AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。
    切入点表达式使用示例
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
 全匹配⽅式:
 public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
 访问修饰符可以省略
 void
com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
 返回值可以使⽤*,表示任意返回值
 *
com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
 *
....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Accou
nt)
 包名可以使⽤..表示当前包及其⼦包
 *
..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
 类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
 * ...(com.lagou.pojo.Account)
 参数列表,可以使⽤具体类型
 基本类型直接写类型名称 : int
 引⽤类型必须写全限定类名:java.lang.String
 参数列表可以使⽤*,表示任意参数类型,但是必须有参数
 * *..*.*(*)
 参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
 * *..*.*(..)
 全通配⽅式:
 * *..*.*(..)
  • 改变代理⽅式的配置
    在前⾯我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了接⼝,则采⽤基于接⼝的动态代理。当被代理对象没有实现任何接⼝的时候,Spring会⾃动切换到基于⼦类的动态代理⽅式。
    但是我们都知道,⽆论被代理对象是否实现接⼝,只要不是final修饰的类都可以采⽤cglib提供的⽅式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的⽅式实现强制使⽤基于⼦类的动态代理(即cglib的⽅式),配置的⽅式有两种
    使用aop:config标签配置
<aop:config proxy-target-class="true">

使⽤aop:aspectj-autoproxy标签配置

<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP
的⽀持-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy>
  • 五种通知类型
    前置通知 配置⽅式:aop:before标签
    前置通知永远都会在切⼊点⽅法(业务核⼼⽅法)执⾏之前执⾏。
    前置通知可以获取切⼊点⽅法的参数,并对其进⾏增强。
<!--
 作⽤:
 ⽤于配置前置通知。
 出现位置:
 它只能出现在aop:aspect标签内部
 属性:
 method:⽤于指定前置通知的⽅法名称
 pointcut:⽤于指定切⼊点表达式
 pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:before method="printLog" pointcut-ref="pointcut1">
</aop:before>

正常执⾏时通知

<!--
 作⽤:
 ⽤于配置正常执⾏时通知
 出现位置:
 它只能出现在aop:aspect标签内部
 属性:
 method:⽤于指定后置通知的⽅法名称
 pointcut:⽤于指定切⼊点表达式
 pointcut-ref:⽤于指定切⼊点表达式的引⽤
 -->
 <aop:after-returning method="afterReturningPrintLog" pointcutref="pt1"></aop:after-returning>

异常通知
异常通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏产⽣异常之后,异常通知执⾏。如果切⼊点⽅法执⾏没有产⽣异常,则异常通知不会执⾏。
异常通知不仅可以获取切⼊点⽅法执⾏的参数,也可以获取切⼊点⽅法执⾏产⽣的异常信息。

<!--
 作⽤:
 ⽤于配置异常通知。
 出现位置:
 它只能出现在aop:aspect标签内部
 属性:
 method:⽤于指定异常通知的⽅法名称
 pointcut:⽤于指定切⼊点表达式
 pointcut-ref:⽤于指定切⼊点表达式的引⽤
 
 -->
 <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"
></aop:after-throwing>

最终通知执⾏时机
最终通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏完成之后,切⼊点⽅法返回之前执⾏。换句话说,⽆论切⼊点⽅法执⾏是否产⽣异常,它都会在返回之前执⾏。
最终通知执⾏时,可以获取到通知⽅法的参数。同时它可以做⼀些清理操作。

<!--
 作⽤:
 ⽤于指定最终通知。
 出现位置:
 它只能出现在aop:aspect标签内部
 属性:
 method:⽤于指定最终通知的⽅法名称
 pointcut:⽤于指定切⼊点表达式
 pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

环绕通知
环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,实现⼿动触发切⼊点⽅法的调⽤。

<!--
 作⽤:
 ⽤于配置环绕通知。
 出现位置:
 它只能出现在aop:aspect标签的内部
 属性:
 method:⽤于指定环绕通知的⽅法名称
 pointcut:⽤于指定切⼊点表达式
 pointcut-ref:⽤于指定切⼊点表达式的引⽤
 
 -->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>

XML+注解模式

XML 中开启 Spring 对注解 AOP 的⽀持,注释掉了纯XML配置下的aop配置,添加开启注解驱动配置。

 <!--进行aop相关的xml配置,配置aop的过程其实就是把aop相关术语落地-->
    <!--横切逻辑bean-->
    <!--<bean id="logUtils" class="com.lagou.edu.utils.LogUtils"></bean>-->
    <!--使用config标签表明开始aop配置,在内部配置切面aspect-->
    <!--aspect = 切入点(锁定方法)+方位点(锁定方法中的特殊时机)+横切逻辑-->
   <!-- <aop:config>
        <aop:aspect id="LogAspect" ref="logUtils">-->
            <!--切入点锁定我们该兴趣的方法,使用aspectj语法表达式-->
<!--
            <aop:pointcut id="pt1" expression=/>
-->
            <!--方位信息-->
            <!--环绕通知一般不和其他通知一起使用-->
            <!--aop:before method="beforeMethod" pointcut-ref="pt1"></aop:before>
            &lt;!&ndash;正常执行通知&ndash;&gt;
            <aop:after-returning method="afterMethod" pointcut-ref="pt1"></aop:after-returning>
            <aop:after-throwing method="exceptionMethod" pointcut-ref="pt1"></aop:after-throwing>
            <aop:after method="successMethod" pointcut-ref="pt1" returning="retValue"></aop:after>-->
            <!--<aop:around method="arrountMethod" pointcut-ref="pt1"></aop:around>-->
        <!-- </aop:aspect>
    </aop:config>-->

    <!--开启注解驱动-->
    <!--proxy-target-class 为true强制使用cglib动态代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

示例

package com.lagou.edu.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LogUtils {

    @Pointcut(value = "execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))" )
    public void pt1(){
    }

    /**
     * 业务逻辑开始之前执行
     */
    @Before("pt1()")
    public void beforeMethod(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
        System.out.println("业务逻辑开始执行之前执行");
    }

    /**方法结束执行(无论是否异常都执行)
     *
     */
    @After("pt1()")
    public void afterMethod(){
        System.out.println("业务逻辑结束执行之后执行");
    }

    /**业务逻辑正常时执行
     *
     */
    @AfterReturning(value = "pt1()",returning = "retVal")
    public void successMethod(Object retVal){
        System.out.println("业务逻辑正常时执行");
    }

    /**异常时执行
     *
     */
    @AfterThrowing("pt1()")
    public void exceptionMethod(){
        System.out.println("异常时执行");
    }



    /**环绕通知
     *
     * @param proceedingJoinPoint
     */
    @Around("pt1()")
    public void arrountMethod(ProceedingJoinPoint proceedingJoinPoint){
        //如果不屑这句话原有业务逻辑就不会被执行
        //Object proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());

        try{
            System.out.println("环绕通知前置通知");
            Object proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            System.out.println("环绕通知后置通知");
        } catch (Throwable throwable) {
            System.out.println("环绕通知异常通知");
            throwable.printStackTrace();
        } finally {
            System.out.println("环绕通知正常通知");
        }
        System.out.println("环绕通知。。。。");
    }
}

纯注解模式

在使⽤注解驱动开发aop时,我们要明确的就是,是注解替换掉配置⽂件中的下⾯这⾏配置

<!--开启注解驱动-->
    <!--proxy-target-class 为true强制使用cglib动态代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

在配置类中使⽤如下注解进⾏替换上述配置


@Component
@Aspect
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
public class LogUtils {

    @Pointcut(value = "execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))" )
    public void pt1(){
    }
    ...
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值