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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容