AOP AspectJ 切面注解中五种通知注解:

要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理。 

关于AOP的相关知识可以参考 https://blog.csdn.net/qq_22899021/article/details/82491042 该文章
在切面类中需要定义切面方法用于响应响应的目标方法,切面方法即为通知方法,通知方法需要用注解标识,AspectJ 支持 5 种类型的通知注解:

  • @Before: 前置通知, 在方法执行之前执行
  • @After: 后置通知, 在方法执行之后执行 。
  • @AfterRunning: 返回通知, 在方法返回结果之后执行
  • @AfterThrowing: 异常通知, 在方法抛出异常之后
  • @Around: 环绕通知, 围绕着方法执行

下面分别举例5中通知方法的使用

首先建立一个目标接口TestAop:

package com.bounter.mybatis.aop;

/**
 * @program: bounterMybatis
 * @description: 人间有味是清欢
 * @author: liuSha.pufengjun
 * @create: 2018-09-07 15:04
 **/
public interface TestAop {
    int add(int i,int j);
    int delete(int i ,int j);
int dev (int i ,int j);
}

然后创建接口的实现类TestAopImpl :

package com.bounter.mybatis.aop;

import org.springframework.stereotype.Component;

/**
 * @program: bounterMybatis
 * @description: 人间有味是清欢
 * @author: liuSha.pufengjun
 * @create: 2018-09-07 15:05
 **/
@Component("testAopImpl")
public class TestAopImpl implements TestAop {
    @Override
    public int add(int i, int j) {
        System.out.println("add   --   result="+ (i+j));
        return i+j;
    }

    @Override
    public int delete(int i, int j) {
        System.out.println("delete   --   result="+ (i-j));
        return i-j;
    }
    @Override
    public int dev(int i, int j) {
        System.out.println("dev   --   result="+ (i/j));
        return i/j;
    }
}

配置文件applicationContext.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: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/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 注解包扫描 -->
	<context:component-scan base-package="com.bounter" />
	

</beans>
  •  

创建测试类:

    @Test
    public void testAop(){
        ApplicationContext  context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        TestAop testAop = (TestAop)context.getBean("testAopImpl");
        testAop.add(2,1);
        testAop.delete(2,1);
    }

运行结果:

add   --   result=3
delete   --   result=1

Process finished with exit code 0

上面的例子把目标类注入到IOC容器中,执行时从容器中获取目标类的bean,然后调用目标方法。 
下面要在目标方法的前后等执行其它操作,打印日志,不需要改变任何目标方法,只需要增加切面类,新建切面类LogProxy,把切面类注入到IOC中,然后在切面类中定义要执行的切面方法即可。

在执行下面切面方法之前,需要先启动五种注解,配置文件中定义如下:

<context:component-scan base-package="com.bounter" />
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

或者直接类上加注解    @EnableAspectJAutoProxy(proxyTargetClass = true)

一、@Before前置通知

用@Before标识的方法为前置方法,在目标方法的执行之前执行,即在连接点之前进行执行。 
示例如下:

package com.bounter.mybatis.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * @program: bounterMybatis
 * @description: 人间有味是清欢
 * @author: liuSha.pufengjun
 * @create: 2018-09-07 15:20
 **/
@Aspect
@Component
public class TestLogProxy {

    @Before("execution(* com.bounter.mybatis.aop.TestAop.*(int,int))")

    public void before(JoinPoint point){
        String methodName = point.getSignature().getName();
        List<Object> ags = Arrays.asList(point.getArgs());
        System.out.println("调用 前 连接点方法名:" + methodName + "参数为: " +ags);
    }




}

执行测试类,输出结果如下:

调用 前 连接点方法名:add参数为: [2, 1]
add   --   result=3
调用 前 连接点方法名:delete参数为: [2, 1]
delete   --   result=1

在目标方法add和delete之前分别执行了前置通知方法。

二、@After后置通知方法

后置方法在连接点方法完成之后执行,无论连接点方法执行成功还是出现异常,都将执行后置方法。示例如下:

package com.bounter.mybatis.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * @program: bounterMybatis
 * @description: 人间有味是清欢
 * @author: liuSha.pufengjun
 * @create: 2018-09-07 15:20
 **/
@Aspect
@Component
public class TestLogProxy {



    @After("execution(* com.bounter.mybatis.aop.TestAop.*(int,int))")

    public void After(JoinPoint point){
        String methodName = point.getSignature().getName();
        List<Object> ags = Arrays.asList(point.getArgs());
        System.out.println("调用 后 连接点方法名:" + methodName + "参数为: " +ags);
    }


}

执行测试类,输出结果如下:

add->result:5
调用后连接点方法为:add,参数为:[3, 2]
div->result:2
调用后连接点方法为:dev,参数为:[4, 2]

发现add和dev两个连接点方法执行之后都调用了后置方法。如果目标连接点方法出现异常时,也会执行后置通知方法。把测试方法改成如下:

    @Test
    public void testAop(){
        ApplicationContext  context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        TestAop testAop = (TestAop)context.getBean("testAopImpl");
        testAop.add(2,1);
        testAop.dev(2,0);
    }

执行测试方法,输出结果如下:

add   --   result=3
调用 后 连接点方法名:add参数为: [2, 1]
调用 后 连接点方法名:dev参数为: [2, 0]

java.lang.ArithmeticException: / by zero

从输出结果中可以看出,即使目标方法出现异常,后置通知方法依然执行。但后置通知拿不到目标方法执行后的结果,因为目标方法有可能出现异常。如果要拿目标方法的执行结果,要用下面的通知方法。

三、@AfterRunning返回通知方法

当连接点方法成功执行后,返回通知方法才会执行,如果连接点方法出现异常,则返回通知方法不执行。返回通知方法在目标方法执行成功后才会执行,所以,返回通知方法可以拿到目标方法(连接点方法)执行后的结果。切面类中定义返回通知方法,示例如下:

package com.bounter.mybatis.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * @program: bounterMybatis
 * @description: 人间有味是清欢
 * @author: liuSha.pufengjun
 * @create: 2018-09-07 15:20
 **/
@Aspect
@Component
public class TestLogProxy {

/*    @Before("execution(* com.bounter.mybatis.aop.TestAop.*(int,int))")
   
    public void before(JoinPoint point){
        String methodName = point.getSignature().getName();
        List<Object> ags = Arrays.asList(point.getArgs());
        System.out.println("调用 前 连接点方法名:" + methodName + "参数为: " +ags);
    }*/

/*    @After("execution(* com.bounter.mybatis.aop.TestAop.*(int,int))")
   
    public void After(JoinPoint point){
        String methodName = point.getSignature().getName();
        List<Object> ags = Arrays.asList(point.getArgs());
        System.out.println("调用 后 连接点方法名:" + methodName + "参数为: " +ags);
    }*/
    /*通过returning属性指定连接点方法返回的结果放置在result变量中,在返回通知方法中可以从result变量中获取连接点方法的返回结果了。*/
    @AfterReturning(value = "execution(* com.bounter.mybatis.aop.TestAop.*(int,int))",returning = "result")
    public void AfterReturning(JoinPoint point,Object result){
        String methodName = point.getSignature().getName();
        List<Object> ags = Arrays.asList(point.getArgs());
        System.out.println("调用 后 连接点方法名:" + methodName + "参数为: " +ags+ " 目标执行结果为: " + result);
    }

}

运行测试方法,输出结果如下:

add   --   result=3
调用 后 连接点方法名:add参数为: [2, 1] 目标执行结果为: 3
dev   --   result=2
调用 后 连接点方法名:dev参数为: [2, 1] 目标执行结果为: 2
delete   --   result=1
调用 后 连接点方法名:delete参数为: [2, 1] 目标执行结果为: 1

当连接点方法出现异常时,不执行返回通知方法,把测试方法该为如下:

    @Test
    public void testAop(){
        ApplicationContext  context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        TestAop testAop = (TestAop)context.getBean("testAopImpl");
        testAop.add(2,1);
        testAop.dev(2,0);
        testAop.delete(2,1);
    }

运行测试方法,输出结果如下:

add   --   result=3
调用 后 连接点方法名:add参数为: [2, 1] 目标执行结果为: 3

java.lang.ArithmeticException: / by zero

从输出结果可以看出,dev(2,0) 出现异常,因此该连接点对应的返回通知方法也不执行。

四、@AfterThrowing异常通知

异常通知方法只在连接点方法出现异常后才会执行,否则不执行。在异常通知方法中可以获取连接点方法出现的异常。在切面类中异常通知方法,示例如下:

/*通过throwing属性指定连接点方法出现异常信息存储在ex变量中,在异常通知方法中就可以从ex变量中获取异常信息了*/
    @AfterThrowing(value = "execution(* com.bounter.mybatis.aop.TestAop.*(int,int))",throwing = "ex")
    public void AfterThrowind(JoinPoint point,Exception ex){
        String methodName = point.getSignature().getName();
        List<Object> ags = Arrays.asList(point.getArgs());
        System.out.println("调用 后 连接点方法名:" + methodName + "参数为: " +ags+ " 异常为: " + ex);

    }

测试方法为:

    @Test
    public void testAop(){
        ApplicationContext  context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        TestAop testAop = (TestAop)context.getBean("testAopImpl");
        testAop.add(2,1);
        testAop.dev(2,0);
        testAop.delete(2,1);
    }

执行测试方法,输出结果如下:

add   --   result=3
调用 后 连接点方法名:dev参数为: [2, 0] 异常为: java.lang.ArithmeticException: / by zero

java.lang.ArithmeticException: / by zero

从输出结果中可以看出,add方法没有异常,因此不执行异常通知方法,dev方法出现异常,执行科异常通知方法。 
上面的例子中,异常类型设置的是Exception,表示捕获连接点方法的所有异常信息,也可以指定捕获指定类型的信息,例如

@AfterThrowing(value="execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))",
            throwing="ex")
    /*只捕获连接点方法中的NullPointerException 类型的异常信息*/
    public void afterReturning(JoinPoint point, NullPointerException ex){
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
        System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex);
    }

五、@Around环绕通知

环绕通知方法可以包含上面四种通知方法,环绕通知的功能最全面。环绕通知需要携带 ProceedingJoinPoint 类型的参数,且环绕通知必须有返回值, 返回值即为目标方法的返回值。在切面类中创建环绕通知方法,示例如下:

 @Around("execution(* com.bounter.mybatis.aop.TestAop.*(int,int))")
    public Object aroundMethod(ProceedingJoinPoint pdj){
        /*result为连接点的放回结果*/
        Object result = null;
        String methodName = pdj.getSignature().getName();

        /*前置通知方法*/
        System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()));

        /*执行目标方法*/
        try {
            result = pdj.proceed();

            /*返回通知方法*/
            System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result);
        } catch (Throwable e) {
            /*异常通知方法*/
            System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e);
        }

        /*后置通知*/
        System.out.println("后置通知方法>目标方法名" + methodName);

        return result;

    }

运行测试方法:

    @Test
    public void testAop(){
        ApplicationContext  context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        TestAop testAop = (TestAop)context.getBean("testAopImpl");
        testAop.add(2,1);
        testAop.dev(2,0);
        
    }

运行测试方法,输出结果:

前置通知方法>目标方法名:add,参数为:[2, 1]
add   --   result=3
返回通知方法>目标方法名add,返回结果为:3
后置通知方法>目标方法名add
前置通知方法>目标方法名:dev,参数为:[2, 0]
异常通知方法>目标方法名dev,异常为:java.lang.ArithmeticException: / by zero
后置通知方法>目标方法名dev

从输出结果中可以看出,环绕通知实现了上面几种通知的结合。 
当dev目标方法出现异常时,在环绕通知方法中已经用try…catch方法进行捕捉了,为什么最后输出结果中还出现了一个返回类型不匹配的错误:

Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int lzj.com.spring.aop.ArithmeticCalculator.div(int,int)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219)
    at com.sun.proxy.$Proxy7.div(Unknown Source)
    at lzj.com.spring.aop.Main.main(Main.java:12)

那是因为在环绕通知方法中开始就定义了目标方法的返回结果 
Object result = null。当目标方法出现异常时,result = pdj.proceed();执行时出现异常,此时result中还是null,所以在环绕通知方法最后return result;时,返回的result就是null,但是环绕通知的返回类型我们定义的是Object类型的,null不能转化为Object类型,所以抛出了个类型转换的错误。我们可以在环绕通知方法中把异常抛出去,即为:

@Around("execution(* com.bounter.mybatis.aop.TestAop.*(int,int))")
    public Object aroundMethod(ProceedingJoinPoint pdj){
        /*result为连接点的放回结果*/
        Object result = null;
        String methodName = pdj.getSignature().getName();

        /*前置通知方法*/
        System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()));

        /*执行目标方法*/
        try {
            result = pdj.proceed();

            /*返回通知方法*/
            System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result);
        } catch (Throwable e) {
            /*异常通知方法*/
            System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e);
            /*当环绕通知方法本身还有其它异常时,非连接点方法出现的异常,此时抛出来*/
            throw new RuntimeException();
        }

        /*后置通知*/
        System.out.println("后置通知方法>目标方法名" + methodName);

        return result;
    }
}

在输出结果中会抛出一个运行时异常java.lang.RuntimeException

插曲:不可以在执行目标方法时在定义result变量:

        ……
        /*执行目标方法*/
        try {
            Object result = pdj.proceed();
            ……
        } catch (Throwable e) {
            ……
        }
        ……
        return result;

这种方法是行不通的,在Object result = pdj.proceed();中,如果pdj.proceed()执行失败,就会被try …catch捕获到异常,而不会就不会执行定义result变量那一步了,即Object result不会执行,所以在return result;就会出现错误。

猜你喜欢

转载自blog.csdn.net/qq_22899021/article/details/82496508