美文网首页
SpringAOP使用

SpringAOP使用

作者: 逍遥白亦 | 来源:发表于2021-03-21 13:52 被阅读0次

目前 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

相关文章

  • Spring AOP源码分析

    前言 通过之前的俩篇文章,我们大体上已经知道如何使用SpringAOP了,同时也了解到了SpringAOP底层使用...

  • SpringAOP使用

    问题描述 面向对象设计中有一个弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、安全检测等,需...

  • SpringAOP使用

    问题描述 面向对象设计中有一个弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、安全检测等,需...

  • SpringAOP使用

    目前 Spring AOP 一共有三种配置方式。 Spring 1.2 基于接口的配置:最早的 Spring AO...

  • 六、AOP实现自动的系统日志功能

    一、本课目标 掌握SpringAOP的配置 二、使用SpringAOP实现日志输出 在下面的这个示例中,通过Spr...

  • SpringAOP

    springAOP实现方式 1.动态代理 使用 Proxy机制,使用反射技术,创建实际对象的代理对象,添加AOP的...

  • Spring配置

    一:使用注释配置AOP: @Aspect:是SpringAOP切面的标识。 @Component:将此类交由Spr...

  • Spring中的Cache

    SpringAOP的完美案例 使用案例 原理解析 EnableCaching @Import用来整合所有在@Con...

  • SpringAOP源码

    1. 入口 SpringAOP的使用中有这么一个注解@EnableAspectJAutoProxy,按照Sprin...

  • spring框架 AOP

    10、 代理模式 为什么要学习代理模式?因为这就是SpringAOP的底层!【SpringAOP 和 Spring...

网友评论

      本文标题:SpringAOP使用

      本文链接:https://www.haomeiwen.com/subject/qwincltx.html