Spring 5框架之使用AspectJ 实现AOP功能 (九)

1.前言

开发中最常见就是使用Aspect完成AOP功能的实现,下面将简单使用@Aspect实现AOP的面向切面编程。关于Spring原生AOP的用法可参考之前的一篇博客文章 Spring5框架之AOP-ProxyFactory底层实现(五)

在使用AspectJ之前让我先温习一下几个比较重要的概念如下所示:

  1. before:在某个连接点之前执行程序逻辑。
  2. after returning:连接点正常后执行的程序逻辑,需要注意的是如果程序抛出异常该通知并不会执行。
  3. after throwing :当程序出现异常时候执行的程序逻辑。
  4. after:当连接点结束执行的程序逻辑(无论是否出现异常都会执行)
  5. around:spring中最强大的通知功能,它可以完成并实现上面4种功能的实现。

2.使用Aspect实现AOP功能

如果使用@Aspect注解需要在项目中支持引入下述Aspect的依赖:

        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.10</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.10</version>
        </dependency>

下面我们依然使用Calculator这个计算器接口演示AspectJ方法的使用如下所示:

  • 新增接口及其实现

首先AspectJ为我们提供了如下几个通知注解:
@Before@AfterReturning@After@Around@AfterThrowing 接下来将使用一个简单的案例演示这几个注解的使用。

public interface Calculator {

    int add(int a, int b);

    int sub(int a, int b);

    double divide(int a, int b);
}


@Service
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    @AdviceRequired
    public int sub(int a, int b) {
        return a - b;
    }

    @Override
    public double divide(int a, int b) {
        return a / b;
    }
}

  • 定义切面类
@Aspect
@Component
public class LogAspect {

    /**
     * 定义切点,其中execution定义切入点表达式
     */
    @Pointcut(value = "execution(* *..aop..*(..))")
    public void logPoint() {

    }
    /**
     * 前置通知在方法执行前执行
     */
    @Before(value = "execution(public * com.codegeek.aop.day1.CalculatorImpl.*(..))")
    public static void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        // 拿到执行方法的源对象-----即切入表达式实际切入的地方并运行的地方
        System.out.println(joinPoint.getTarget().getClass());
        //  System.out.println(joinPoint.getStaticPart()); 打印详细切入点表达式

        System.out.println("LogAspect-普通通知方法@before:" + joinPoint.getSignature().getName() + "日志开始了....方法参数:" + Arrays.asList(args));
    }

    /**
     * 方法执行后返回结果后执行该通知
     * @param result
     */
    @AfterReturning(value = "execution(public * com.codegeek.aop.day1.CalculatorImpl.*(..))", returning = "result")
    public static void logRun(Object result) {
        System.out.println("LogAspect-普通通知@AfterReturning" + "运行结果为:" + result);
    }

    /**
     *
     * 如果方法执行出现异常将执行此通知
     */
    @AfterThrowing(value = "execution(public  * com.codegeek.aop.day1.Calculator.*(..))", throwing = "e")
    public static void logException(Exception e) {
        System.out.println("LogAspect-普通通知@AfterThrowing出异常啦:" + e);
    }

    /**
     * 后置通知切入点执行后
     */
    @After(value = "execution(public  * com.codegeek.aop.day1.Calculator.*(..))")
    public void logEnd() {
        System.out.println("LogAspect-普通通知@After日志结束了");
    }

    @Around(value = "logPoint()")
    public Object logAround(ProceedingJoinPoint proceedingJoinPoint) {
        Object proceed = null;
        try {
            // @Before
            System.out.println("环绕前通知.....当前执行的方法:" + proceedingJoinPoint.getSignature().getName());
            proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            // @AfterReturn
            System.out.println("环绕后通知.....");
        } catch (Throwable throwable) {
            // @AfterThrowing
            System.out.println("环绕异常通知.......");
            // 如果不抛出异常----则就将异常catch掉了,普通通知异常将无法感知到异常对象,所以认为是正常执行的
           throw new RuntimeException(throwable);

        } finally {
            // @After
            System.out.println("环绕结束通知.....");
        }
        return proceed;
    }
}

在我们定义完切面类后有几个重要的点需要注意一下:

  • 使用了@Component和@Aspect注解声明一个切面类对象,且可以被Spring IOC容器扫描到此对象。
  • 使用了 @Pointcut注解定义了方法的切点,所谓的切点的作用就是决定通知能够在那些目标类的目标方法执行。
  • @Before、@Around、@After、@AfterReturning、@AfterThrowing 中可以引入切点的值,亦可自定义切点的值

接下来我们还需要定义配置类以使Spring可以扫描到切面类并成功可以执行通知。

@Configuration
@ComponentScan(basePackages = {"com.codegeek.aop.day1"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AOPConfig {

}
  • 测试方法:

需要注意的是在测试类上可以引入此配置类如下所示:
在这里插入图片描述

    @Test
    public void testAspect() {
        // 配置切面类IOC就生成接口的代理类否则就是基本类
        System.out.println();
        Calculator bean = applicationContext.getBean(Calculator.class);
        System.out.println(bean.add(1, 5));
        System.out.println("-------------------");
    }

这里我们使用了正常的方法测试其运行结果如下:

环绕前通知.....当前执行的方法:add
class com.codegeek.aop.day1.CalculatorImpl
LogAspect-普通通知方法@before:add日志开始了....方法参数:[1, 5]
环绕后通知.....
环绕结束通知.....
LogAspect-普通通知@After日志结束了
LogAspect-普通通知@AfterReturning运行结果为:6
6
-------------------

接下来我们测试一个异常的情况如下:

    @Test
    public void testAspect() {
        // 配置切面类IOC就生成接口的代理类否则就是基本类
        System.out.println();
        Calculator bean = applicationContext.getBean(Calculator.class);
        System.out.println(bean.add(1, 5));
        System.out.println("-------------------");
        System.out.println(bean.divide(5, 0));
        System.out.println(bean);
        System.out.println(bean.getClass());
    }

运行测试方法后输出结果如下所示:

环绕前通知.....当前执行的方法:add
class com.codegeek.aop.day1.CalculatorImpl
LogAspect-普通通知方法@before:add日志开始了....方法参数:[1, 5]
环绕后通知.....
环绕结束通知.....
LogAspect-普通通知@After日志结束了
LogAspect-普通通知@AfterReturning运行结果为:6
6
-------------------
环绕前通知.....当前执行的方法:divide
class com.codegeek.aop.day1.CalculatorImpl
LogAspect-普通通知方法@before:divide日志开始了....方法参数:[5, 0]
环绕异常通知.......
环绕结束通知.....
LogAspect-普通通知@After日志结束了
LogAspect-普通通知@AfterThrowing出异常啦:java.lang.RuntimeException: java.lang.ArithmeticException: / by zero

java.lang.RuntimeException: java.lang.ArithmeticException: / by zero

我们发现了异常通知执行了,但是程序运行正常情况下异常通知将不会执行,于此同时可以发现@AfterReturning注解通知并没有执行,它只有在程序运行正常下才会执行通知。关于通知执行顺序可以很形象表示如下:

[普通前置]
try {
  环绕通知前置
  环绕执行
  环绕返回
} catch(Exception e) {
  环绕异常通知
} finally {
  环绕后置通知
}
[普通后置]
[普通方法返回/普通异常通知]

3.使用xml实现AOP实现

我们首先将LogAspect类上的@Aspect注解进行注释,然后新建立一个切面类OrderAspect如下:

@Component
//@Aspect
@Order(1)
public class OrderAspect {
    
   // @Before(value = "execution(* *..aop..*(..))")
    public static void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        //  System.out.println(joinPoint.getStaticPart()); 打印详细切入点表达式

        System.out.println("OrderAspect-普通通知方法@before:" + joinPoint.getSignature().getName() + "日志开始了....方法参数:" + Arrays.asList(args));
    }
   // @Pointcut(value = "execution(* *..aop..*(..))")
    public void logPoint() {

    }

    // returning 告诉方法执行完毕后返回的值
   // @AfterReturning(value = "execution(public * com.codegeek.aop.day1.CalculatorImpl.*(..))", returning = "result")
    public static void logRun(Object result) {
        System.out.println("OrderAspect-普通通知@AfterReturning" + "运行结果为:" + result);
    }

    //@AfterThrowing(value = "execution(public  * com.codegeek.aop.day1.Calculator.*(..))", throwing = "e")
    public static void logException(Exception e) {
        System.out.println("OrderAspect-普通通知@AfterThrowing出异常啦:" + e);
    }

  //  @After(value = "execution(public  * com.codegeek.aop.day1.Calculator.*(..))")
    public void logEnd() {
        System.out.println("OrderAspect-普通通知@After日志结束了");
    }
}

然后在xml中配置如下内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--  false使用jdk代理否则使用CGlib-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<context:component-scan base-package="com.codegeek.aop.day1"/>

  <aop:config>
    <aop:pointcut id="logPoint" expression="execution(* *..aop..*(..))"/>
    <aop:aspect id="myAspect" ref="orderAspect">
      <aop:before method="logStart" pointcut-ref="logPoint" arg-names="joinPoint"/>
      <aop:after method="logEnd" pointcut-ref="logPoint" />
      <aop:after-returning method="logRun" pointcut-ref="logPoint" returning="result"/>
      <aop:after-throwing method="logException" pointcut-ref="logPoint" throwing="e"/>
    </aop:aspect>
  </aop:config>
  <bean id="orderAspect" class="com.codegeek.aop.day1.OrderAspect"/>
</beans>

我们再次运行测试方法如下所示:

    @Test
    public void testAspect() {
        // 配置切面类IOC就生成接口的代理类否则就是基本类
        System.out.println();
        Calculator bean = applicationContext.getBean(Calculator.class);
        System.out.println(bean.add(1, 5));
        System.out.println("-------------------");
        System.out.println(bean.divide(5, 0));
        System.out.println(bean);

运行结果如下所示:

OrderAspect-普通通知方法@before:add日志开始了....方法参数:[1, 5]
OrderAspect-普通通知@After日志结束了
OrderAspect-普通通知@AfterReturning运行结果为:6
6
-------------------
OrderAspect-普通通知方法@before:divide日志开始了....方法参数:[5, 0]
OrderAspect-普通通知@After日志结束了
OrderAspect-普通通知@AfterThrowing出异常啦:java.lang.ArithmeticException: / by zero

java.lang.ArithmeticException: / by zero

4. 如何选择AOP类型

在上面我们演示了一个基于Aspect风格注解的AOP实现,也看到了一个基于XML配置的AOP实现。这些受到多种因素的影响,例如程序需求、开发工具、开发团队对AOP熟悉的程度等等。由于Spring AOP与AspectJ都使用了相同的Aspect风格,如果有额外的需要AspectJ的功能需求,可以将现有的Aspect风格注解的代码迁移到Aspect上。

猜你喜欢

转载自blog.csdn.net/javaee_gao/article/details/106745225