万隆的笔记 万隆的笔记
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
  • Spring

    • Spring简介
    • Spring核心思想
    • 手写实现IoC和AOP
    • Spring Web与Bean装配
    • Spring Transaction
    • Spring Validation
    • Spring IoC基础应用
    • Spring IoC高级特性
    • Spring IoC源码剖析
    • Spring AOP应用
      • AOP 相关术语
      • Spring中AOP的代理选择
      • Spring中AOP的配置方式
    • Spring AOP声明式事务
    • Spring AOP源码剖析
  • SprinvMVC

  • SpringBoot

  • Spring
  • Spring
2023-02-11
目录

Spring AOP应用

# Spring AOP应用

AOP,Aspect oriented Programming,⾯向切⾯编程。在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、⽇志代码、事务控制代码、性能监控代码等等。

# AOP 相关术语

# 业务主线

在讲解AOP术语之前,我们先来看⼀下下⾯这两张图,针对这些扩展的需求,我们只进⾏分析,在此基础上去进⼀步回顾AOP,不进⾏实现。

aop

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

aop2

# AOP 术语

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

# Spring中AOP的代理选择

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

# Spring中AOP的配置方式

在Spring的AOP配置中,也和IoC配置⼀样,⽀持3类配置⽅式:XML配置、XML+注解组合配置、纯注解配置。

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

# XML模式

Spring是模块化开发的框架,使⽤AOP需要引⼊AOP的jar。

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aop</artifactId>
 <version>5.1.12.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjweaver</artifactId>
 <version>1.9.4</version>
</dependency>

AOP-XML配置:

  • Step1:在Spring的配置⽂件中加⼊aop的约束

    <beans ......
           xmlns:aop="http://www.springframework.org/schema/aop"
           ......
           xsi:schemaLocation="
            ......
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd">
    
  • Step2:把通知Bean交给Spring管理

    public class LogUtils {
    
        public void beforeMethod() {
            System.out.println("业务逻辑开始执行之前执行");
        }
    
        public void afterMethod() {
            System.out.println("业务逻辑执行完毕后执行");
        }
    
        public void exceptionMethod() {
            System.out.println("异常时执行");
        }
    
    }
    
  • Step3:使⽤aop:config开始aop的配置

  • Step4:使⽤aop:aspect配置切⾯

  • Step5:使⽤对应的标签配置通知的类型

⼊⻔案例采⽤前置通知,标签为aop:before

<!--把通知bean交给spring来管理-->
<bean id="logUtils" class="com.example.transfer.utils.LogUtils"/>
<!-- aop配置,就是术语落地-->
<aop:config>
    <!--配置切⾯-->
    <aop:aspect id="logAdvice" ref="logUtils">
        <!--配置切点-->
        <aop:pointcut id="pointcut1" expression="execution(public * com.example.transfer.service.impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String, int))"/>
        <!--配置前置通知-->
        <aop:before method="beforeMethod" pointcut-ref="pointcut1"/>
        <!--<aop:before method="beforeMethod" pointcut="execution(public * com.example.transfer.service.impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String, int))"/>-->
    </aop:aspect>
</aop:config>

# 切⼊点表达式

上述配置实现了对 TransferServiceImpl 的 transfer ⽅法进⾏增强,在其执⾏之前,输出了记录⽇志的语句。这⾥⾯,我们接触了⼀个⽐较陌⽣的名称:切⼊点表达式,它是做什么的呢?

  • 切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分。(AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架 中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。)
  • 切⼊点表达式使⽤示例:使用全限定方法名:访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表),例如:public void com.example.demo.Test(java.lang.String, int), 其中
    • 访问修饰符可以忽略。void com.example.demo.Test(java.lang.String, int)
    • 返回值可以使用 *,标识任意返回值。* com.example.demo.Test(java.lang.String, int)
    • 包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个。* ......Test(java.lang.String, int)
    • 包名也可以使⽤..表示当前包及其⼦包。* ..Test(java.lang.String, int)
    • 类名和⽅法名,都可以使⽤.表示任意类,任意⽅法。* ...(java.lang.String, int)
    • 参数列表,可以使⽤具体类型。基本类型直接写类型名称 : int,引⽤类型必须写全限定类名:java.lang.String。
    • 参数列表也可以使⽤*,表示任意参数类型,但是必须有参数。* *..*.*(*)
    • 参数列表也可以使⽤..,表示有⽆参数均可。有参数可以是任意类型。* *..*.*(..)
    • 全通配⽅式:* *..*.*(..)

# 五种通知类型

以下五种通知类型都只能出现在aop:aspect标签内部,他们有以下配置属性:

  • method:⽤于指定前置通知的⽅法名称
  • pointcut:⽤于指定切⼊点表达式
  • pointcut-ref:⽤于指定切⼊点表达式的引⽤
  1. 前置通知 - aop:before,⽤于配置前置通知。

    <aop:before method="beforeMethod" pointcut-ref="pointcut1"/>
    

    作用 :前置通知永远都会在切⼊点⽅法(业务核⼼⽅法)执⾏之前执⾏,前置通知可以获取切⼊点⽅法的参数,并对其进⾏增强。

    public void beforeMethod(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
        System.out.println("业务逻辑开始执行之前执行");
    }
    
  2. 正常执行时通知 - aop:after-returning.

    该配置returning参数,接收执行的返回值。

    <aop:after-returning method="afterMethod" pointcut-ref="pointcut1" returning="returnValue"/>
    

    作用:⽤于配置切点正常执⾏时通知。可定义接收返回的参数通过属性

  3. 异常通知 - aop:after-throwing

    <aop:after-throwing method="exceptionMethod" pointcut-ref="pointcut1" />
    

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

  4. 最终通知 - aop:after

    <aop:after method="afterMethod" pointcut-ref="pointcut1" />
    

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

  5. 环绕通知 - aop:around

    <aop:around method="aroundMethod" pointcut-ref="pointcut1" />
    

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

    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知中的before method");
        Object result = null;
        try{
            // 控制原有业务逻辑是否执行
             result = proceedingJoinPoint.proceed();
        }catch(Exception e) {
            System.out.println("环绕通知中的exception method");
        }finally {
            System.out.println("环绕通知中的after method");
        }
        return result;
    }
    

# 改变代理⽅式的配置

Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择。

  • 当被代理对象实现了接⼝,则采⽤基于接⼝的动态代理。
  • 当被代理对象没有实现任何接⼝的时候,Spring会⾃动切换到基于⼦类的动态代理⽅式。

但是⽆论被代理对象是否实现接⼝,只要不是final修饰的类都可以采⽤cglib提供的⽅式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的⽅式,实现强制使⽤基于⼦类的动态代理(即cglib的⽅式),该配置的⽅式有两种:

  1. 使⽤aop:config标签配置:

    <aop:config proxy-target-class="true">
    
  2. 使⽤aop:aspectj-autoproxy标签配置:

    <!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP的⽀持-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    

# XML+注解模式

XML 中开启 Spring 对注解 AOP 的⽀持:

<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>

通过注解:@Aspect,@Pointcut,@Before等注解去完成AOP的配置,这些注解对应着XML模式的配置这里不赘述,请看示例:

@Component
@Aspect
public class LogUtils {

    @Pointcut("execution(public * com.example.transfer.service.impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String, int))")
    public void pointcut1(){}

    @Before("pointcut1()")
    public void beforeMethod(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
        System.out.println("业务逻辑开始执行之前执行");
    }

    @AfterReturning(value = "pointcut1()", returning = "returnValue")
    public void afterReturnMethod(Object returnValue) {
        System.out.println("业务逻辑执行完毕后执行");
    }

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

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

    @Around("pointcut1()")
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知中的before method");
        Object result = null;
        try{
            // 控制原有业务逻辑是否执行
             result = proceedingJoinPoint.proceed();
        }catch(Exception e) {
            System.out.println("环绕通知中的exception method");
        }finally {
            System.out.println("环绕通知中的after method");
        }
        return result;
    }
}

# 纯注解模式

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

<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>

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

@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy // 开启spring对注解AOP的⽀持
public class SpringConfiguration {
  
}
上次更新: 11/29/2023, 12:35:02 AM
Spring AOP声明式事务

Spring AOP声明式事务→

最近更新
01
2025
01-15
02
Elasticsearch面试题
07-17
03
Elasticsearch进阶
07-16
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式