Spring核心——AOP总结

本文详细介绍了Spring AOP的概念,包括面向切面编程的定义、AOP术语解析,如横切关注点、切面、通知等。通过实例展示了Spring使用AOP的步骤,包括配置文件、切面类和目标类的创建,并详细解释了切入点表达式和不同类型的通知(前置、后置、返回、异常和环绕)。此外,还涵盖了重用切入点表达式、设置切面优先级以及基于XML的AOP配置方式。
摘要由CSDN通过智能技术生成

AOP概述

  • AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理。
  • AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • 面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。

1)AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。是一种通过动态代理实现程序功能扩展和统一维护的一种技术。

2)利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

3)AOP编程操作的主要对象是切面(aspect),而切面用于模块化横切关注点(公共功能)。

AOP术语

  1. 横切关注点 :
    • 从每个方法中抽取出来的同一类非核心业务
  2. 切面(Aspect):
    • 封装横切关注点信息的
  3. 通知(Advice):
    • 存放横切关注点的方法
  4. 目标(Target):
    • 被通知的对象
  5. 代理(Proxy):
    • 先目标对象应用通知之后创建的代理对象
  6. 连接点(Joinpoint):
    • 横切关注点在程序代码中的具体位置
  7. 切入点(pointcut):
    • 切入点指声明的一个或多个连接点的集合

Spring使用AOP的步骤

1.引入依赖

<!--spirng-aspects的jar包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.23</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

2.创建Spring的配置文件,扫描包和开启AspectJ注解支持

<?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:context="http://www.springframework.org/schema/context"
       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/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">

    <context:component-scan base-package="com.zyd"/>

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

3.创建切面类,编写通知方法

package com.zyd.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @author : ZYD
 * @date : 2022/10/24 19:21
 * @description
 */
@Component
@Aspect
public class LoggingAspect {

//切面表达式下文会详细介绍
	//通知(advice)
    @Before(value = "execution(public int com.zyd.service.*.*(..))")
    public void beFore() {
        System.out.println("前置通知执行了");
    }
	通知(advice)
    @After(value = "execution(public int com.zyd.service.*.*(..))")
    public void after() {
        System.out.println("后置通知执行了");
    }
}

4.创建目标类

package com.zyd.service;

import org.springframework.stereotype.Service;

/**
 * @author : ZYD
 * @date : 2022/10/24 19:26
 * @description
 */
@Service
public class Calculator {

    public int add(int a, int b) {
        System.out.println("目标方法执行了");
        return a + b;
    }
}

5.创建测试类,查看AOP效果

package com.zyd;

import com.zyd.service.Calculator;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author : ZYD
 * @date : 2022/10/24 19:28
 * @description
 */
public class AopTest {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("SpringAop.xml");

    @Test
    public void test01() {
        Calculator calculator = ioc.getBean(Calculator.class);
        int add = calculator.add(1, 2);

    }
}

在这里插入图片描述

切入点表达式

1.作用

  • 通过表达式的方式定位一个或多个具体的连接点

2.语法格式

  • execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
    在这里插入图片描述
   public int com.zyd.service.Calculator.add(int,int))   目标方法是:com.zyd.service.Calculator中的add方法
   public int com.zyd.service.Calculator.*(int,int))     目标方法是com.zyd.service.Calculator中的所有
   public int Calculator.*(int,int))         目标方法是和切面同包的Calculator下的所有方法
   public int *.*(int,int))                  目标方法是任何包任何类中的任何方法
   public int Calculator.*(int,int)        目标方法是和切面同包的Calculator下所有的方法,参数必须是intint
   public int Calculator.*(String,int,int)   和切面同包的Calculator下所有的方法,参数必须是Stringintint
   public int Calculator.*(..)       和切面同包的Calculator下所有的方法,参数必须是任意
   public int Calculator.*(String,..)   切面同包的Calculator下所有的方法,参数必须是以String开始,后面无所谓
   public int Calculator.*(..,double)   和切面同包的Calculator下所有的方法,参数必须是以double结尾,前面无所谓
   public int Calculator.add(..)         和切面同包的Calculator下所有的add方法,参数任意,返回值是int
   public * *.Calculator.*(..)
   * Calculator.add(..)          和切面同包的Calculator下所有的add方法,参数任意,返回值是任意
   * *..*(..)                    所有类的所有方法,参数任意,返回值任意
   
   //在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来
   Calculator中的所有add方法和所有sub方法  (参数任意。返回值任意)
        execution(* Calculator.add(..)) || execution(* Calculator.sub(..))
   所有类的所有方法,排除Calculator中的add方法以外的其他所有
        !execution(* Calculator.add(..))
   Calculator中除add方法以外的其他所有
        execution(* Calculator.*(..)) && !execution( * Calculator.add(..))

通知细节

1.前置通知

使用@Before注解标识
在方法执行之前执行的通知。

@Before(value = "execution(* *..*(..))")
    public void beFore(JoinPoint joinPoint) {
    	//获取方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName);
        //获取参数
        Object[] args = joinPoint.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("前置通知执行了");
    }

2.后置通知

使用@After注解标识
后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候。

@After(value = "execution(public int com.zyd.service.*.*(..))")
    public void after(JoinPoint joinPoint) {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName);
        //获取参数
        Object[] args = joinPoint.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("后置通知执行了");
    }

3.返回通知

使用@AfterReturning注解标识(连接点方法抛异常不执行)
无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。

  • 在返回通知中,通过@AfterReturning注解的returning属性获取连接点的返回值。该属性的值即为用来传入返回值的参数名称
  • 必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值
  • 原始的切点表达式需要出现在pointcut属性中
//返回通知
    @AfterReturning(pointcut = "execution(public int com.zyd.service.*.*(..))",returning = "result")
    public void returningAdvice(JoinPoint joinPoint , Object result) {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //获取参数
        Object[] args = joinPoint.getArgs();
        System.out.println("Logging: The method " + methodName + " returns " + result);
        System.out.println("返回通知");
    }

4.异常通知

使用@AfterThrowing注解标识

在异常通知中,通过@AfterThrowing注解的throwing属性获取异常信息。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。

如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行。

//异常通知
@AfterThrowing(pointcut = "execution(* *..*(..))",throwing = "e")
public void throwingAdvice(JoinPoint joinPoint,Throwable e){
    //获取方法名
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logging: The method "+methodName+" occurs "+e);
}

5.环绕通知

通过@Around注解标识

环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。

对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。

在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。

注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed()的返回值,否则会出现异常

//环绕通知
@Around("execution(* *..*(..))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
    //获取方法名
    String methodName = proceedingJoinPoint.getSignature().getName();
    //获取参数
    Object[] args = proceedingJoinPoint.getArgs();
    Object result=null;
    try {
        //前置通知
        System.out.println("★Logging: The method "+methodName+" begins with "+ Arrays.toString(args));
        //执行目标方法
        result = proceedingJoinPoint.proceed();
        //返回通知
        System.out.println("★Logging: The method "+methodName+" returns "+result);
    } catch (Throwable throwable) {
        //异常通知
        System.out.println("★Logging: The method "+methodName+" occurs "+throwable);
        throwable.printStackTrace();
    }finally {
        //后置通知
        System.out.println("★Logging: The method "+methodName+" ends");
    }
    return result;
}

重(chong)用切入点表达式

@Pointcut(value = "execution(* *..*(..))")
    public void pointcut() {}

	//实现重用
    @Before(value = "pointcut()")
    public void beFore(JoinPoint joinPoint) {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName);
        //获取参数
        Object[] args = joinPoint.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("前置通知执行了");
    }

设置切面的优先级

在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。

切面的优先级可以通过实现Ordered接口或利用@Order注解指定。

实现Ordered接口,getOrder()方法的返回值越小,优先级越高。

若使用@Order注解,通过注解的value属性指定优先级,值越小优先级越高。

在这里插入图片描述

基于XML的方式实现AOP

除了使用AspectJ注解声明切面,Spring也支持在bean配置文件中声明切面。这种声明是通过aop名称空间中的XML元素完成的。

正常情况下,基于注解的声明要优先于基于XML的声明。通过AspectJ注解,切面可以与AspectJ兼容,而基于XML的配置则是Spring专有的。由于AspectJ得到越来越多的 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:context="http://www.springframework.org/schema/context"
       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/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">

<!--    <context:component-scan base-package="com.zyd"/>-->

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

    <!--配置计算器实现类-->
    <bean id="calculator" class="com.zyd.service.Calculator"></bean>

    <!--配置切面类-->
    <bean id="loggingAspect" class="com.zyd.aop.LoggingAspect"></bean>

    <!--AOP配置-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pointCut"
                      expression="execution(* com.zyd.service.Calculator.*(..))"/>
        <!--配置切面-->
        <aop:aspect ref="loggingAspect">
            <!--前置通知-->
            <aop:before method="beFore" pointcut-ref="pointCut"></aop:before>
            <!--返回通知-->
            <aop:after-returning method="returningAdvice" pointcut-ref="pointCut" returning="result"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="throwingAdvice" pointcut-ref="pointCut" throwing="e"></aop:after-throwing>
            <!--后置通知-->
            <aop:after method="after" pointcut-ref="pointCut"></aop:after>
            <!--环绕通知-->
            <aop:around method="aroundAdvice" pointcut-ref="pointCut"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值