Spring

[Spring]Spring AOP基本概念介绍与入门

2021-02-13  本文已影响0人  AbstractCulture

1. Spring容器解决了什么问题

优点:

  1. 让代码架构自上到下实现了低耦合.Spring支持接口注入,变更代码逻辑时,只需要变更实现类即可做到无缝的切换.
  2. OOP将业务程序分解成各个层次的对象,通过对象联动完成业务

缺点:

  1. 无法处理分散在各个业务之间的通用系统需求.例如:代码日志、业务功能权限校验、缓存、事务管理...容易发生代码入侵,增加耦合度和维护成本.

2. 面向切面编程

Aspect Oriented Programming,面向切面编程,即我们常说的AOP.
它提供了一种在编译期间和运行期间对某个功能进行增强的技术,它是OOP的一种补充.在OOP中每个单元模块为Class,而AOP关注的单元模块为切面(Aspect).
举个例子:@Transactional这个关于事务管理的注解,在方法上进行标记,那么在这么方法的开始和结束都会进行一些事务的操作.

横切面

从图上可以看到,当addUser加上@Transactional,其中涉及数据库的操作就会被事务管理的类负责代理,进而实现功能上的增强.
Spring基于AspectJ开发了Spring AOP的框架,提供基于schema方式或者@AspectJ注解风格的切面编程能力.
其中,框架内部提供了一系列声明式的切面服务:例如@Transactional@Async@Cacheable...
另外,用户如果想自定义自己的切面,也可以使用Spring AOP框架进行编程.

注意,AOP是一种思想,并不是Spring所独有的.

2.1 切面编程中的概念

要了解AOP,需要先了解一下AOP中常说的概念:

2.1.1 Spring AOP中的Advice
Advice(通知) 描述
Before advice 前置通知,在连接点之前执行,不会影响整体的执行流程,除非抛出异常.
After returning advice 后置通知,在连接点正常返回之后执行(如果抛出了异常就不会执行此通知).
After throwing advice 在某个连接点抛出异常后执行
After (finally) advice 无论连接点是否正常执行,均会执行此通知(相当于try finally中的finally).
Around advice 环绕通知可以做到上述通知可以做到的事情.想象一下被代理的方法为a(),那么环绕可以对a()try catch finally,环绕通知是最常用的一种通知.

既然Around advice是覆盖所有advice的,那么为什么Spring AOP还需要声明这么多advice,官方的说法是建议使用最小的advice级别来满足你的需求.
打个比方:如果仅仅需要记录每个方法的入参做一个log操作,那么使用before advice就已经可以满足了,而不需要使用到around advice.

2.1.2 advice执行顺序

先说结论:Spring从5.2.7后对@After的执行顺序进行了调整.如图所示:
按官方的说法是跟随AspectJ的语义.
点我前往

5.2.7

在5.2.7之前,Spring AOP按照@Around,@Before,After,AfterReturning,AfterThrowing的顺序执行.

before_5.2.7

2.2 开始简单的AOP编程

package com.xjm.service.impl;

import com.xjm.service.HelloService;
import org.springframework.stereotype.Service;

/**
 * @author jaymin
 * 2020/11/26 17:04
 */
@Service
public class HelloServiceImpl implements HelloService {

    @Override
    public String hello() {
        return "Hello,Spring Framework!";
    }
}

package com.xjm.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.logging.Logger;

/**
 * @author jaymin. <br>
 * 系统基础切面,主要用于研究AOP.
 * 2021/2/12 20:56
 */
@Aspect
@Component
public class SystemServiceAspect {

    private static final Logger log = Logger.getGlobal();

    /**
     * 使用常量统一管理切入点
     */
    public static final String SYSTEM_SERVICE_POINT_CUT = "systemServicePointCut()";

    /**
     * <p>pointcut可以使用表达式来指定切入点.
     * <p>execution中的内容即为表达式.</p>
     * <p>* com.xjm..service..*.*(..)</p>
     * <p>表示,拦截com.xjm包下的service包中的所有类的所有方法(包括任意参数)
     */
    @Pointcut("execution(* com.xjm..service..*.*(..))")
    public void systemServicePointCut() {
    }

    /**
     * 在方法执行前进行切入
     * @param joinPoint
     */
    @Before(SYSTEM_SERVICE_POINT_CUT)
    public void before(JoinPoint joinPoint) {
        log.info("before method execute ");
    }

    /**
     * 环绕整个方法的执行
     * @param joinPoint
     * @return
     */
    @Around(SYSTEM_SERVICE_POINT_CUT)
    public Object around(JoinPoint joinPoint) {
        LocalDateTime startTime = LocalDateTime.now();
        log.info("around  method starts ");
        Object result;
        try {
            result = ((ProceedingJoinPoint) joinPoint).proceed();
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        } finally {
            LocalDateTime endTime = LocalDateTime.now();
            long executeTime = Duration.between(startTime, endTime).toMillis();
            log.info("method end.");
        }
        return result;
    }

    /**
     * 在方法返回值后进行切入
     * @param joinPoint
     * @param result
     */
    @AfterReturning(pointcut = SYSTEM_SERVICE_POINT_CUT, returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        log.info("method return");
    }

    /**
     * 在方法抛出异常后进行切入
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(pointcut = SYSTEM_SERVICE_POINT_CUT, throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Exception exception) {
        log.info("current method throw an exception,message");
    }

    /**
     * 获取连接点方法名
     * @param joinPoint
     * @return
     */
    public String getMethodName(JoinPoint joinPoint) {
        return ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
    }

    /**
     * 在方法执行后进行切入
     * @param joinPoint
     */
    @After(SYSTEM_SERVICE_POINT_CUT)
    public void after(JoinPoint joinPoint) {
        log.info("after method execute");
    }
}

注意,Spring是默认不开启AOP的,如果使用的是SpringBoot,需要在启动类上加上@EnableAspectJAutoProxy.

Result:

二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect around
信息: around  method starts 
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect before
信息: before method execute 
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect around
信息: method end.
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect after
信息: after method execute
二月 13, 2021 10:42:38 下午 com.xjm.aop.SystemServiceAspect afterReturning
信息: method return

笔者使用的是Spring 5.1.x版本

总结

上一篇下一篇

猜你喜欢

热点阅读