Spring-AOP基础讲解

未实现AOP业务流程:
未实现AOP业务流程
上图中红色部分,全是通用的横切逻辑代码,我们可以用AOP特性抽离横切代码。

实现AOP业务流程:
实现AOP业务流程
上图中的XX切面,就是我们抽离的横切逻辑代码。

Spring-AOP术语

术语解释
Joinpoint(连接点)指可以把增强代码,加入到业务主线中的点。从上图中可以看出,连接点指的就是方法(图中红色的方法名)。使用动态代理技术,可以方法执行前后切入增强代码。
Pointcut(切入点)指Spring指定要切入增强代码的连接点。从上图中可以看出,判断访问权限拦截的三个方法既是连接点也是切入点,而register表现层方法只是连接点不是切入点。
Advice(通知/增强)指提供增强功能的方法。由于增强可以在切入点前后或者异常捕获时执行,这里可以细分为:前置通知后置通知异常通知最终通知环绕通知
Target(目标对象)它指的是代理的目标对象。即被代理对象。
Proxy(代理)它指的是一个类被AOP织入增强后,产生的代理类。即代理对象。
Weaving(织入)指把增强应用到代理对象并创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Aspect(切面)它指定是增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是切面类。例如,事务切面,它里面定义的方法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。

实际在Spring使用中,我们只会用到Pointcut,Advice,Aspect这几个概念:

  • Aspect:切面,封装增强业务代码,指定增强代码在哪个切点的哪个方位执行
  • Pointcut:切点,指定我们要在哪一个方法切入Aspect的增强功能
  • Advice:增强(增强业务+执行方位),指定Aspect的功能在Pointcut方法的哪个方位(前、后、异常后、finally,环绕)进行切入

Spring配置AOP

需要引入依赖包:如果有引入spring其他jar包中依赖aop模块这里可以不用引入

    <!--spring aop的jar包支持-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>

    <!--spring依赖第三方的aop框架aspectj的jar-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.13</version>
    </dependency>

我们分三种方式讲解配置

纯XML配置AOP

Aspect切面类:

public class LogUtils {

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

    /**
     * 业务逻辑结束时执行(无论异常与否)
     */
    public void afterMethod() {
        System.out.println("业务逻辑结束时执行,无论异常与否都执行.......");
    }

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

    /**
     * 业务逻辑正常时执行,参数名称可以配置是指定`retVal`
     */
    public void successMethod(Object retVal) {
        System.out.println("业务逻辑正常时执行.......");
    }

    /**
     * 环绕通知 可替代其他四种通知,不推荐与其他四种通知同时使用。
     * 环绕通知 类似代理模式拦截方法
     *
     */
    public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知中的beforemethod....");
        Object result = null;
        try{
            // 控制原有业务逻辑是否执行
            // result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        }catch(Exception e) {
            System.out.println("环绕通知中的exceptionmethod....");
        }finally {
            System.out.println("环绕通知中的after method....");
        }

        return result;
    }
}
<!--进行aop相关的xml配置,配置aop的过程其实就是把aop相关术语落地-->
<!--横切逻辑bean-->
<bean id="logUtils" class="com.wjy.utils.LogUtils"></bean>

<!--使用config标签表明开始aop配置,在内部配置切面aspect-->
<!--aspect = 切入点(锁定方法)+ 方位(锁定方法中的特殊时机)+ 横切逻辑 -->
<aop:config>

    <!-- 配置一个日志切面 -->
    <aop:aspect id="logAspect" ref="logUtils">
        <!--切入点锁定我们需要拦截的方法,使用aspectj语法表达式:
        返回值 类全限定名.方法(参数)

        返回值:可以用 * 代替任意返回值
        类全限定名:可以使用*代替任意包,或者代替任意类,使用..代替任意层级的包(零到多个层级任意名称的包)
        方法:可以使用*代替任意方法
        参数:可以使用*代替任意参数(必须有参数)使用..代替任意参数(可以没有参数)
        -->
        <aop:pointcut id="pt1" expression="execution(* com.wjy.service.impl.TransferServiceImpl.*(..))"/>

        <!--方位信息,pointcut-ref关联切入点-->
        <!--aop:before前置通知/增强-->
        <aop:before method="beforeMethod" pointcut-ref="pt1"/>
        <!--aop:after,最终通知,无论如何都执行-->
        <aop:after method="afterMethod" pointcut-ref="pt1"/>
        <!--aop:after-returnning,正常执行通知,returning:指定返回值注入的参数名称-->
        <aop:after-returning method="successMethod" returning="retVal" pointcut-ref="pt1"/>
        <!--aop:after-throwing,异常通知-->
        <aop:after-throwing method="exceptionMethod" pointcut-ref="pt1"/>

        <!--环绕通知配置,不推荐和以上四种同时使用-->
        <aop:around method="arroundMethod" pointcut-ref="pt1"/>
    </aop:aspect>
</aop:config>

XML和注解混合配置

类上添加注解:

@Component
// 指定类为切面类
@Aspect
public class LogUtils {

    /**
     * 配置切入点
     */
    @Pointcut("execution(* com.wjy.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++) {
            Object arg = args[i];
            System.out.println(arg);
        }
        System.out.println("业务逻辑开始执行之前执行.......");
    }

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

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

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

    /**
     * 环绕通知 可替代其他四种通知,不推荐与其他四种通知同时使用。
     * 环绕通知 类似代理模式拦截方法
     */
    // 指定为环绕通知,由于不推荐和以上四种同时使用,这里注释掉
    /*@Around("pt1()")*/
    public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知中的beforemethod....");
        Object result = null;
        try {
            // 控制原有业务逻辑是否执行
            // result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        } catch (Exception e) {
            System.out.println("环绕通知中的exceptionmethod....");
        } finally {
            System.out.println("环绕通知中的after method....");
        }
        return result;
    }
}

XML删除所有关于AOP配置添加自动AOP代理配置

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

纯注解配置

Aspect类按照XML和注解混合配置方式注解配置。

在配置类上面添加启动Aspect自动代理注解

@Configuration
@EnableAspectJAutoProxy
public class SpringConfig {
    ......
}

Expression表达式简单说明

配置切点的时候,spring使用expression表达式定位方法,这里简单说明一下如何写:

  • 唯一定位:
    expression="execution(public void com.lagou.edu.service.impl.TransferServiceImpl.transfer(String,String,int))"
    
    要将方法作用域,返回值类型,类全限定名,方法名,方法参数类型列表全部写上
  • 批量定位:
    批量定位就是,有些地方可以用*或者.来表示任意名称,*.的区别是,*最少有一个,.可以一个都没有
    方法作用域可以省略,其他可以用*,.替换
    expression="execution(* com.wjy.service..*.*(..))
    
    上面表示,拦截com.wjy.service包及任意层级子包下任意类的任意方法,参数可有可无,返回值任意类型

SpringAOP实现流程

这里就不分析源码了,只是简单总结一下。

  1. 解析配置文件(XML/注解)获取AOP配置信息
  2. SpringBean声明周期,后置处理器的时候,会将所有AOP配置织入到SpringBean中生成代理对象

好了,这样最后注入的SpringBean就是拥有AOP增强的代理对象了。

注意: AOP增强织入过程中使用了动态代理技术cglib或者jdk,Spring会自动根据当前Bean是否实现接口,动态选择,如果实现接口使用jdk,如果没有使用cglib,也可以配置强制使用cglibproxy-target-class="true",这也是为什么Spring官方建议@Transaction使用在类或者方法上,因为如果加载接口上,有强制使用cglib类代理,那么是获取不到@Transaction注解信息的)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值