快速搭建AOP环境

一、什么是AOP?

AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

优势:利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。
作用:把重复功能(日志记录,性能统计,安全控制,事务处理,异常处理等)抽取出来,需要使用这些功能时,利用动态代理技术,在不修改源码情况下对业务逻辑进行增强。

比如,在《Spring实战(第4版)》中有如下一张图描述了AOP的大体模型。

在这里插入图片描述

切面实现了横切关注点(跨多个应用对象的逻辑)的模块化

从这张图中,我们可以看出:所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。

总之一句话:AOP是指在程序的运行期间动态的将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。

二、搭建AOP环境

使用AOP步骤:
1.将切面类和业务逻辑类(目标方法所在类)都加入到容器中,并且要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)。
2.在切面类上的每个通知方法上标注通知注解,告诉Spring何时何地运行(通过切入点表达式)。
3.开启基于注解的AOP模式,即加上@EnableAspectJAutoProxy注解,这是最关键的一点。

1. 导入AOP依赖

要想搭建AOP环境,首先,我们就需要在项目的pom.xml文件中引入AOP的依赖,如下所示:

<properties>
    <spring.version>5.2.6.RELEASE</spring.version>
</properties>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.version}</version>
</dependency>

2.定义目标类

创建一个MathHandler类,用于处理数学计算上的一些逻辑。比如,我们在MathHandler类中定义了一个加法操作,返回两个整数类型值的和,如下所示。

public class MathHandler {
    
    

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

3.定义切面类

创建一个LogAspect切面类,在LogAspect类中定义了几个打印日志的方法,以这些方法来感知MathHandler类中的add()方法的运行情况。如果需要切面类来感知目标类方法的运行情况,则需要使用Spring AOP中的通知方法。

AOP中的通知方法及其注解与含义如下:

  • 前置通知(@Before):在目标方法运行之前运行。
  • 后置通知(@After):在目标方法运行结束之后运行,不管是正常结束还是异常结束都会执行。
  • 返回通知(@AfterReturning):在目标方法正常返回之后运行。
  • 异常通知(@AfterThrowing):在目标方法抛出异常后运行。
  • 环绕通知(@Around):动态代理,手动推进目标方法运行。

综上,LogAspect类中的具体方法定义如下所示:

@Aspect
public class LogAspect {
    
    

    @Pointcut("execution(public int io.mykit.spring.plugins.register.aop.MathHandler.*(..))")
    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("加法异常,异常信息:{}");
    }
}
  • logStart()方法:MathHandler类的add()方法运行之前运行。
  • logEnd()方法:MathHandler类的add()方法运行结束之后运行。
  • logReturn()方法:MathHandler类的add()方法正常返回之后运行。
  • logException()方法:MathHandler类的add()方法抛出异常后执行。

4.将目标类和切面类加入到IOC容器

新建AopConfig类,并使用@Configuration注解标注这是一个Spring的配置类,同时使用@EnableAspectJAutoProxy注解开启基于注解的AOP模式。在AopConfig类中,使用@Bean注解将MathHandler类和LogAspect类加入到IOC容器中,如下所示:

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
    
    
    
    @Bean
    public MathHandler mathHandler(){
    
    
        return new MathHandler();
    }

    @Bean
    public LogAspect logAspect(){
    
    
        return new LogAspect();
    }
}

5.创建测试类

创建AopTest测试类,并在AopTest类中创建testAop01()方法,如下所示:

public class AopTest {
    
    

    @Test
    public void testAop01(){
    
    
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);

		// 不要自己创建这个对象,自己创建的对象不是从容器中获取的,不会被切面切到
        // MathHandler mathHandler = new MathHandler();
        // mathHandler.div(1, 1);
        
        MathHandler mathHandler = context.getBean(MathHandler.class);
        mathHandler.add(1, 2);
        context.close();
    }
}

运行AopTest类中的testAop01()方法,输出的结果信息如下所示:

加法运行开始,参数列表是:{
    
    }
目标方法执行
加法运行结束
加法正常返回,运行结果:{
    
    }

可以看到,执行了切面类中的方法,并打印出了相关信息。但是没有打印参数列表和运行结果。

6.在切面类中打印参数列表和返回结果

要想打印出参数列表和运行结果,就需要对LogAspect类中的方法进行优化,优化后的结果如下所示:

@Aspect
public class LogAspect {
    
    

	// pointCut()方法是抽取出来的一个公共的切入点表达式,方法名随便写,方法体不用写。
    @Pointcut("execution(public int io.mykit.spring.plugins.register.aop.MathHandler.*(..))")
    public void pointCut(){
    
    

    }

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
    
    
    	// joinPoint.getSignature().getName()是目标方法名
        System.out.println(joinPoint.getSignature().getName() + " 运行开始,参数列表是:{"+ Arrays.asList(joinPoint.getArgs()) +"}");
    }

    @After("pointCut()")
    public void logEnd(JoinPoint joinPoint){
    
    
        System.out.println(joinPoint.getSignature().getName() + " 运行结束");
    }
    
	// returning指定该方法的哪个参数来封装返回值,即"result"对应Object result
    @AfterReturning(value = "pointCut()", returning = "result")
     // 一定要注意:JoinPoint这个参数一定不能写到后面,它必须出现在参数列表的第一位,否则Spring也是无法识别的,就会报错
    public void logReturn(JoinPoint joinPoint, Object result){
    
    
        System.out.println(joinPoint.getSignature().getName() + " 正常返回,运行结果:{"+result+"}");
    }

    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void logException(JoinPoint joinPoint, Exception exception){
    
    
        System.out.println(joinPoint.getSignature().getName() + " 异常,异常信息:{"+exception+"}");
    }
}

这里,需要注意的是:JoinPoint参数一定要放在参数的第一位。

此时,我们再次运行AopTest类中的testAop01()方法,输出的结果信息如下所示:

add 运行开始,参数列表是:{
    
    [1, 2]}
目标方法执行
add 运行结束
add 正常返回,运行结果:{
    
    3}

7.目标方法抛出异常

我们在MathHandler类的add()方法中抛出一个异常,来测试下异常情况,如下所示:

public class MathHandler {
    
    

    public int add(int i, int j){
    
    
        System.out.println("目标方法执行");
        throw new RuntimeException();
       //return i + j;
    }
}

此时,我们再次运行AopTest类中的testAop01()方法,输出的结果信息如下所示:

add 运行开始,参数列表是:{
    
    [1, 2]}
目标方法执行
add 运行结束
add 异常,异常信息:{
    
    java.lang.RuntimeException}

可以看到,正确的输出了切面中打印的信息。至此,我们的AOP测试环境就搭建成功了。

猜你喜欢

转载自blog.csdn.net/qq_36602071/article/details/130001542
今日推荐