• Spring AOP


    三联支持 一起鼓励 一起进步

    Spring AOP



    一、基于xml的aop配置示例

    AOP的相关概念

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

    AOP是OOP的延续,是软件开发的一个热点,也是Spring框架的一个重要内容,是函数式编程的一种衍生类型。

    简单的说,就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对我们的方法进行增强。

    在Spring中,框架会根据目标是否实现了接口来决定采用哪种动态代理方式。

    Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
    
    Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。 (即被代理类中的所有方法都是连接点,但只有真正被代理增强的方法才是切入点)。
    
    Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知的类型:前置通知、后置通知、异常通知、最终通知、环绕通知。
    
    Introduction(引介):引介是一种特殊的通知,在不修改类代码的前提下, Introduction可为在运行期为类动态的添加一些方法或Field。
    
    Target(目标对象):代理的目标对象。
    
    Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
    
    Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
    
    Aspect(切面):是切入点和通知(引介)的结合。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    xml开启注解aop支持:

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    • 1

    1.引入需要的pom依赖

    <!-- spring核心包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <!-- 切入点表达式解析依赖 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.编写bean.xml配置文件(需要引入xmlns:aop约束)

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean id="accountService" class="com.study.service.impl.AccountServiceImpl">
        </bean>
        
        <bean id="logger" class="com.study.util.Logger">
        </bean>
        
        <aop:config>
            <aop:aspect id="logAdvice" ref="logger" >
                <aop:before method="printLog" 
                            pointcut="execution(public void com.study.service.impl.AccountServiceImpl.saveAccount())">
                </aop:before>
            </aop:aspect>
        </aop:config>
    
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3**.配置aop的步骤:**

    1. 被代理的类的bean:accountService
    2. 通知的bean:logger
    3. 使用aon:config标签表明开始aop的配置
    4. 使用aop:aspect标签表明开始配置切面
      ○ id属性:给切面提供一个唯一的标识
      ○ ref属性:指定通知类bean的id
    5. 在aop:aspect标签的内部使用对应标签来配置通知的类型
      aop:before标签:配置前置通知
      ○ method属性:用于指定Logger类中哪个方法是前置通知
      ○ pointcut属性:用于指定切入点表达式,该表达式含义是对业务层哪些方法进行增强

    切入点表达式的写法:

    ● 关键字:execution(表达式)
    ● 表达式:
    ○ 完整写法:访问修饰符 返回值 包名.包名.包名…包名.类名.方法名(参数列表)
    public void com.study.service.impl.AccountServiceImpl.SaveAccount()

    ○ 参数列表
    基本类型直接写名称:int

    引用类型写包名.类名: java.lang.String

    public void com.study.service.impl.AccountServiceImpl.SaveAccount(int)

    全通配写法:* .*(…)

    全通配写法由来:

    ● 访问修饰符可以省略
    void com.study.service.impl.AccountServiceImpl.SaveAccount()

    ● 返回值可以使用通配符* 表示任意类型返回值

    • com.study.service.impl.AccountServiceImpl.SaveAccount()

    ● 包名可以使用通配符表示任意包。但是有几级包,就需要写几个

    • ....AccountServiceImpl.SaveAccount()

    ● 包名可以使用…表示当前包及其子包

    • *…AccountServiceImpl.SaveAccount()

    ● 类名可以使用通配符*表示任意类

    • .SaveAccount()

    ● 方法名可以使用通配符*表示任意方法

    • .*()

    ● 参数列表可以使用通配符*表示任意类型参数,但是必须有参数

    • .()

    ● 参数列表可以使用通配符…表示有无参数均可,有参数可以是任意类型

    • .*(…)

    ● 全通配写法:* .*(…)

    4**.spring四种通知(不含环绕通知)的xml配置**

    四种通知对应的xml标签:

    ● 前置通知:aop:before
    在切入点方法执行之前执行

    ● 后置通知:aop:after-returning
    在切入点方法正常执行之后执行。

    ● 异常通知:aop:after-throwing
    在切入点方法执行产生异常之后执行。

    ● 最终通知:aop:after
    无论切入点方法是否正常执行,它都会在其后面执行。

    每次执行时,后置通知和异常通知永远只能执行一个。

    <aop:config>
            <aop:aspect id="logAdvice" ref="logger" >
                <!-- 前置通知: 在切入点方法执行之前执行 -->
                <aop:before 
                            method="beforePrintLog"
                            pointcut="execution(public void com.study.service.impl.AccountServiceImpl.saveAccount())">
                </aop:before>
                <!-- 后置通知: 在切入点方法正常执行之后执行。(每次执行时,后置通知和异常通知永远只能执行一个) -->
                <aop:after-returning 
                                     method="afterReturningPrintLog" 
                                     pointcut="execution(public void com.study.service.impl.AccountServiceImpl.saveAccount())">
                </aop:after-returning>
                <!-- 异常通知:在切入点方法执行产生异常之后执行 -->
                <aop:after-throwing 
                                    method="afterThrowingPrintLog" 
                                    pointcut="execution(public void com.study.service.impl.AccountServiceImpl.saveAccount())">
                </aop:after-throwing>
                <!-- 最终通知: 无论切入点方法是否正常执行它都会在其后面执行 -->
                <aop:after 
                           method="afterPrintLog" 
                           pointcut="execution(public void com.study.service.impl.AccountServiceImpl.saveAccount())">
                </aop:after>
            </aop:aspect>
        </aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    二、基于注解的AOP配置

    底层:动态代理

    加入依赖:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>4.3.12.RELEASE</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    
    
    • 1

    ● @Aspect
    声明当前类是一个切面类。

    另外,切面类还需使用@Component注解声明是一个bean

    切入点注解:

    ● @Pointcut(“execution(表达式)”)
    作用于方法上。

    通知的注解:

    ● @Before(“切入点方法名()”)
    前置通知。

    ● @AfterReturning(“切入点方法名()”)
    后置通知

    ● @AfterThrowing(“切入点方法名()”)
    异常通知

    ● @After(“切入点方法名()”)
    最终通知

    ● @Around(“切入点方法名()”)
    环绕通知

    使用@Pointcut注解,抽取公共的切入点表达式。
    切面类上使用@Aspect注解,声明该类为切面类。

    示例:

    @Aspect
    public class LogAspect {
    
        @Pointcut("execution(public int com.study.aop.MathCalculator.*(..))")
        public void pointCut() {}
    
        @Before("pointCut()")
        public void logStart() {
            System.out.println("除法运行了...参数列表: ");
        }
    
        @After("pointCut()")
        public void logEnd() {
            System.out.println("除法结束了");
        }
    
        @AfterReturning("pointCut()")
        public void logReturn() {
            System.out.println("除法正常返回...返回值:");
        }
    
        @AfterThrowing("pointCut()")
        public void logException() {
            System.out.println("除法出现异常...异常信息:");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    将切面类和要切入的类都加入ioc容器,并且使用@EnableAspectJAutoProxy在配置类中开启Aspectj自动代理:

    @Configuration
    @EnableAspectJAutoProxy
    public class MainConfigOfCalculator {
        @Bean
        public MathCalculator mathCalculator() {
            return new MathCalculator();
        }
    
        @Bean
        public LogAspect logAspect() {
            return new LogAspect();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    如果直接自己创建MathCalculator对象,则运行的方法依然是该对象没有被代理的方法。只有从ioc容器中获取的MathCalculator对象才是被动态代理的对象。

    在通知方法中获取被代理的方法的信息:

    ● 带有通知注解的方法,都可以加入一个JoinPoint类型的参数,可以通过该参数获取当前代理的方法名称、方法参数列表等信息。
    示例:

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] methodArgs = joinPoint.getArgs();
        System.out.println(methodName + "方法运行了...参数列表: " + Arrays.toString(methodArgs));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ● @AfterReturning注解带有returning属性,可以在方法的参数中定义一个代理方法的返回结果参数,将该参数名指定到@AfterReturning的returning属性上,方法运行的结果便会赋值给该参数。
    例如:

    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {   // 如果想在使用result的同时,也使用joinPoint,必须将joinPoint参数放在参数的第一位上。
        System.out.println("除法正常返回...返回值:" + result);
    }
    
    • 1
    • 2
    • 3
    • 4

    ● @AfterThrowing注解带有throwing属性,可以在方法的参数中定义一个异常参数,将该参数名指定到@AfterThrowing的throwing属性上,便可以通过该参数获取方法运行中出现的异常。
    示例:

    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void logException(Exception exception) {
        System.out.println("除法出现异常...异常信息:" + exception.getMessage());
    }
    
    • 1
    • 2
    • 3
    • 4

    Spring5.0.2版本,使用注解方式配置aop时,后置通知/异常通知 会在 最终通知 执行之后才执行。

    三、Spring AOP 切点(pointcut)表达式’

    参考:https://blog.csdn.net/qq_40741855/article/details/104545165

    三、推荐博文
    https://blog.csdn.net/eclipse1024/article/details/127266980
    https://blog.csdn.net/vbniepwq/article/details/115707096

  • 相关阅读:
    Docker添加软链接,解决c盘占用问题
    【Vue3】组件递归
    DAC8563数模转换模块的使用介绍
    月是故乡明,每逢佳节倍思亲,近乡情更怯
    Flutter快学快用17 打包发布:Flutter 应用,你离线上运营只差最后一步
    短链接网站系统设计与实践
    Springboot面向个性化推荐的教务管理系统720em计算机毕业设计-课程设计-期末作业-毕设程序代做
    C语言百日刷题第九天
    CV5200自组网远程WiFi模组,无人机无线图传应用,高清低时延方案
    ES6基础知识
  • 原文地址:https://blog.csdn.net/weixin_44214857/article/details/131145568