AOP作为OOP的一个强有力补充,发展到现在基本属于静态语言的标配了,君不见Java程序员谈及Spring,必提及IOC和AOP,只是相较于OOP中涉及到的专有名词,AOP相关的术语离我们日常生活比较遥远,诸如连接点(Joinpoint),切点(Pointcut),增强(Advice)等等,让第一次接触到的人难免有种丈二和尚摸不着头脑的感觉。
本文意图改变以往的先背诵相关术语,然后去结合实际例子,接着不断重复以上步骤以求理解的套路,转而直接从使用者的角度出发,由需求反推专有名词意义的方式来加快对AOP基础的理解。
假设有包含两个方法的一个类:
@Component
public class ClassBeAOP {
public void doing() {
System.out.println("doing somthing");
}
public void doing2() {
System.out.println("doing somthing");
}
}
现在我们的需求是要对其中的doing()
方法进行一些强化,比如在方法执行前输出一段日志。(在正式开始论述AOP专有名称前,我将尽量避免它们的出现)。
正式开始前,先让我们来捋下上面需求的一些关键点:
ClassBeAOP
类的doing()
方法,其执行前。以上两条其实就是AOP相关基础术语的所要定义的全部内容,接下来就让我们结合AOP术语进行逐条分析:
关于对AOP生效位置的精确定位,我们需要将其拆分为两部分:编译时位置,运行时位置(注意这两个位置是笔者自己发明的,读者不要过于纠结,会在接下来进行解释)。
在本例中,编译位置为 ClassBeAOP
类的doing()
方法 , 而在AOP中被定义为 切点(Pointcut)。
额外说明:
@Pointcut()
来声明。对于作强化之用的额外代码,在AOP术语中就被称为 增强(Advice)。
额外说明:
以上我们一直在强调AOP切入点,那么势必存在一系列的备选点,而这些备选点在AOP术语中就被称为连接点(Joinpoint)。
额外说明:
@Before()
, @After()
,,@AfterReturning()
,@AfterThrowing()
, @Around()
来定义连接点。切面由切点和增强组成, 它既包括横切逻辑的定义, 也包括连接点的定义。
织入是将增强添加到目标类的具体连接点上的过程。
一共有三种织入方式:
以上,Spring采用3,而Aspect采用1和2。
对于其他术语,这里我们直接一笔带过:
增强逻辑的织入到的目标类。
引介是一种特殊的增强, 其为类添加额外的属性和方法. 这样可以为业务类动态添加接口的实现, 让业务类成为该接口的实现类。
一个类被AOP织入增强后, 产生的结果类就是糅合了原有逻辑和增强逻辑的代理类。
以下是使用Spring的方式定义一个切面,对上面的ClassBeAOP
进行AOP,以满足我们的需求。
@Component
@Aspect
public class ServiceAspect {
// 配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
// 这里结合上面的讲解, @Pointcut只是确定了编译时位置, 我们还需要运行时位置, 也就是这里确定的方法的切入时机——是执行之前, 还是执行之后等等
@Pointcut("execution(* xx.yy.zz.ClassBeAOP.doing(..))")
public void aspect() {
}
// 配置前置通知,使用在方法aspect()上注册的切入点 同时接受JoinPoint切入点对象,可以没有该参数
// 这里的 @Before 正是确定了运行时位置 --- 执行前
@Before("aspect()")
public void before(JoinPoint joinPoint) {
// 这里的代码块就是 增强(Advice) 了。
System.out.println("before!!!!!!!!");
}
}
单元测试结果如下:
ClassBeAOP bean = bf.getBean("classBeAOP", ClassBeAOP.class);
/*
输出
before!!!!!!!!
doing
*/
bean.doing();
/*
输出
doing2
*/
bean.doing2();
最后我们结合Spring 来总结下AOP术语:
@Pointcut()
(切点(Advice))来确定AOP生效时的编译时位置。@Before()
来确定AOP生效时的运行时位置。@Before()
修饰的方法里就是增强代码逻辑的增强(Advice)。@Pointcut()
,而仅仅只需要借助@Before()
即可同时完成编译时位置(@Before()
中的 value()
值钱,)和运行时位置(@Before()
代表的方法执行前),以及作为增强的@Before()
修饰的方法体。