1、为什么要使用AOP

  • 实验类:

    package com.xianhuii;
    
    public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
        @Override
        public int add(int i, int j) {
            // 非业务需求1
            System.out.println("The method add begins with [" + i + ", " + j + "]");
            // 业务代码
            int result = i + j;
            // 非业务需求2
            System.out.println("The method add edns with [" + result + "]");
            return result;
        }
    
        @Override
        public int sub(int i, int j) {
            // 非业务需求1
            System.out.println("The method sub begins with [" + i + ", " + j + "]");
            // 业务代码
            int result = i - j;
            // 非业务需求2
            System.out.println("The method sub edns with [" + result + "]");
            return result;
        }
    
        @Override
        public int mul(int i, int j) {
            // 非业务需求1
            System.out.println("The method mul begins with [" + i + ", " + j + "]");
            // 业务代码
            int result = i * j;
            // 非业务需求2
            System.out.println("The method mul edns with [" + result + "]");
            return  result;
        }
    
        @Override
        public int div(int i, int j) {
            // 非业务需求1
            System.out.println("The method div begins with [" + i + ", " + j + "]");
            // 业务代码
            int result = i / j;
            // 非业务需求2
            System.out.println("The method div edns with [" + result + "]");
            return result;
        }
    
    }
  • 问题:

    • 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
    • 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块、方法里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。

2 动态***解决问题

  • 接口:

    package com.xianhuii;
    
    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);
    }
  • 业务类:

    package com.xianhuii;
    
    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;
        }
    
    }
  • 动态***类:

    package com.xianhuii;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    
    public class ArithmeticCalculatorLogginProxy {
        // 被***的对象(目标对象)
        private ArithmeticCalculator target;
    
        // 通过构造器的方式将目标对象传入
        public ArithmeticCalculatorLogginProxy( ArithmeticCalculator target) {
            this.target = target;
        }
    
        // 获取***对象
        public ArithmeticCalculator getLoggingProxy() {
            // 定义***对象
            ArithmeticCalculator proxy;
    
            /**
             * loader:ClassLoader(类加载器)
             * interfaces:目标类的所有接口,目的是获取接口中的方法
             * handler:InvocationHandler
             */
            ClassLoader loader = target.getClass().getClassLoader();
            Class<?>[] interfaces = target.getClass().getInterfaces();
            InvocationHandler handler = new InvocationHandler() {
                /**
                 *
                 * @param proxy:***对象,在invoke方法中一般不会用
                 * @param method:正在调用的方法
                 * @param args:调用方法传入的参数
                 * @return
                 * @throws Throwable
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    String methodName = method.getName();
                    // 加日志
                    System.out.println("The method " + methodName + " begins with" + Arrays.asList(args));
                    // 执行目标方法
                    Object result = method.invoke(target, args);
                    // 加日志
                    System.out.println("The method " + methodName + " ends with " + result);
                    return result;
                }
            };
            proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, handler);
            return proxy;
        }
    
    }
  • 测试:

    package com.xianhuii.test;
    
    import com.xianhuii.ArithmeticCalculator;
    import com.xianhuii.ArithmeticCalculatorImpl;
    import com.xianhuii.ArithmeticCalculatorLogginProxy;
    
    public class SpringTest {
        public static void main(String[] args) {
            // 目标对象
            ArithmeticCalculator target = new ArithmeticCalculatorImpl();
            // 获取***对象
            ArithmeticCalculator proxy = new ArithmeticCalculatorLogginProxy(target).getLoggingProxy();
            int result = proxy.add(1, 1);   // 实际上要去执行invoke()方法
            System.out.println("结果" + result);
        }
    }
  • 测试结果:

图片说明

3 Spring AOP相关概念

图片说明

  • 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象(整合了一系列通知的类的对象)。
  • 通知(Advice):切面必须要完成的功能(特定的非业务方法,如日志)。
  • 目标(Target):被通知的对象(业务类对象)。
  • (Proxy):向目标对象应用通知之后创建的对象(类对象)。
  • 连接点(Joinpoint):程序指定的某个特定位置,如某个方法调用前、调用后、方法抛出异常后。连接点由两个信息确定:方法表示的程序执行点、相对点表示的方位。
  • 切点(Pointcut):每个类都有多个连接点。AOP通过切点定位到特定的连接点。切点使用类和方法作为连接点的查询条件。

4 使用AspectJ注解声明切面

  • 要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例(@Component)。当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与AspectJ切面向匹配的Bean创建***。

  • 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类。

  • 通知是标注有某种注解的简单的Java方法。

  • AspectJ支持5种类型的通知注解:

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

    aspectjweaver.jar
  • 配置:

    <?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                           
                               https://www.springframework.org/schema/context/spring-context.xsd      
                               http://www.springframework.org/schema/aop
                               https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.xianhuii"></context:component-scan>
        <!-- 基于注解开发,开启aop自动proxy -->
        <aop:aspectj-autoproxy/>
    
    </beans>
  • 目标类接口:

    package com.xianhuii;
    
    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);
    }
  • 目标类:

    package com.xianhuii;
    
    import org.springframework.stereotype.Component;
    
    @Component
    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;
        }
    
    }
  • 切面类:

    package com.xianhuii;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    
    /**
    
     * 日志切面
       */
       @Component  // 标识为组件
       @Aspect // 标识为切面
       public class LoggingAspect {
       /**
    
        * 通知:前置、后置、返回、异常、环绕
          */
    
       /**
    
        * 前置通知:在方法执行之前执行。
        * JoinPoint:连接点对象,包含了连接点相关信息:方法名、方法参数等。
          */
          //    @Before("execution(public int com.xianhuii.ArithmeticCalculator.add(int,int ))") 指定方法
          //    @Before("execution(public int com.xianhuii.ArithmeticCalculator.*(int,int ))") 指定类下的所有方法
          @Before("execution(* com.xianhuii.*.*(..))") // 任意修饰符任意返回值 com.xianhuii包下的任意类中的任意方法(任意形参)
          public void beforeMethod(JoinPoint joinPoint) {
          // 获取方法名
          String methodName = joinPoint.getSignature().getName();
          // 获取参数
          Object[] args = joinPoint.getArgs();
          System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
          }
    
       /**
    
        * 后置通知:在方法执行后执行,不管方法有没有抛出异常。
        * 后置通知访问不到方法的返回值。
          */
          @After("execution(* com.xianhuii.*.*(..))")
          public void afterMethod(JoinPoint joinPoint) {
          String methodName = joinPoint.getSignature().getName();
          System.out.println("The methods " + methodName + " ends.");
          }
    
       /**
    
        * 返回通知:在方法正常执行返回结果以后执行。
        * 返回通知可以访问到方法的返回值。
          */
          @AfterReturning(value = "execution(* com.xianhuii.*.*(..))", returning = "result")
          public void afterReturningMethod(JoinPoint joinPoint, Object result) {
          String methodName = joinPoint.getSignature().getName();
          System.out.println("The method " + methodName + " ends with " + result);
          }
    
       /**
    
        * 异常通知:方法执行过程中抛出异常后执行的。
        * 可以指定抛出特定的异常再执行异常通知。在形参的位置指定异常的类型即可。
          */
          @AfterThrowing(value = "execution(* com.xianhuii.*.*(..))", throwing = "ex")
          public void afterThrowingMethod(JoinPoint joinPoint, ArithmeticException ex) {
          String methodName = joinPoint.getSignature().getName();
          System.out.println("The method " + methodName + " occurs exception: " + ex.getMessage());
          }
    
       /**
    
        * 环绕通知:类似于动态***的整个过程。
          */
          @Around("execution(* com.xianhuii.*.*(..))")
          public Object aroundMethod(ProceedingJoinPoint pjp) {
          String methodName = pjp.getSignature().getName();
          Object[] args = pjp.getArgs();
          try {
              // 前置通知
              System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
              // 执行目标方法
              Object result = pjp.proceed();
              // 返回通知
              System.out.println("The method " + methodName + " ends with " + result);
              return result;
          } catch (Throwable throwable) {
              // 异常通知
              System.out.println("The method " + methodName + " occues exception: " + throwable);
              throwable.printStackTrace();
          } finally {
              System.out.println("The method " + methodName + " ends.");
          }
          return null;
          }
          }
  • 测试:

    package com.xianhuii.test;
    
    import com.xianhuii.ArithmeticCalculator;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class SpringTest {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            ArithmeticCalculator calculator = (ArithmeticCalculator) context.getBean("arithmeticCalculatorImpl");
            System.out.println(calculator.getClass());  // ***对象:class com.sun.proxy.$Proxy14
            int result = calculator.div(1, 1);
            System.out.println("AspectJ result: " + result);
        }
    }

5 切面优先级

  • @Order:指定切面的优先级,值越小,优先级越高。

  • 标注@Order的切面比不标注@Order切面的优先级高。

  package com.xianhuii;

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

  import java.util.Arrays;

  @Component
  @Aspect
  @Order// 数值越小优先级越高
  public class ValidatorAspect {
      @Before("execution(* com.xianhuii.*.*(..))")
      public void beforeMethod(JoinPoint joinPoint) {
          // 获取方法名
          String methodName = joinPoint.getSignature().getName();
          // 获取参数
          Object[] args = joinPoint.getArgs();
          System.out.println("ValidatorAspect: the method " + methodName + " begins with " + Arrays.asList(args));
      }
  }

6 重用切入点表达式

  • @Pointcut("execution(* com.xianhuii..(..))")。

  • 使用本类中切入点表达式:

    package com.xianhuii;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    
    @Component
    @Aspect
    @Order   // 数值越小优先级越高
    public class ValidatorAspect {
        /**
         * 重用切入点表达式
         */
        @Pointcut("execution(* com.xianhuii.*.*(..))")
        public void suibain() {
        }
    
        @Before("suibain()")
        public void beforeMethod(JoinPoint joinPoint) {
            // 获取方法名
            String methodName = joinPoint.getSignature().getName();
            // 获取参数
            Object[] args = joinPoint.getArgs();
            System.out.println("ValidatorAspect: the method " + methodName + " begins with " + Arrays.asList(args));
        }
    
    }
  • 使用其他类的切入点表达式:

    package com.xianhuii;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    @Component  // 标识为组件
    @Aspect // 标识为切面
    public class LoggingAspect {
    
        @Before("com.xianhuii.ValidatorAspect.suibain()")
        public void beforeMethod(JoinPoint joinPoint) {
        }
    
    }

7 使用XML配置切面

  • 目标类接口:

    package com.xianhuii;
    
    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);
    }
  • 目标类:

    package com.xianhuii;
    
    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;
        }
    
    }
  • 切面类:

    package com.xianhuii;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    import java.util.Arrays;
    
    /**
    
     * 日志切面
       */
       public class LoggingAspect {
    
       public void beforeMethod(JoinPoint joinPoint) {
           // 获取方法名
           String methodName = joinPoint.getSignature().getName();
           // 获取参数
           Object[] args = joinPoint.getArgs();
           System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
       }
    
       public void afterMethod(JoinPoint joinPoint) {
           String methodName = joinPoint.getSignature().getName();
           System.out.println("The methods " + methodName + " ends.");
       }
    
       public void afterReturningMethod(JoinPoint joinPoint, Object result) {
           String methodName = joinPoint.getSignature().getName();
           System.out.println("The method " + methodName + " ends with " + result);
       }
    
       public void afterThrowingMethod(JoinPoint joinPoint, ArithmeticException ex) {
           String methodName = joinPoint.getSignature().getName();
           System.out.println("The method " + methodName + " occurs exception: " + ex.getMessage());
       }
    
       public Object aroundMethod(ProceedingJoinPoint pjp) {
           String methodName = pjp.getSignature().getName();
           Object[] args = pjp.getArgs();
           try {
               // 前置通知
               System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
               // 执行目标方法
               Object result = pjp.proceed();
               // 返回通知
               System.out.println("The method " + methodName + " ends with " + result);
               return result;
           } catch (Throwable throwable) {
               // 异常通知
               System.out.println("The method " + methodName + " occues exception: " + throwable);
               throwable.printStackTrace();
           } finally {
               System.out.println("The method " + methodName + " ends.");
           }
           return null;
       }
       }
  • 配置:

    <?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 配置切面 -->
        <bean id="loggingAspect" class="com.xianhuii.LoggingAspect"></bean>
        <bean id="validatorAspect" class="com.xianhuii.ValidatorAspect"></bean>
    
        <!-- 配置目标bean -->
        <bean id="arithmeticCalculatorImpl" class="com.xianhuii.ArithmeticCalculatorImpl"></bean>
    
        <!-- 配置aop -->
        <aop:config>
            <!-- 配置切入点表达式 -->
            <aop:pointcut id="myPointCut" expression="execution(* com.xianhuii.*.*(..))"></aop:pointcut>
            <!-- 配置切面及通知 -->
            <aop:aspect ref="loggingAspect" order="1">
                <!--<aop:before method="beforeMethod" pointcut="execution(* com.xianhuii.*.*(..))"/>-->
                <aop:before method="beforeMethod" pointcut-ref="myPointCut"/>
                <aop:after method="afterMethod" pointcut-ref="myPointCut"/>
                <aop:after-returning method="afterReturningMethod" pointcut-ref="myPointCut" returning="result"/>
                <aop:after-throwing method="afterThrowingMethod" pointcut-ref="myPointCut" throwing="ex"/>
            </aop:aspect>
            <aop:aspect ref="validatorAspect" order="2"></aop:aspect>
        </aop:config>
    
    </beans>