zoukankan      html  css  js  c++  java
  • 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 {

    /**