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);
}
- 通知执行顺序
正常情况:

异常情况:
