Spring 详解(二)------- AOP关键概念以及两种实现方式



### 1. AOP 关键词
- target:目标类,需要被代理的类。例如:ArithmeticCalculator - Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法 - PointCut 切入点:已经被增强的连接点。例如:add() - advice:通知/增强,增强代码。例如:showRaram、showResult - Weaving(织入):是指把增强 advice 应用到目标对象 target 来创建新的代理对象proxy的过程. - proxy 代理类:通知+切入点 - Aspect(切面)::是切入点 pointcut 和通知 advice 的结合

2. AOP 的作用


当我们为系统做参数验证,登录权限验证或者日志操作等,为了实现代码复用,我们可能把日志处理抽离成一个新的方法。但是这样我们仍然必须手动插入这些方法,这样的话模块之间高耦合,不利于后期的维护和功能的扩展,有了 AOP 我们可以将功能抽成一个切面,代码复用好,低耦合。

3. AOP 的通知类型


Spring 按照通知 Advice 在目标类方法的连接点位置,可以分为5类 - 前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。 - 正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。 - 异常返回通知[After throwing advice]:在连接点抛出异常后执行。 - 返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。 - 环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
Spring 中使用五种通知
1. 前置通知
    <aop:before method="" pointcut="" pointcut-ref=""/>
        method : 通知,及方法名
        pointcut :切入点表达式,此表达式只能当前通知使用。
        pointcut-ref : 切入点引用,可以与其他通知共享切入点。
    通知方法格式:public void myBefore(JoinPoint joinPoint){
        参数1:org.aspectj.lang.JoinPoint  用于描述连接点(目标方法),获得目标方法名等
2. 后置通知  目标方法后执行,获得返回值
    <aop:after-returning method="" pointcut-ref="" returning=""/>
        returning 通知方法第二个参数的名称
   通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object result){
        参数1:连接点描述
        参数2:类型Object,参数名 returning="result" 配置的
3. 异常通知  目标方法发生异常后
    <aop:after-throwing method="testException" throwing="e"
    pointcut="execution(* com.anqi.testAop.ArithmeticCalculator.div(..))"/>
        throwing 发生的异常
   通知方法格式:public Object testRound(ProceedingJoinPoint pjp){
        参数1:ProceedingJoinPoint
        返回值为 reslut

### 4. 基于 xml 的配置方式 xml 配置文件
<context:component-scan base-package="com.anqi">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--1、 创建目标类 -->
<bean id="arithmeticCalculator" class="com.anqi.testAop.ArithmeticCalculatorImpl"></bean>
<!--2、创建切面类(通知)  -->
<bean id="logAspect" class="com.anqi.testAop.MyLogger"></bean>
<aop:config>
    <aop:aspect ref="logAspect">
        <!-- 切入点表达式 也可以在通知内部分别设置切入点表达式 -->
        <aop:pointcut expression="execution(* com.anqi.testAop.*.*(..))" id="myPointCut"/>
        <!-- 配置前置通知,注意 method 的值要和 对应切面的类方法名称相同 -->
        <aop:before method="before" pointcut-ref="myPointCut" />
        <aop:after method="after" pointcut-ref="myPointCut" />
        <aop:after-returning method="testAfterReturn" returning="result" pointcut-ref="myPointCut"/>
        <aop:after-throwing method="testException" throwing="e" pointcut="execution(* com.anqi.testAop.ArithmeticCalculator.div(..))"/>
        <!--<aop:around method="testRound"  pointcut-ref="myPointCut"  /> 最强大,但是一般不使用-->
    </aop:aspect>
</aop:config>

目标类

public interface ArithmeticCalculator {
    int add(int i, int j);
    int sub(int i, int j);

    int mul(int i, int j);
    int div(int i, int j);
}

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

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

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

    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}

切面类 ``` java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import java.util.Arrays;

/**

  • 创建日志类
    */
    public class MyLogger {

    public void before(JoinPoint joinPoint) {
    System.out.println("前置通知 参数为["+joinPoint.getArgs()[0]+","+joinPoint.getArgs()[1]+"]");
    }
    public void after(JoinPoint joinPoint) {
    System.out.println("后置通知 "+ joinPoint.getSignature().getName());
    }

    public void testException(JoinPoint joinPoint, Throwable e) {
    System.out.println("抛出异常: "+ e.getMessage());
    }

    public void testAfterReturn(JoinPoint joinPoint, Object result) {
    System.out.println("返回通知,返回值为 " + result);
    }

    public Object testRound(ProceedingJoinPoint pjp) {
    Object result = null;
    String methodName = pjp.getSignature().getName();
    Object[] args = pjp.getArgs();
    try {
    //前置通知
    System.out.println("!!!前置通知 --> The Method"+methodName+" begins"+ Arrays.asList(args));
    //执行目标方法
    result = pjp.proceed();
    //返回通知
    System.out.println("!!!返回通知 --> The Method"+methodName+" ends"+ args);

     }catch(Throwable e) {
         //异常通知
         System.out.println("!!!异常通知 --> The Method"+methodName+" ends with"+ result);
     }
     //后置通知
     System.out.println("!!!后置通知 --> The Method"+methodName+" ends"+ args);
     return result;
    

    }
    }

<br/>
测试
``` java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
    public static void main(String[] args) {
        ApplicationContext application = new ClassPathXmlApplicationContext("spring-context.xml");
        ArithmeticCalculator a = application.getBean(ArithmeticCalculator.class);
        int result = a.add(1,2);
        System.out.println(result);
        System.out.println(a.div(5,0));
    }
}
/*
    前置通知 参数为[1,2]
    后置通知 add
    返回通知,返回值为 3
    3
    前置通知 参数为[5,0]
    后置通知 div
    抛出异常: / by zero
*/

5. 基于注解的配置方式

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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.anqi">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!-- 使 AspectJ 注解起作用: 自动为匹配的类生成代理对象 -->
    <aop:aspectj-autoproxy/>
</beans>

目标类 ``` java public interface ArithmeticCalculator { int add(int i, int j); int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);

}
import org.springframework.stereotype.Service;

@Service
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}

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

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

@Override
public int div(int i, int j) {
    int result = i / j;
    return result;
}

}

<br>
切面
``` java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;

/**
 * 创建日志类
 */
@Aspect
@Component
public class MyLogger {

    @Before("execution(* com.anqi.testAop.*.*(..))")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置通知 参数为["+joinPoint.getArgs()[0]+","+joinPoint.getArgs()[1]+"]");
    }
    @After("execution(* com.anqi.testAop.*.*(..))")
    public void after(JoinPoint joinPoint) {
        System.out.println("后置通知 "+ joinPoint.getSignature().getName());
    }

    @AfterThrowing(value="execution(* com.anqi.testAop.ArithmeticCalculator.div(..))", throwing = "e")
    public void testException(JoinPoint joinPoint, Throwable e) {
        System.out.println("抛出异常: "+ e.getMessage());
    }

    @AfterReturning(value="execution(* com.anqi.testAop.*.*(..))", returning = "result")
    public void testAfterReturn(JoinPoint joinPoint, Object result) {
        System.out.println("返回通知,返回值为 " + result);
    }

    @Around("execution(* com.anqi.testAop.*.*(..))")
    public Object testRound(ProceedingJoinPoint pjp) {
        Object result = null;
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        try {
            //前置通知
            System.out.println("!!!前置通知 --> The Method"+methodName+" begins"+ Arrays.asList(args));
            //执行目标方法
            result = pjp.proceed();
            //返回通知
            System.out.println("!!!返回通知 --> The Method"+methodName+" ends"+ args);

        }catch(Throwable e) {
            //异常通知
            System.out.println("!!!异常通知 --> The Method"+methodName+" ends with"+ result);
        }
        //后置通知
        System.out.println("!!!后置通知 --> The Method"+methodName+" ends"+ args);
        return result;
    }
}


输出结果与第一种方式一致,这里就不再赘述了。

6. 切面的优先级


可以使用@Order来指定切面的优先级 ``` java //参数验证切面 @Order(1) @Aspect @Component public class ValidateAspect {

@Before("execution(public int com.anqi.spring.aop.order.ArithmeticCalculator.*(int, int))")
public void validateArgs(JoinPoint join) {
String methodName = join.getSignature().getName();
Object[] args = join.getArgs();
System.out.println("validate"+methodName+Arrays.asList(args));
}
}

//把这个类声明为一个切面:需要把该类放入到 IOC 容器中, 再声明为一个切面
@Order(2)
@Aspect
@Component
public class LoggingAspect2 {

/**