Spring之AOP的具体使用

Spring AOP 的具体应用

1. AOP的本质

在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、⽇志代码、事务控制代码、性能监控代码。在原有业务的上下层添加增强额外的功能。下图描述的就是未采⽤AOP思想设计的程序,当我们红⾊框中圈定的⽅法时,会带来⼤量的重复劳动。程序中充斥着⼤量的重复代码,使我们程序的独⽴性很差。

image-20210328125138664

⽽下图中是采⽤了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运⽤动态代理技术,统一在运⾏期对需要使⽤的业务逻辑⽅法进⾏增

image-20210328125430076

2. AOP的常用术语

名词解释
Joinpoint(连接点)它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可 以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的 代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。
Pointcut(切⼊点)它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我 们看出表现层transfer ⽅法就只是连接点,因为判断访问权限的功能并没有对其增强。
Advice(通知/增强)它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀ 样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法 正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么 这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知
Target(⽬标对象)它指的是代理的⽬标对象。即被代理对象。
Proxy(代理)它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。
Weaving(织入)它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代 理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。
Aspect(切⾯)它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这 个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开 启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯ 的案例中TrasnactionManager 就是⼀个切⾯。

连接点:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点

切⼊点:指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法

Advice增强

第⼀个层次:指的是横切逻辑

第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体的特殊时机)

Aspect切⾯:切⾯概念是对上述概念的⼀个综合 Aspect切⾯ = 切⼊点+增强 = 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑众多的概念,⽬的就是为了锁定要在哪个地⽅插⼊什么横切逻辑代码

3. Spring中AOP的代理选择

Spring 实现AOP思想使⽤的是动态代理技术

默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。

4. AOP的具体使用xml模式下

  1. 首先引入spring aop相关的jar
 <!--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>
  </dependencies>
  1. 横切面的创建
/**
 * aop横切面
 * @author lane
 * @date 2021年03月28日 下午1:23
 */
public class AopLogUtils {

    /*
     * aop之业务方法之前执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public void beforeMethod(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println("aop之业务方法之前执行......获取参数0为"+args[0]);
    }

    /*
     * aop之业务方法之后执行(无论异常与否)
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public void afterMethod(){
        System.out.println("aop之业务方法之后执行(无论异常与否)......");
    }

    /*
     * aop之业务方法异常时执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public void exceptionMethod(){
        System.out.println("aop之业务方法之异常时执行......");
    }
    /*
     * aop之业务方法成功时执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public void successMethod(){
        System.out.println("aop之业务方法之成功时执行......");
    }
    /*
     * aop之业务方法之环绕通知1v5
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint )throws Throwable {
        Object result = null;
        System.out.println("环绕通知方法之前执行类似beforeMethod......");
        try{
            //此方法类似method.invoke(),不执行的话原方法就不会执行,所以可以加前置后置通知,也可以控制原有方法是否执行
            result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        }catch (Exception e){
            System.out.println("环绕通知方法之后执行类似exceptionMethod......");

        }  finally {
            System.out.println("环绕通知方法之后执行类似afterMethod......");
        }
        System.out.println("环绕通知方法成功之后执行类似successMethod......");
        return result;
    }
}
  1. xml核心配置
 <!--
在Spring的配置⽂件中加⼊aop的约束 xmlns: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
">
<!-- Spring基于XML的AOP配置步骤
第⼀步:把通知Bean交给Spring管理
第⼆步:使⽤aop:config开始aop的配置
第三步:使⽤aop:aspect配置切⾯
第四步:使⽤对应的标签配置通知的类型-->
  
<!--进行aop相关的xml配置,配置aop的过程其实就是把aop相关术语落地-->
    <!--配置横切逻辑bean-->
    <bean id="aopLogUtils" class="com.lagou.edu.utils.AopLogUtils"></bean>
    <!--使用aop config 标签表明aop开始配置,在内部配置切面aspect-->
    <!--aspect = 切入点(锁定方法) + 方位点(锁定方法中的特殊时机) +横切逻辑-->
    <aop:config>
        <aop:aspect id="logAspect" ref="aopLogUtils">
            <!--切入点锁定我们感兴趣的方法,使用aspectj语法表达式 -->
            <aop:pointcut id="pt1" expression="execution(public void  com.lagou.edu.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int))"/>
            <!--方位点 pointcut-ref引用切入点,pointcut则是直接指定具的方法中表达式内容-->
            <aop:before method="beforeMethod" pointcut-ref="pt1"/>
            <aop:after method="afterMethod" pointcut-ref="pt1"  />
            <aop:after-returning method="successMethod" pointcut-ref="pt1"/>
            <aop:after-throwing method="exceptionMethod" pointcut-ref="pt1"/>
            <!--环绕通知1v5尽量不要和其它方法一起执行-->
            <aop:around method="aroundMethod" pointcut-ref="pt1"/>
        </aop:aspect>
    </aop:config>
  1. 测试代码
/**
     * 测试bean的aop xml配置
     */
    @Test
    public void testAopXml() throws Exception {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        TransferService transferService = (TransferService) applicationContext.getBean("transferService");
        transferService.transfer("6029621011000","6029621011001",100);

    }
  1. 测试结果

aop之业务方法之前执行…获取参数0为6029621011000
环绕通知方法之前执行类似beforeMethod…

执行转账业务逻辑

环绕通知方法之后执行类似afterMethod…
环绕通知方法成功之后执行类似successMethod…

aop之业务方法之成功时执行…
aop之业务方法之后执行(无论异常与否)…

  1. 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.Account)
包名可以使⽤..表示当前包及其⼦包
* ..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
* ...(com.lagou.pojo.Account)
参数列表,可以使⽤具体类型
基本类型直接写类型名称 : int
引⽤类型必须写全限定类名:java.lang.String
参数列表可以使⽤*,表示任意参数类型,但是必须有参数
* *..*.*(*)
参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
* *..*.*(..)
全通配⽅式:
* *..*.*(..)

举个栗子从单个方法到全部拦截

拦截表达式
<aop:pointcut id="pt1" expression="execution(public void  com.lagou.edu.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int))"/>
一级变换每一个包、类、方法、返回值都变成*替换
<aop:pointcut id="pt1" expression="execution(* *.*.*.*.*.*.*(java.lang.String,java.lang.String,int))"/> 
二级变换所有的包变成*.. 类变成*.
<aop:pointcut id="pt1" expression="execution(* *..*.*(java.lang.String,java.lang.String,int))"/>  
三级变换参数变成*,必须有参数的 
<aop:pointcut id="pt1" expression="execution(* *..*.*(*))"/>  
四级变换,参数可有可无的,不建议这样配哟
<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"/> 
  1. 改变代理⽅式的配置

在前⾯我们已经说了,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>

5. AOP的具体使用注解模式下

1.依赖同上
2.切面类配置信息
/**
 * aop横切面注解模式
 * @author lane
 * @date 2021年03月28日 下午1:23
 */
@Component
@Aspect
//@EnableAspectJAutoProxy //纯注解模式下在配置类上加上这个,只是在这做示范
public class AopLogUtilsAnno {
    //指定切入点
    @Pointcut("execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))")
    public void pt1(){

    }
    /*
     * aop之业务方法之前执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @Before("pt1()")
    public void beforeMethod(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println("aop之业务方法之前执行......获取参数0为"+args[0]);
    }

    /*
     * aop之业务方法之后执行(无论异常与否)
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @After("pt1()")
    public void afterMethod(){
        System.out.println("aop之业务方法之后执行(无论异常与否)......");
    }

    /*
     * aop之业务方法异常时执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @AfterThrowing("pt1()")
    public void exceptionMethod(){
        System.out.println("aop之业务方法之异常时执行......");
    }
    /*
     * aop之业务方法成功时执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @AfterReturning("pt1()")
    public void successMethod(){
        System.out.println("aop之业务方法之成功时执行......");
    }
    /*
     * aop之业务方法之环绕通知1v5
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @Around("pt1()")
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint )throws Throwable {
        Object result = null;
        System.out.println("环绕通知方法之前执行类似beforeMethod......");
        try{
            //此方法类似method.invoke(),不执行的话原方法就不会执行,所以可以加前置后置通知,也可以控制原有方法是否执行
            result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        }catch (Exception e){
            System.out.println("环绕通知方法之后执行类似exceptionMethod......");

        }  finally {
            System.out.println("环绕通知方法之后执行类似afterMethod......");
        }
        System.out.println("环绕通知方法成功之后执行类似successMethod......");
        return result;
    }
}

3. xml配置
  • 有xml模式下的配置
<!--开启aop注解驱动,proxy-target-class = true则强制使用cglib动态代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
<!--开启aop注解驱动spring自己选择动态代理-->
    <aop:aspectj-autoproxy/>
  • 纯注解模式下开起aop注解驱动需要在配置类上加如下注解

    @EnableAspectJAutoProxy

4. 测试类
 /**
     * 测试bean的aop anno配置
     */
    @Test
    public void testAopAnno() throws Exception {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        TransferService transferService = (TransferService) applicationContext.getBean("transferService");
        transferService.transfer("6029621011000","6029621011001",100);

    }
5. 测试结果

环绕通知方法之前执行类似beforeMethod…
aop之业务方法之前执行…获取参数0为6029621011000

执行转账业务逻辑

环绕通知方法之后执行类似afterMethod…
环绕通知方法成功之后执行类似successMethod…
aop之业务方法之后执行(无论异常与否)…
aop之业务方法之成功时执行…

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值