SpringAop基础知识

我只是一个虾纸丫 提交于 2020-03-23 07:13:25

一:什么是AOP?

  AOP(Aspect Oriented Programming)被称为面向切面编程。

  AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。

  Aop 的作用在于分离系统中的核心关注点和横切关注点。业务逻辑不必关心日志、权限等横切关注点的处理,降低了程序的耦合度,提高可维护性;并且Aop提供了易配置、可插拔的特性,使用十分方便,是面向对象编程的有效补充。

二、AOP中的一些基础概念

  连接点:就是一个个方法,通俗来说,所有的方法都是连接点。(Spring AOP 只能使IOC容器中的Bean的方法)

  切入点:切入点是一个匹配连接点的断言或者表达式。通俗来说,就是在哪些连接点上来执行通知。

  通知:通知(advice)是你在你的程序中想要应用在连接点上应用横切关注点(日志、事务等)的实现。

  切面:其实就是共有功能的实现。如日志切面、权限切面、事务切面等。就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度的一个Java类。

  织入:将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。

  目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。

  代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。

三、SpringAop的五种通知类型

  1、前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用 @Before 注解使用这个Advice。

@Before(value = "point()")
public void before(JoinPoint joinPoint) {
    log.info("前置通知...");
    //获取目标方法的参数信息  
    Object[] objects = joinPoint.getArgs();
    //AOP代理类的信息  
    Object proxy = joinPoint.getThis();
    //代理的目标对象 
    Object target = joinPoint.getTarget();
    //通知的签名
    Signature signature = joinPoint.getSignature();
    //代理的方法名称
    String methodName = signature.getName();
    //AOP代理类的名字  
    String proxyClassName = signature.getDeclaringTypeName();
    //AOP代理类的类(class)信息  
    Class proxyClass = signature.getDeclaringType();

    //获取RequestAttributes
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    //从获取RequestAttributes中获取HttpServletRequest的信息(注意这里获取的是request域的信息)
    HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
    //从获取RequestAttributes中获取HttpSession的信息(注意这里获取的是session域的信息)
    HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);

    //获取请求参数
    Enumeration<String> enumeration = request.getParameterNames();
    Map<String, String> parameterMap = new HashMap<>();     //保存了请求参数信息
    while (enumeration.hasMoreElements()) {
        String parameter = enumeration.nextElement();
        parameterMap.put(parameter, request.getParameter(parameter));
    }
}

  2、返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过@AfterReturning 关注使用它。

/**
 * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
 */
@AfterReturning(value = "point()", returning = "keys")
public void afterReturn(JoinPoint joinPoint, Object keys) {
    log.info("后置返回通知...");
}
/**
 * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
 */
@AfterReturning(value = "point()", returning = "keys")
public void afterReturn(Object keys) {
    log.info("后置返回通知. 返回值为:" + keys);
}

另外,对于后置返回通知,通知方法的参数类型必须与代理目标方法的返回类型一致,否则后置返回通知不生效。当通知方法的入参为Object类型时将匹配任何目标返回值。

  3、抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用 @AfterThrowing 注解来使用。

@AfterThrowing(value = "point()", throwing = "exception")
public void afterThrow(JoinPoint joinPoint, Throwable exception) {
    if (exception instanceof NullPointerException) {
        log.info("发生了空指针异常!!!");
    }
    exception.printStackTrace();
}

  4、后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 @After 注解使用。

@After(value = "point()")
public void after(JoinPoint joinPoint) {
    log.info("后置最终通知...");
}

  5、围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过 @Around 注解使用。

  环绕通知使用一个代理ProceedingJoinPoint类型的对象来管理目标对象,所以此通知的第一个参数必须是ProceedingJoinPoint类型。在通知体内调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行。proceed()方法也可能会被调用并且传入一个Object[]对象,该数组中的值将被作为方法执行时的入参。

@Around(value = "point()")
public Object logMethodExecuteTime(ProceedingJoinPoint joinPoint) {
    Object object = null;
    long start = System.currentTimeMillis();   //joinPoint.proceed();执行前等于前置通知
    try {
        object = joinPoint.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();        //异常通知
    }
    String className = joinPoint.getTarget().getClass().getName();
    String methodName = joinPoint.getSignature().getName();
    log.info("execute " + className + ":" + methodName + " spend " + (System.currentTimeMillis() - start) + "ms.");
    //后置通知...
    return object;
}

  四、利用SpringAop记录方法执行时长

  1、本文使用自定义注解实现切点表达式,因此首先创建一个注解以及切点表达式。

  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  public @interface ExecuteTime {
  }
  @Pointcut(value = "@annotation(com.fcml.study.transaction.propagation.aop.annotation.ExecuteTime)")
  public void point() {
  }

  2、定义记录方法执行时长的切面逻辑

    @Around(value = "point()")
    public Object logMethodExecuteTime(ProceedingJoinPoint joinPoint) {
        Object object = null;
        long start = System.currentTimeMillis();
        try {
            object = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace(); 
        }
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        log.info("execute " + className + ":" + methodName + " spend " + (System.currentTimeMillis() - start) + "ms.");
        return object;
    }

  3、在需要记录的方法上加上注解即可。

  github: https://github.com/dwkfcml/Code-Fragment/tree/master/Aop/first-aop

 

  人们 
  高估了一天能做的事情 
  低估了一年能做的事情

  

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