Spring 2.0 @AspectJ 配置

Spring 2.0 以后,引入了 @AspectJ Schema-based 的两种配置方式,我们先来介绍 @AspectJ 的配置方式,之后我们再来看使用 xml 的配置方式。

添加依赖

首先,我们需要依赖 aspectjweaver.jar 这个包,这个包来自于 AspectJ:

  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjweaver</artifactId>
  4. <version>1.8.11</version>
  5. </dependency>

如果是使用 Spring Boot 的话,添加以下依赖即可:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

配置开启aop

其次,我们需要开启 @AspectJ 的注解配置方式,有两种方式:
在 xml 中配置:

<aop:aspectj-autoproxy/>

使用 @EnableAspectJAutoProxy

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

一旦开启了上面的配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,我们称之为一个 Aspect

注意了,@Aspect 注解要作用在 bean 上面,不管是使用 @Component 等注解方式,还是在 xml 中配置 bean,首先它需要是一个 bean。

配置切面

配置 Pointcut

首先,我们需要配置 Pointcut,Pointcut 在大部分地方被翻译成切点,用于定义哪些方法需要被增强或者说需要被拦截

Spring AOP 只支持 bean 中的方法(不像 AspectJ 那么强大),所以我们可以认为 Pointcut 就是用来匹配 Spring 容器中的所有 bean 的方法的。

@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

我们看到,@Pointcut 中使用了 execution 来正则匹配方法签名,这也是最常用的,除了 execution,我们再看看其他的几个比较常用的匹配方式:

  • within:指定所在类或所在包下面的方法(Spring AOP 独有)

    @Pointcut("within(com.javadoop.springaoplearning.service..*)")
    
  • @annotation:方法上具有特定的注解,如 @Subscribe 用于订阅特定的事件。

    @Pointcut("execution( .*(..)) && @annotation(com.javadoop.annotation.Subscribe)")
    
  • bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)

    @Pointcut("bean(*Service)")
    

    Tips:上面匹配中,通常 “.” 代表一个包名,”..” 代表包及其子包,方法参数任意匹配使用两个点 “..”。

配置 Advice

配置 pointcut 就是配置我们需要拦截哪些方法,接下来,我们要配置需要对这些被拦截的方法做什么。

@Aspect
public class AdviceExample {

    // 下面方法就是写拦截 "dao层实现"
    @Before("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ... 实现代码
    }

    // 当然,我们也可以直接"内联"Pointcut,直接在这里定义 Pointcut
    // 把 Advice 和 Pointcut 合在一起了,但是这两个概念我们还是要区分清楚的
    @Before("execution(* com.javadoop.dao.*.*(..))")
    public void doAccessCheck() {
        // ... 实现代码
    }

    @AfterReturning("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

    @AfterReturning(
        pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // 这样,进来这个方法的处理时候,retVal 就是相应方法的返回值,是不是非常方便
        //  ... 实现代码
    }

    // 异常返回
    @AfterThrowing("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ... 实现代码
    }

    @AfterThrowing(
        pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ... 实现代码
    }

    // 注意理解它和 @AfterReturning 之间的区别,这里会拦截正常返回和异常的情况
    @After("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // 通常就像 finally 块一样使用,用来释放资源。
        // 无论正常返回还是异常退出,都会被拦截到
    }

    // 感觉这个很有用吧,既能做 @Before 的事情,也可以做 @AfterReturning 的事情
    @Around("com.javadoop.aop.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}

Spring 2.0 schema-based 配置

本节将介绍的是 Spring 2.0 以后提供的基于 命名空间的 XML 配置。这里说的 schema-based 就是指基于 aop 这个 schema。

配置 Aspect

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>

所有的配置都在 下面。
中需要指定一个 bean,和前面介绍的 LogArgsAspect 和 LogResultAspect 一样,我们知道该 bean 中我们需要写处理代码。
然后,我们写好 Aspect 代码后,将其“织入”到合适的 Pointcut 中,这就是面向切面。、

配置 Pointcut

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.javadoop.springaoplearning.service.*.*(..))"/>

    <!--也可以像下面这样-->
    <aop:pointcut id="businessService2"
        expression="com.javadoop.SystemArchitecture.businessService()"/>

</aop:config>

作为 的直接子元素,将作为全局 Pointcut。

我们也可以在 内部配置 Pointcut,这样该 Pointcut 仅用于该 Aspect:

<aop:config>
    <aop:aspect ref="logArgsAspect">
        <aop:pointcut id="internalPointcut"
                expression="com.javadoop.SystemArchitecture.businessService()" />
    </aop:aspect>
</aop:config>

参考:https://javadoop.com/post/spring-aop-intro