spring aop 从入门到精通

github链接双手奉上:
https://github.com/Evan2021Evan/test

  • 从实战出发

  没用aop之前:

@Service
public class TestService {
    public void doSomething1() {
        beforeLog();
        System.out.println("==doSomething1==");
        afterLog();
    }
    public void doSomething2() {
        beforeLog();
        System.out.println("==doSomething1==");
        afterLog();
    }
    public void doSomething3() {
        beforeLog();
        System.out.println("==doSomething1==");
        afterLog();
    }
    public void beforeLog() {
        System.out.println("打印请求日志");
    }
    public void afterLog() {
        System.out.println("打印响应日志");
    }
}

如果加了新doSomethingXXX方法,就需要在新方法前后手动加beforeLog和afterLog方法。
原本相安无事的,但长此以往,总有会出现几个刺头青。
刺头青A说:每加一个新方法,都需要加两行重复的代码,是不是很麻烦?
刺头青B说:业务代码和公共代码是不是耦合在一起了?
刺头青C说:如果有几千个类中加了公共代码,而有一天我需要删除,是不是要疯了?
spring大师们说:我们提供一套spring的aop机制,你们可以闭嘴了。

  下面看看用spring aop(偷偷说一句,还用了aspectj)是如何打印日志的:

@Service
public class TestService {
    public void doSomething1() {
        System.out.println("==doSomething1==");
    }
    public void doSomething2() {
        System.out.println("==doSomething1==");
    }
    public void doSomething3() {
        System.out.println("==doSomething1==");
    }
}
@Component
@Aspect
public class LogAspect {
    @Pointcut("execution(public * com.sue.cache.service.*.*(..))")
    public void pointcut() {
    }
    @Before("pointcut()")
    public void beforeLog() {
        System.out.println("打印请求日志");
    }
    @After("pointcut()")
    public void afterLog() {
        System.out.println("打印响应日志");
    }
}

  增加了LogAspect类,在类上加了@Aspect注解。先在类中使用@Pointcut注解定义了pointcut方法,然后将beforeLog和afterLog方法移到这个类中,分别加上@Before和@After注解。
改造后,业务方法在TestService类中,而公共方法在LogAspect类中,是分离的。如果要新加一个业务方法,直接加就好,LogAspect类不用改任何代码,新加的业务方法就自动拥有打印日志的功能,是不是很神奇?
spring aop其实是一种横切的思想,通过动态代理技术将公共代码织入到业务方法中。

此时,一个黑影一闪而过。
刺头青D问:你说的“横切”,“动态代理”,“织入” 是什么东东?

  • 先了解重要的术语

连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。
切点(Pointcut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。
通知(Advice) 增强是织入到目标类连接点上的一段程序代码。
切面(Aspect) 切面由切点和通知组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
目标对象(Target) 需要被增强的业务对象。
代理类(Proxy) 一个类被AOP织入增强后,就产生了一个代理类。
织入(Weaving) 织入就是将增强添加到对目标类具体连接点上的过程。

  • 大白话解释

故事一:
  比如生活中我们裤裆子破了,我们需要加一块厚布子缝上去,增强它,我们可以在裤子的任何地方都能缝,这些所有的地方叫做连接点(可以被缝,可以被增强)。
但我们只缝裤裆子那一块,那块地方就叫做切入点(我们要增强的地方),我们增强了切入点(裤裆子),就会让它变的更耐磨。
我们要给裤裆子缝一个更厚的布,那块布就叫做Advice(增强的东西)最后我们缝裤子增强裤裆子的过程 ,叫做织入最后缝好的裤裆那一块,叫做切面,这个面让我们增强了。核心是采用的动态代理模式。

故事二:
  比如现在我有一个任务,就是给小区里所有的单元门贴声明(声明内容是告诉大家明天停电)
那么现在这个小区的单元门就是切入点(我选择的点)。
那么除了单元门,还有楼梯口,公示栏等,这些都是连接点(可以被选择)。
这个声明就是通知。单元门和声明一起构成了切面,把这个声明贴到单元门这个过程就是织入。

故事三:
  比如在餐馆里点菜,菜单有 10 个菜,这 10 个菜就是 JoinPoint,但我只点了带有萝卜名字的菜,那么带有萝卜名字这个条件就是针对 JoinPoint(10 个菜)的筛选条件,即 pointcut,最终只有胡萝卜,白萝卜这两个 JoinPoint 满足条件,然后我们就可以在吃胡萝卜前洗手(before advice),或吃胡萝卜后买单(after advice),也可以统计吃胡萝卜的时间(around advice),这些洗手,买单,统计时间的动作都是与吃萝卜这个业务动作解藕的,都是统一写在 advice 的逻辑里。

  • 还是那个刺头青D说(旁边:这位仁兄比较好学):spring aop概念弄明白了,挺简单的。@Pointcut注解的execution表达式刚刚看得我一脸懵逼,可以再说说吗,我请你吃饭?*

  • 切点表达式

该表达式的含义是:匹配访问权限是public,任意返回值,包名为:com.sue.cache.service,下面的所有类所有方法和所有参数类型。图中所有用表示,比如图中类名用.表示的是所有类

  其实spring支持9种表达式,execution只是其中一种。


  • 五种通知

@Before

该通知在方法执行之前执行,只需在公共方法上加@Before注解,就能定义前置通知.
(一般用于权限校验,参数校验)
@Before("pointcut()")
public void beforeLog(JoinPoint joinPoint) {
    System.out.println("打印请求日志");
}

@After

该通知在方法执行之后执行,只需在公共方法上加@After注解,就能定义后置通知:
(无论是否有异常都执行)
@After("pointcut()")
public void afterLog(JoinPoint joinPoint) {
    System.out.println("打印响应日志");
}

@Around

该通知在方法执行前后执行,只需在公共方法上加@Round注解,就能定义环绕通知:
(ProceedingJoinPoint )
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("打印请求日志");
    Object result = joinPoint.proceed();
    System.out.println("打印响应日志");
    return result;
}

@AfterReturning

该通知在方法结束后执行,能够获取方法返回结果,只需在公共方法上加@AfterReturning注解,就能定义结果通知:
(参数必须一致)
@AfterReturning(pointcut = "pointcut()",returning = "retVal")
public void afterReturning(JoinPoint joinPoint, Object retVal) {
    System.out.println("获取结果:"+retVal);
}

@AfterThrowing

该通知在方法抛出异常之后执行,只需在公共方法上加@AfterThrowing注解,就能定义异常通知:
(参数必须一致)
@AfterThrowing(pointcut = "pointcut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Throwable e) {
    System.out.println("异常:"+e);
}

  • 通知执行顺序

正常情况:


异常情况:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

相关阅读更多精彩内容

友情链接更多精彩内容