十四、【AOP】基本使用

  • AOP(Aspect Oriented Programming):面向切面编程。AOP是在我们原来写的代码的基础上,进行一定的包装,比如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者增强处理。我们需要实现一个代理来创建实例,实际运行的实例其实是生成的代理类的实例。
  • Spring的AOP和AspectJ?Spring AOP的底层实现有两种,一种是JDK的动态代理,另一种是CGLIB,Spring AOP没有用到AspectJ ,只是借鉴了它并添加了AspectJ 风格的注解,使用Aspectj必须用到它自己特殊的编译器和运行环境的插件。Spring AOP只是用到了AspectJ 的配置而已,没有用它的编译器、也没有用它的类加载器。Spring AOP对于没有实现接口的类,会用CGLIB来动态生成子类,否则使用JDK自带的动态代理。
  • Spring AOP:首先要说明的是,这里的Spring AOP 是纯的 Spring 代码,和 AspectJ 没什么关系,但是 Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。@Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。

基于@AspectJ注解的AOP配置,本文只介绍关于注解的方式,配置文件的方式来实现AOP不作分析

  1. 首先引入AOP所依赖的jar包
<!-- AOP -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.0.8.RELEASE</version>
</dependency>

其中包含2个必须的包

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.0.8.RELEASE</version>
    <scope>compile</scope>
    <optional>true</optional>
</dependency>

如果使用了SpringBoot的话,直接添加如下依赖:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver 并不是因为需要使用 AspectJ 的处理功能,而是因为 Spring 使用了 AspectJ 提供的一些注解,实际上还是纯的 Spring AOP 代码。明确一点,@AspectJ 采用注解的方式来配置使用 Spring AOP。

开启 @AspectJ 的注解配置方式,有两种方式:

一、使用xml方式:

<aop:aspectj-autoproxy/>

二、使用@EnableAspectJAutoProxy

/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
@Configuration
// 手动开启Aspect注解
@EnableAspectJAutoProxy
public class Config {
    
}

采用@Configuration配置文件的方式来模拟spring上下文环境

/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
public class MainTest {

    public static void main(String[] args) {
        // 加载配置文件
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
        BusinessCalculate businessCalculate = ac.getBean(BusinessCalculate.class);
        int result1 = businessCalculate.calculate(10, 5);
        System.out.println("===================================================");
        RandomCalculate randomCalculate = ac.getBean(RandomCalculate.class);
        int result2 = randomCalculate.calculate(10, 5);
    }

}

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

/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
@Aspect
public class BeforeAdvice {

}

Demo介绍:

  1. 编写两个业务类(目的是为了测试不同的切入点)
/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
public class BusinessCalculate {

    public int calculate(int i, int j) {
        int result = i / j;
        System.out.println("BusinessCalculate-业务方法执行。。。。。。");
        return result;
    }

}

/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
public class RandomCalculate {

    public int calculate(int i, int j) {
        int result = i / j;
        System.out.println("RandomCalculate-业务方法执行。。。。。。");
        return result;
    }

}
  1. 编写切入点SystemArchitecture类(Spring建议是这个名字) (此类上面不需要加@Aspect)
/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
public class SystemArchitecture {

    @Pointcut("bean(*domCalculate)")
    public void definitionPointCut(){}

    // execution(* *(..)) 所有方法
    // 抽取公共表达式
    // @Pointcut("execution(public int com.nmys.story.springCore.aopdemo.BusinessCalculate.*(..))")
    @Pointcut("execution(* com.nmys.story.springCore.aopdemo.BusinessCalculate.*(..))")
	// @Pointcut("bean(*Calculate)")
    public void pointCut() {}

}

介绍一下切入点的表达式:

  • within:指定所在类或所在包下面的方法(Spring AOP 独有)如 @Pointcut("within(com.nmys.story.service..*)")。
  • @annotation:方法上具有特定的注解,如 @Subscribe 用于订阅特定的事件。如 @Pointcut("execution( .*(..)) && @annotation(com.nmys.story.Subscribe)")。
  • bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)如 @Pointcut("bean(*Service)")。
  • execution 来正则匹配方法签名(根据业务需求来查阅一下表达式的具体写法)。

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

  1. 定义切面(切面最好不要都揉在一个类中,显得杂乱无章,建议分开写,如下)
/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
@Aspect
public class BeforeAdvice {

    // 前置通知
    @Before("com.zhangjianbing.story.springCore.aopdemo.SystemArchitecture.pointCut()")
    public void logBefore(JoinPoint joinPoint) {
        // 获取传入的参数
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
        System.out.println("调用方法之前执行logBefore。。。。。。");
    }

    // 前置通知
    @Before("com.zhangjianbing.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()")
    public void randomBefore() {
        System.out.println("调用方法之前执行randomBefore。。。。。。");
    }

}

其中如果需要拿到方法的入参,则用JoinPoint类来获得

/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
@Aspect
public class AfterAdvice {

    // 后置通知
    @After("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()")
    public void logAfter() {
        System.out.println("调用方法之后执行logAfter。。。。。。");
    }

    // 后置通知
    @After("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()")
    public void randomAfter() {
        System.out.println("调用方法之后执行randomAfter。。。。。。");
    }

}

上面加上了@Aspect注解表明spring会将此bean当作切面来管理

介绍一下其他通知方法的写法:

/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
@Aspect
public class LogAspect {

//    // 前置通知
//    @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()")
//    public void logBefore() {
//        System.out.println("调用方法之前执行logBefore。。。。。。");
//    }
//
//    // 前置通知
//    @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()")
//    public void randomBefore() {
//        System.out.println("调用方法之前执行randomBefore。。。。。。");
//    }

//    // 后置通知
//    @After("pointCut()")
//    public void logAfter() {
//        System.out.println("调用方法之后执行logAfter。。。。。。");
//    }
//
//    // 正常返回通知(抛出异常则不会执行)
//    @AfterReturning("pointCut()")
//    public void logReturn() {
//        System.out.println("方法正常返回结果后执行logReturn。。。。。。");
//    }
//
//    // 异常通知
//    @AfterThrowing("pointCut()")
//    public void logException() {
//        System.out.println("logException。。。。。。");
//    }
//
//    @Around("pointCut()")
//    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//        // 比前置通知提前执行
//        System.out.println("环绕通知 - around 执行目标方法之前。。。。。。");
//        // 利用反射机制来调用目标方法
//        Object proceed = proceedingJoinPoint.proceed();
//        System.out.println("环绕通知 - around 执行目标方法之后。。。。。。");
//        return proceed;
//    }
}

@Around():环绕通知在实际中不常用,proceed()方法实际是用了反射来调用目标方法,上面的具体注释很清楚。

  1. 编写config类
/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
@Configuration
@EnableAspectJAutoProxy
public class Config {

    // 将@Aspect修饰的类和业务类都交给spring来管理
    @Bean
    public BeforeAdvice beforeAdvice() {
        return new BeforeAdvice();
    }

    @Bean
    public AfterAdvice afterAdvice() {
        return new AfterAdvice();
    }

    @Bean
    public BusinessCalculate businessCalculate() {
        return new BusinessCalculate();
    }

    @Bean
    public RandomCalculate randomCalculate() {
        return new RandomCalculate();
    }

}

需要将业务类和切面类都交给spring来管理,同时需要开启Aspect(@EnableAspectJAutoProxy)

  1. 编写测试类
/**
 * @author zhangjianbing
 * @From www.zhangjianbing.com
 */
public class MainTest {

    public static void main(String[] args) {
        // 加载配置文件
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
        BusinessCalculate businessCalculate = ac.getBean(BusinessCalculate.class);
        int result1 = businessCalculate.calculate(10, 5);
        System.out.println("===================================================");
        RandomCalculate randomCalculate = ac.getBean(RandomCalculate.class);
        int result2 = randomCalculate.calculate(10, 5);
    }

}
  1. 运行结果:
10
5
调用方法之前执行logBefore。。。。。。
BusinessCalculate-业务方法执行。。。。。。
调用方法之后执行logAfter。。。。。。
===================================================
调用方法之前执行randomBefore。。。。。。
RandomCalculate-业务方法执行。。。。。。
调用方法之后执行randomAfter。。。。。。

以上结果表明对目标方法进行了增强。

最后再说一下,以上使用的是Spring的AOP,和AspectJ基本没什么关系。

原文地址:https://www.cnblogs.com/zhangjianbing/p/13726723.html