📜  Spring Boot – AOP(面向方面的编程)(1)

📅  最后修改于: 2023-12-03 15:20:12.690000             🧑  作者: Mango

Spring Boot – AOP(面向方面的编程)

面向方面的编程(AOP)是一种编程范式,是一种与面向对象编程(OOP)相对应的编程风格。它通过定义一些横跨多个模块的通用逻辑来实现代码重用和模块化。Spring框架中的AOP模块提供了一种优雅的方式来实现AOP编程。

术语解释

在讲解Spring Boot中的AOP之前,先解释下一些相关的术语:

切面(Aspect)

切面是一个关注点的模块化,这个关注点可能会横切一个或多个对象。通俗点来说,切面就是一种与业务无关,横跨多个模块的通用逻辑,例如日志记录、性能统计、权限验证等。

连接点(Joint Point)

被拦截到的点,例如方法、异常、字段等

通知(Advice)

AOP在特定的连接点上执行的操作,它分为以下五种类型:

  1. 前置通知(Before):在目标方法执行前执行
  2. 后置通知(After):在目标方法执行后执行
  3. 后置返回通知(AfterReturning):在目标方法返回后执行
  4. 后置异常通知(AfterThrowing):在目标方法抛出异常后执行
  5. 环绕通知(Around):在目标方法执行前后都执行
切点(Pointcut)

一组连接点的集合,关注的是方法执行,因此我们基本上都是通过定义方法名来规定哪些方法需要被包括在当前的Pointcut中。

引入(Introduction)

一种在不修改类代码的前提下,为类动态地添加一些方法或字段。

目标对象(Target Object)

代理的目标对象

AOP代理(AOP Proxy)

AOP框架创建的对象,包含了目标对象和通知(Advice)

Spring Boot中的AOP

在Spring Boot中,可以通过几种方式来实现AOP编程:

  1. 使用注解方式
  2. 使用XML配置文件方式
  3. 使用@AspectJ注解方式(它是Spring AOP框架中对AspectJ注入式风格的支持,可以使用更加强大的AOP解决方案)
1. 使用注解方式

在spring boot项目中,可以使用@EnableAspectJAutoProxy来启用注解式AOP,默认情况下,Spring会使用JDK动态代理基于接口创建代理对象(也就是说,目标对象实现了接口),如果目标对象没有实现接口,则会采用CGLIB动态代理方式。

接下来以一个示例来介绍如何在spring boot中使用注解方式实现AOP编程。

@Aspect
@Component
public class LoggingAspect {
    private static final Logger LOGGER = 
         LoggerFactory.getLogger(LoggingAspect.class);

    @Before("execution(* com.example.demo.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        LOGGER.info("Start {} with arguments {}", 
             joinPoint.getSignature().getName(), 
             Arrays.toString(joinPoint.getArgs()));
    }

    @AfterReturning(pointcut = 
        "execution(* com.example.demo.service.*.*(..))", 
        returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        LOGGER.info("End {} with result {}", 
            joinPoint.getSignature().getName(), result);
    }

    @AfterThrowing(pointcut = 
        "execution(* com.example.demo.service.*.*(..))", 
        throwing = "exception")
    public void logAfterThrowing(JoinPoint joinPoint, Exception exception) {
        LOGGER.error("Exception in {} with cause = '{}'", 
            joinPoint.getSignature().getName(), exception.getCause());
    }
}

上面的示例展示了如何在spring boot项目中使用注解方式实现AOP编程,其中@Aspect注解用于定义一个切面,@Before、@AfterReturning、@AfterThrowing等注解用于定义通知,其中execution(* com.example.demo.service..(..))表示匹配com.example.demo.service包下所有方法,并且方法的参数可以是任意类型。

2. 使用XML配置文件方式

除了使用注解方式,还可以使用XML配置文件方式实现AOP编程,以下是一个示例。

首先,创建切面LoggingAspect.java:

public class LoggingAspect {
    private static final Logger LOGGER = 
         LoggerFactory.getLogger(LoggingAspect.class);

    public void logBefore(JoinPoint joinPoint) {
        LOGGER.info("Start {} with arguments {}", 
             joinPoint.getSignature().getName(), 
             Arrays.toString(joinPoint.getArgs()));
    }

    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        LOGGER.info("End {} with result {}", 
            joinPoint.getSignature().getName(), result);
    }

    public void logAfterThrowing(JoinPoint joinPoint, Exception exception) {
        LOGGER.error("Exception in {} with cause = '{}'", 
            joinPoint.getSignature().getName(), exception.getCause());
    }
}

接着,创建XML配置文件aop.xml:

<?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-3.0.xsd
           http://www.springframework.org/schema/aop 
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    
    <bean id="loggingAspect" class="com.example.demo.aspect.LoggingAspect"/>

    <aop:config>
        <aop:aspect id="aspectLogging" ref="loggingAspect">
            <aop:pointcut id="loggingPointcut"
                expression="execution(* com.example.demo.service.*.*(..))"/>
            <aop:before pointcut-ref="loggingPointcut" 
                method="logBefore" />
            <aop:after-returning pointcut-ref="loggingPointcut" 
                returning="result" method="logAfterReturning" />
            <aop:after-throwing pointcut-ref="loggingPointcut" 
                throwing="exception" method="logAfterThrowing" />
        </aop:aspect>
    </aop:config>

</beans>

上面的配置文件定义了一个LoggingAspect切面,它匹配com.example.demo.service包下所有方法,并且使用logBefore、logAfterReturning和logAfterThrowing方法分别实现前置通知、后置返回通知、后置异常通知操作。

最后,需要在配置类中加入@EnableAspectJAutoProxy注解开启AOP。

@Configuration
@EnableAspectJAutoProxy
@ImportResource("classpath:aop.xml")
public class AppConfig {
}
3. 使用@AspectJ注解方式

Spring AOP框架同时支持AspectJ注入式风格,它提供了更加强大的AOP解决方案。以下是一个示例。

首先,创建切面LoggingAspect.java:

@Aspect
@Component
public class LoggingAspect {
    private static final Logger LOGGER = 
         LoggerFactory.getLogger(LoggingAspect.class);

    @Pointcut("execution(* com.example.demo.service.*.*(..))")
    private void serviceMethods() { }

    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        LOGGER.info("Start {} with arguments {}", 
             joinPoint.getSignature().getName(), 
             Arrays.toString(joinPoint.getArgs()));
    }

    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        LOGGER.info("End {} with result {}", 
            joinPoint.getSignature().getName(), result);
    }

    @AfterThrowing(pointcut = "serviceMethods()", throwing = "exception")
    public void logAfterThrowing(JoinPoint joinPoint, Exception exception) {
        LOGGER.error("Exception in {} with cause = '{}'", 
            joinPoint.getSignature().getName(), exception.getCause());
    }
}

其中@Pointcut注解用于定义切点serviceMethods,它与execution(* com.example.demo.service..(..))匹配com.example.demo.service包下所有方法效果相同。

接下来,需要在配置类中创建LoggingAspect切面的Bean。

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

    @Bean
    public LoggingAspect loggingAspect() {
        return new LoggingAspect();
    }
}

以上示例展示了如何在Spring Boot中使用三种方式实现AOP编程,开发人员可以根据自己的喜好和实际情况选择合适的方式进行使用。