学习Spring(九) -- Spring使用AOP

二次信任 提交于 2020-04-06 22:04:09

AOP通知

Spring中常用的AOP通知有五种:

  1. 前置通知:在某方法调用之前执行;

  2. 后置通知:在后方法调用之后执行;

  3. 异常通知:在某方法发生异常时执行;

  4. 返回通知:在某方法进行返回时执行;

  5. 环绕通知:可手动进行控制以上四种通知的执行;

AOP配置

    加入一下spring的jar包:


    配置文件中引入xmlns:aop,加入<aop:aspectj-autoproxy></aop:aspectj-autoproxy>,具体配置如下:

<?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:util="http://www.springframework.org/schema/util"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/util 
http://www.springframework.org/schema/util/spring-util-4.2.xsd 
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-4.2.xsd 
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">

    <context:component-scan base-package="cn.net.bysoft.lesson7">
    </context:component-scan>
    <!-- 自动生成aop代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

    接下来编写测试类与切面类,做一个简单的接口,其中有4个方法,有是加减乘除四个函数,需求是在执行这4个函数的时候进行日志输入:

package cn.net.bysoft.lesson7;

public interface Arithmentic {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
package cn.net.bysoft.lesson7;

import org.springframework.stereotype.Component;

@Component()
public class ArithmenticImpl implements Arithmentic {

    @Override
    public int add(int i, int j) {
        return i + j;
    }

    @Override
    public int sub(int i, int j) {
        return i - j;
    }

    @Override
    public int mul(int i, int j) {
        return i * j;
    }

    @Override
    public int div(int i, int j) {
        return i * j;
    }
}

前置通知

    编写一个日志切面类,提供前置通知:

package cn.net.bysoft.lesson7;

import java.util.Arrays;

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

@Aspect
@Component
public class LoggingAspect {
    // 调用前
    @Before("execution(public int cn.net.bysoft.lesson7.Arithmentic.*(int, int))")
    public void beforeMethod(JoinPoint joinPoint) {
        System.out.println("The method " + joinPoint.getSignature().getName()
                + " begins with" + Arrays.asList(joinPoint.getArgs()));
    }
}

    其中beforeMethod使用@Before装饰,代表前置通知,在方法调用之前执行,@Before的参数是代表对cn.net.bysoft.lesson7这个包中的Arithmentic类的所有方法进行前置通知。

    参数JoinPoint是切面的连接点,可以获得执行的方法的信息,如名称和参数值等,在前置通知时获得不到返回值,因为Arithmentic的方法还未执行。

    编写测试类:

package cn.net.bysoft.lesson7;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Lesson7Test {

    private AbstractApplicationContext ctx;

    @Before
    public void initApplicationContext() {
        // 创建Spring容器
        ctx = new ClassPathXmlApplicationContext(
                "applicationContext-lesson7.xml");
    }

    @Test
    public void test(){
        Arithmentic arithmentic = ctx.getBean(Arithmentic.class);
        int result = arithmentic.add(3, 6);
        System.out.println(result);
        ctx.close();
    }

}


后置通知

    加入后置通知,使用@After修饰方法,后置通知表示无论调用的方法是否异常,都会执行:

// 调用后,无论是否异常
    @After("execution(public int cn.net.bysoft.lesson7.Arithmentic.*(int, int))")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("The method " + joinPoint.getSignature().getName()
                + " begins with" + Arrays.asList(joinPoint.getArgs()));
    }
@Test
    public void testAfter(){
        Arithmentic arithmentic = ctx.getBean(Arithmentic.class);
        int result = arithmentic.div(3, 6);
        System.out.println(result);
        ctx.close();
    }


    进行测试,是否如果方法发生异常,也会执行后置通知,将除数设置为0:

异常通知

    若方法如上面的代码会发生异常,也可以进行通知。使用@AfterThrowing修饰方法,value等于要调用的方法,throwing等于异常,如下:

@AfterThrowing(value = "execution(public int cn.net.bysoft.lesson7.Arithmentic.*(int, int))", throwing = "ex")
    public void exceptionMethod(JoinPoint joinPoint, Exception ex) {
        System.out.println("The method " + joinPoint.getSignature().getName()
                + " exception: " + ex);
    }

返回通知

    当方法执行成功,没有发生异常,并返回结果,可以使用返回通知进行日志输出。使用@AfterReturning修饰方法,value等于要进行切面的方法,returing等于返回值,如下:

    // 返回通知
    @AfterReturning(value = "execution(public int cn.net.bysoft.lesson7.Arithmentic.*(int, int))", returning = "result")
    public void returnMethod(JoinPoint joinPoint, Object result) {
        System.out.println("The method " + joinPoint.getSignature().getName()
                + " returning: " + result);
    }


环绕通知

    环绕通知功能很强大,可以将上面的几种通知进行自定义控制,它相当于我们编写的动态代理,使用@Around对方法进行修饰,传递一个ProceedingJoinPoint方法,可以决定是否决定执行目标方法,并且环绕通知必须有返回值,该返回值即为目标方法的返回值。代码如下:

// 环绕通知
    @Around("execution(public int cn.net.bysoft.lesson7.Arithmentic.*(int, int))")
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("aroundMethod");
        return 100;
    }

    为了区分环绕通知与之前的通知,现将之前的通知代码注释掉:


    结果为100,从此处可以看出环绕通知功能非常强大,接下来,完成环绕通知的代码,调用参数proceedingJoinPoint对象的proceed方法,可以执行目标方法,并获得返回值,并在该方法之前、之后、异常和返回时加入想要的通知:

// 环绕通知
    @Around("execution(public int cn.net.bysoft.lesson7.Arithmentic.*(int, int))")
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
        Object result = null;
        String methodName = proceedingJoinPoint.getSignature().getName();
        try {
            // 前置通知。
            System.out.println("after " + methodName);
            result = proceedingJoinPoint.proceed();
            // 返回通知。
            System.out.println("returing " + methodName);
        } catch (Throwable e) {
            // 异常通知。
            System.out.println("exception " + e);
            throw new RuntimeException(e);
        }
        // 后置通知。
        System.out.println("afte " + methodName);
        return result;
    }

测试一下,执行目标方法,不带有异常,结果如下:


    在测试一个目标方法发生异常的情况:


    以上,就是spring常用的5种aop通知。

切面的优先级

    如果有多个切面类,比如一个用来输出日志,一个用来验证参数是否合法,还有一些其他的……如何设置切面的优先级,比如验证参数的切面类要在输出日志的切面类之前执行,具体来看代码,编写一个验证切面类:

package cn.net.bysoft.lesson7;

import java.util.Arrays;

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

@Aspect
@Component
public class ValidationAspect {

    @Before("execution(public int cn.net.bysoft.lesson7.Arithmentic.*(int, int))")
    public void beforeMethod(JoinPoint joinPoint) {
        System.out.println("validate args" + joinPoint.getSignature().getName()
                + " begins with" + Arrays.asList(joinPoint.getArgs()));
    }
}

进行测试:


    目前日志切面类在验证切面类之前执行,可以使用@Order对切面类进行修饰,输入需要进行排序:

@Order(1)
public class ValidationAspect {
@Order(2)
public class LoggingAspect {

    再一次进行测试:

    以上就是spring的aop的简单使用。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!