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

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中常说的概念:

  • Aspect: 切面,切面是AOP中封装切面逻辑的模块化单元.在Spring AOP中,切面往往以类或者XML的形式存在。
    打个比方,如果你需要对系统日志做一个切面处理,那么你就需要编写一个类来描述其中的逻辑,那么这个类就是Aspect.
  • Join point: 连接点,指程序执行中的一点。在Spring AOP中,连接点始终代表方法的执行.
  • Advice: 通知,即在特定的连接点切面执行的时机.Spring AOP提供了AroundBeforeAfter等切面逻辑执行的时机.
  • Pointcut: 切入点,连接点表示的是所有可以进行切面逻辑的地方,那么切入点则对应切面逻辑需要切入的地方,Spring AOP支持使用AspectJ的切入点表达式来指定切入点.
    这里可能会有点绕,这里举一个例子:假设一个类中有3个方法,那么这三个方法都可以作为切面的连接点,此时如果只需要对其中的一个方法做切面逻辑,那么这个方法就是我们的切入点了。
  • Introduction: 在某个被切面逻辑环绕的对象中引入新的接口.在Spring AOP中,可以通过introduction来将一个新的接口"引入"到被切入的类中.
  • Target object: 被切面逻辑环绕的对象。在Spring AOP中,被切面逻辑环绕的类通常是被代理的,也可以称之为被代理的对象。
  • AOP proxy: 由AOP框架创建出的对象,为了去实现AOP逻辑.在Spring AOP中,支持两种代理JDK动态代理CGLIB代理
  • Weaving: 将切面与被代理对象进行链接.Spring 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编程

  • 建立一个简单的service作为被代理对象
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");
    }
}

  • 执行hello方法

注意,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版本

总结

  • Spring AOP以可插拔的形式提供了面向切面编程的框架能力,以便对OOP做进一步的增强.
  • AOP是另一种编程思想,它拥有成熟的一套体系和自己的术语.
  • AOP可以通过预编译方式或者运行期动态代理的方式对程序功能进行织入.
  • Spring AOP仅支持方法执行连接点,内部使用了JDK动态代理和CGLIB代理对AOP进行支持.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容