SpringAOP使用

目前 Spring AOP 一共有三种配置方式。

  • Spring 1.2 基于接口的配置:最早的 Spring AOP是完全基于几个接口的。
  • Spring 2.0 schema-based 配置:Spring 2.0以后使用XML的方式来配置,使用 命名空间<aop></aop>
  • Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做 @AspectJ,但是这个和AspectJ其实没啥关系。

1. 基于接口的配置

首先先介绍Spring2.0之前的AOP实现方式。

1.1 基于Advice的实现。

先介绍一个最简单的使用,对目标类所有方法均加上前置通知。

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Spring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!--spring AOP的包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.11.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.11.RELEASE</version>
        </dependency>

        <!--springIOC的包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.2.11.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.11.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.11.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.11.RELEASE</version>
        </dependency>
    </dependencies>
    
</project>

目标接口

package com.zyz.earlyAOP;

public interface Calculate {

    /**
     * 加法
     * @param numA
     * @param numB
     * @return
     */
    int add(int numA, int numB);

    /**
     * 减法
     * @param numA
     * @param numB
     * @return
     */
    int sub(int numA, int numB);
}

目标类,实现了两个方法

package com.zyz.earlyAOP;

public class TXCalculate implements Calculate {

    @Override
    public int add(int numA, int numB) {
        System.out.println("执行目标方法:add");
        return numA + numB;
    }

    @Override
    public int sub(int numA, int numB) {
        System.out.println("执行目标方法:sub");
        return numA - numB;
    }
}

通知类,在执行方法之前,加入前置通知。

package com.zyz.earlyAOP;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;
import java.util.Arrays;

public class TXAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object object) throws Throwable {
        String methodName = method.getName();
        System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+ Arrays.asList(args));
    }
}

主配置类

package com.zyz.earlyAOP;

import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.context.annotation.Bean;

public class EarlyAopMainConfig {

    @Bean
    public Calculate txCalculate(){
        return new TXCalculate();
    }

    @Bean
    public TXAdvice txAdvice(){
        return new TXAdvice();
    }

    @Bean
    public ProxyFactoryBean calculate(){
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        //选择的Advisor或Advice
        proxyFactoryBean.setInterceptorNames("txAdvice");
        //目标类
        proxyFactoryBean.setTarget(txCalculate());
        return proxyFactoryBean;
    }

}

启动类

package com.zyz.earlyAOP;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainStat {

    public static void main(String[] args) {
        ApplicationContext ctx =
                new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
        Calculate calculateProxy = ctx.getBean("calculate", Calculate.class);
        calculateProxy.add(1,2);
        calculateProxy.add(2,1);
    }

}

运行结果

执行目标方法【add】的<前置通知>,入参[1, 2]
执行目标方法:add
执行目标方法【sub】的<前置通知>,入参[2, 1]
执行目标方法:sub

1.2 引Advisor

在实际应用中,我们可能需要对一个类的不同方法,进行不同的Advice(增强),那么就需要引入Advisor。

这里只针对add方法进行增强。

修改

package com.zyz.earlyAOP;

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.context.annotation.Bean;

public class EarlyAopMainConfig {

    @Bean
    public Calculate txCalculate(){
        return new TXCalculate();
    }

    @Bean
    public NameMatchMethodPointcutAdvisor txAspect(){
        NameMatchMethodPointcutAdvisor  advisor = new NameMatchMethodPointcutAdvisor();
        //选择的通知
        advisor.setAdvice(txAdvice());
        //选择的方法
        advisor.setMappedName("add");
        return advisor;
    }

    @Bean
    public ProxyFactoryBean calculate(){

        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        //选择的Advisor或Advice
        proxyFactoryBean.setInterceptorNames("txAspect");
        //目标类
        proxyFactoryBean.setTarget(txCalculate());
        return proxyFactoryBean;
    }

}

启动类

package com.zyz.earlyAOP;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainStat {

    public static void main(String[] args) {
        ApplicationContext ctx =
                new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
        Calculate calculateProxy = ctx.getBean("calculate", Calculate.class);
        calculateProxy.add(1,2);
        calculateProxy.sub(2,1);
    }

}

执行结果

执行目标方法【add】的<前置通知>,入参[1, 2]
执行目标方法:add
执行目标方法:sub

NameMatchMethodPointcutAdvisor类也可以对多个方法进行增强。

advisor.setMappedNames(new String[]{"add","sub"});

但是这里有个问题,就是使用的时候,我们需要获取Calculate的代理类,即

Calculate calculateProxy = ctx.getBean("calculate", Calculate.class);

假定以后有多个代理类,那么代码就会反复修改,所以应该想办法解决问题。

1.3 Autoproxy

好在贴心的Spring提供了自动代理的功能,可以让我们在使用的时候完全不关心代理。

修改如下

package com.zyz.earlyAOP;

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.context.annotation.Bean;

public class EarlyAopMainConfig {

    @Bean
    public Calculate txCalculate(){
        return new TXCalculate();
    }

    @Bean
    public TXAdvice txAdvice(){

        return new TXAdvice();
    }

    @Bean
    public NameMatchMethodPointcutAdvisor txAspect(){
        NameMatchMethodPointcutAdvisor  advisor = new NameMatchMethodPointcutAdvisor();
        //选择的通知
        advisor.setAdvice(txAdvice());
        //选择的方法
        advisor.setMappedName("add");
        return advisor;
    }

    @Bean
    public BeanNameAutoProxyCreator autoProxyCreator() {
        BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
        beanNameAutoProxyCreator.setBeanNames("tx*");
        beanNameAutoProxyCreator.setInterceptorNames("txAspect");
        return beanNameAutoProxyCreator;
    }

}

package com.zyz.earlyAOP;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainStat {

    public static void main(String[] args) {
        ApplicationContext ctx =
                new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
        Calculate calculateProxy = ctx.getBean("txCalculate", Calculate.class);
        calculateProxy.add(1,2);
        calculateProxy.sub(2,1);
    }

}

2. schema-based XML配置

由于我对这种XML配置方法比较排斥(幸好Spring自己也发现了,所以后续推出了Springboot),所以这里先不介绍了,有兴趣的同学可以看看这篇。

3. 基于注解的配置

这里重点说下基于注解的配置。

3.1 使用方法

3.1.1 开启@AspectJ注解配置方式

有两种方式

在XML中配置:

<aop:aspectj-autoproxy/>

使用@EnableAspectJAutoProxy直接

@Configuration
@EnableAspectJAutoProxy
public class Config {

}

开启了上述配置之后,所有在容器中,被@AspectJ注解的 bean 都会被 Spring 当做是 AOP 配置类,称为一个 Aspect。

3.1.2 配置Pointcut(增强的切入点)

在Spring 中,我们可以认为 Pointcut 是用来匹配Spring 容器中所有满足指定条件的bean的方法。

下面完整列举一下 Pointcut 的匹配方式:

  • execution:匹配方法签名
    // 指定的方法
    @Pointcut("execution(* testExecution(..))")
    public void anyTestMethod() {}
  • within:指定所在类或所在包下面的方法(Spring AOP 独有)
    // service 层
    // ".." 代表包及其子包
    @Pointcut("within(com.zyz.earlyAOP..*)")
    public void inSvcLayer() {}
  • @annotation:方法上具有特定的注解
    // 指定注解
    @Pointcut("@annotation(com.zyz.earlyAOP.HaveAop)")
    public void withAnnotation() {}
  • bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)
    // controller 层
    @Pointcut("bean(testController)")
    public void inControllerLayer() {}

关于 Pointcut 的配置,Spring 官方建议,将所有通用的Pointcut配置都写到一个类里,官方还贴心的给出了一个例子:

package com.xyz.someapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SystemArchitecture {

  /**
   * A join point is in the web layer if the method is defined
   * in a type in the com.xyz.someapp.web package or any sub-package
   * under that.
   */
  @Pointcut("within(com.xyz.someapp.web..*)")
  public void inWebLayer() {}

  /**
   * A join point is in the service layer if the method is defined
   * in a type in the com.xyz.someapp.service package or any sub-package
   * under that.
   */
  @Pointcut("within(com.xyz.someapp.service..*)")
  public void inServiceLayer() {}

  /**
   * A join point is in the data access layer if the method is defined
   * in a type in the com.xyz.someapp.dao package or any sub-package
   * under that.
   */
  @Pointcut("within(com.xyz.someapp.dao..*)")
  public void inDataAccessLayer() {}

  /**
   * A business service is the execution of any method defined on a service
   * interface. This definition assumes that interfaces are placed in the
   * "service" package, and that implementation types are in sub-packages.
   * 
   * If you group service interfaces by functional area (for example, 
   * in packages com.xyz.someapp.abc.service and com.xyz.def.service) then
   * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
   * could be used instead.
   */
  @Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
  public void businessService() {}
  
  /**
   * A data access operation is the execution of any method defined on a 
   * dao interface. This definition assumes that interfaces are placed in the
   * "dao" package, and that implementation types are in sub-packages.
   */
  @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
  public void dataAccessOperation() {}

}

3.1.3 配置Advice

注意,实际开发过程当中,Aspect 类应该遵守单一职责原则,不要把所有的Advice配置全部写在一个Aspect类里面。

这里为了简单起见,都写到一个类里

public class AspectAdvice {

    @Pointcut("execution(* com.zyz.aspectAop.TXCalculate.*(..))")
    public void pointCut(){}

    @Before(value = "pointCut()")
    public void methodBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+ Arrays.asList(joinPoint.getArgs()));
    }

    @After(value = "pointCut()")
    public void methodAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<后置通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }

    @AfterReturning(value = "pointCut()",returning = "result")
    public void methodReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<返回通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }

    @AfterThrowing(value = "pointCut()")
    public void methodAfterThrowing(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<异常通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }

}

3.2 示例代码

目标接口

package com.zyz;

public interface Calculate {

    /**
     * 加法
     * @param numA
     * @param numB
     * @return
     */
    int add(int numA, int numB);

    /**
     * 减法
     * @param numA
     * @param numB
     * @return
     */
    int sub(int numA, int numB);
}

目标接口

package com.zyz.aspectAop;

import com.zyz.Calculate;
import org.springframework.stereotype.Component;

@Component
public class TXCalculate implements Calculate {

    @Override
    public int add(int numA, int numB) {
        System.out.println("执行目标方法:add");
        return numA + numB;
    }

    @Override
    public int sub(int numA, int numB) {
        System.out.println("执行目标方法:sub");
        return numA - numB;
    }
}

配置Pointcut和Advice

package com.zyz.aspectAop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Order
@Component
public class AspectAdvice {

    @Pointcut("execution(* com.zyz.aspectAop.TXCalculate.*(..))")
    public void pointCut(){}

    @Before(value = "pointCut()")
    public void methodBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+ Arrays.asList(joinPoint.getArgs()));
    }

    @After(value = "pointCut()")
    public void methodAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<后置通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }

    @AfterReturning(value = "pointCut()",returning = "result")
    public void methodReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<返回通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }

    @AfterThrowing(value = "pointCut()")
    public void methodAfterThrowing(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【"+methodName+"】的<异常通知>,入参"+Arrays.asList(joinPoint.getArgs()));
    }

}

主配置类

package com.zyz.aspectAop;

import com.zyz.Calculate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class MainConfig {

    @Bean
        public Calculate calculate(){
        return new TXCalculate();
    }

    @Bean
    public AspectAdvice aspectAdvice(){
        return new AspectAdvice();
    }

}

启动类

package com.zyz.aspectAop;


import com.zyz.Calculate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainClass {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);

        Calculate calculate = (Calculate) ctx.getBean("calculate");
        int retVal = calculate.add(2,4);
    }
}

输出:

执行目标方法【add】的<前置通知>,入参[2, 4]
执行目标方法:add
执行目标方法【add】的<返回通知>,入参[2, 4]
执行目标方法【add】的<后置通知>,入参[2, 4]

参考

  1. https://juejin.cn/post/6844903987062243341#heading-10
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容