八、Spring AOP(两种配置方式实现)

一、Spring AOP

1.1 AOP概述

  1. 定义

AOP(Aspect Oriented Programming)面向切面编程,通过预编译和运行期动态代理的方式,实现了程序各层级业务逻辑的隔离,降低了程序的耦合性,提高了程序开发的效率

  1. 作用

在程序运行期间,通过动态代理的方式不改变源码实现对方法的增强

  1. 优势
  1. 降低了代码的耦合性
  2. 提高了开发效率
  3. 方便维护

1.2 相关术语

  1. 连接点 Joinpoint

被拦截到的点;spring中指方法,因为spring只支持方法的拦截

  1. 切入点 Pointcut

进行了增强的连接点

  1. 通知 Advice

在切入点进行的所有增强的操作
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知
在这里插入图片描述

  1. 切面 Aspect

切入点和通知的结合

  1. 目标对象 Target

被代理的对象

  1. 代理对象 Proxy

增强后的对象

  1. 织入 Weaving

把切面应用到目标对象产生增强后的代理对象的过程
注意:spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入

  1. 引入

二、XML配置实现

  1. xml配置步骤
  1. 使用aop:config标签开始spring aop的配置
  2. 使用aop:aspect标签开始配置切面
  • id: 切面ID
  • ref: 切面处理Bean
  1. 使用aop:aspect等子标签配置通知类型
  • method: 通知处理的具体方法
  • pointcut: 切入点表达式,用于描述哪些连接点可以作为切入点
  1. 切入点表达式
  • 关键字:execution
  • 表达式:访问修饰符 返回值 类限定名.方法名(参数列表)
  • 标准写法:public void com.lizza.service.UserService.update(int)
  • 访问修饰符可以省略:void com.lizza.service.UserService.update(int)
  • 返回值可以使用通配符:* com.lizza.service.UserService.update(int)
  • 包名可以使用通配符,有几级包写几个*:* ..*.UserService.update(int)
  • 包名使用…表示当前包及其子包:* com.lizza…update(int)
  • 类名和方法名都可以使用通配符:* com.lizza…*()
  • 参数列表:* com.lizza…*(…)
  • 基本类型:直接写名称,如int,char,double
  • 应用类型:全限定名.类名,如:java.lang.String
  • *:表示必须有参数
  • …:表示有无参数均可
  • 全通配形式:* .*(…)
  • beans.xml
<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.lizza.service.UserService"></bean>

    <bean id="log" class="com.lizza.util.Log"></bean>

    <!--
        Spring中配置AOP的步骤
        1. 使用<aop:config>标签开始spring aop的配置
        2. 使用<aop:aspect>标签开始配置切面
            id: 切面ID
            ref: 切面处理Bean
        3. 使用<aop:aspect>等子标签配置通知类型
            method: 通知处理的具体方法
            pointcut: 切入点表达式,用于描述哪些连接点可以作为切入点
        4. 切入点表达式
            关键字:execution
            表达式:访问修饰符 返回值 类限定名.方法名(参数列表)
            标准写法:public void com.lizza.service.UserService.update(int)
            访问修饰符可以省略:void com.lizza.service.UserService.update(int)
            返回值可以使用通配符:* com.lizza.service.UserService.update(int)
            包名可以使用通配符,有几级包写几个*:* *.*.*.UserService.update(int)
            包名使用..表示当前包及其子包:* com.lizza..update(int)
            类名和方法名都可以使用通配符:* com.lizza..*()
            参数列表:* com.lizza..*(..)
                基本类型:直接写名称,如int,char,double
                应用类型:全限定名.类名,如:java.lang.String
                *:表示必须有参数
                ..:表示有无参数均可
            全通配形式:* *..*.*(..)
    -->
    
    <aop:config>
        <aop:aspect id="logAdvice" ref="log">
            <aop:before method="log" pointcut="execution(* com.lizza..*())"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

三、注解配置实现

3.1 关键注解

  • @Aspect:指定切面类
  • @Component:将切面类作为Spring容器的bean交由Spring容器管理
  • @Pointcut:指定切入点
  • @Before:前置通知
  • @AfterReturning:后置通知
  • @AfterThrowing:异常通知
  • @After:最终通知
  • @Around:环绕通知
  • @EnableAspectJAutoProxy:开启aop

3.2 使用步骤

  1. 使用@EnableAspectJAutoProxy注解开启AOP支持
  2. 编写切面,使用@Component注解和@Aspect注解来指定切面类
  3. 使用@Pointcut注解指定切入点,并且编写execution(* com.lizza..*())表达式来指定要切入的包
  4. 使用@Before@AfterReturning@AfterThrowing@After@Around注解来指定前置通知,后置通知,异常通知,最终通知,环绕通知

3.3 示例代码

  • 配置切面
@Aspect
@Component
public class Log {

    @Pointcut("execution(* com.lizza..*())")
    public void pointCut() {}

    /**
     * 前置通知:切入点执行之前执行
     */
    @Before("pointCut()")
    public void beforeLog() {
        System.out.println("前置通知:记录日志...");
    }

    /**
     * 后置通知:切入点执行之后执行
     */
    @AfterReturning("pointCut()")
    public void afterLog() {
        System.out.println("后置通知:记录日志...");
    }

    /**
     * 异常通知:切入点执行发生异常时执行
     */
    @AfterThrowing("pointCut()")
    public void exceptionLog() {
        System.out.println("异常通知:记录日志...");
    }

    /**
     * 最终通知:无论切入点执行发生异常与否,都会执行
     */
    @After("pointCut()")
    public void finalLog() {
        System.out.println("最终通知:记录日志...");
    }

    /**
     * 环绕通知
     */
    @Around("pointCut()")
    public Object aroundLog(ProceedingJoinPoint point) {
        Object result;
        try {
            System.out.println("环绕通知-前置:记录日志...");
            Object[] args = point.getArgs();            // 获取方法执行的参数
            result = point.proceed(args);               // 执行切入点方法
            System.out.println("环绕通知-后置:记录日志...");
            return result;
        } catch (Throwable t) {
            System.out.println("环绕通知-异常:记录日志...");
            throw new RuntimeException(t);
        } finally {
            System.out.println("环绕通知-最终:记录日志...");
        }
    }
}

  • 开启aop
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.lizza")
public class SpringConfig {

}

3.4 注意问题

  • spring aop注解配置时,最终通知会在异常通知或后置通知之前执行,故实际应用时建议使用环绕通知
  • spring aop环绕通知使用ProceedingJoinPoint来获取切入点的方法,参数

源码地址:https://github.com/KJGManGlory/spring-framework

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章