前情提要
本文不是源码分析,只是试图用更立体的方式让各位看官对AOP有一些新的理解。
AOP,面向切面编程
AOP之名,在江湖上盛传已久,不论是初出茅庐的萌新,还是纵横江湖已久的高手都想探其究竟。陷足江湖的我不经想问AOP到时是什么?武林高手?绝世秘籍?为何如此引人注目
洪七公和御厨
这个疑惑终于在那次随着江湖大佬洪七公进入皇宫御膳房偷吃各大名菜时,得见宫廷御厨改进做菜方式后略有所悟。
宫廷名肴,不仅味道绝佳,制作方式也是工序繁多,由于洪七公经常偷入御膳房吃喝,搞得御膳房为了准时提供菜肴给宫中各位贵人享用,不得不加班加点(有时赶不及话还要被贵人责骂),御膳房总管因为此事是即忧伤又发愁,头发都要掉光了。
在多日苦想后,大总管认为只要能提高做菜的速度,多做一些菜,即使被偷吃掉一些也无妨。
那么怎么提高效率呢?
大总管发现,御膳房的御厨个个都是全能选手,从选材到刀工,从调味到烹煮无一不精,但是问题是御厨的数量并不多(综合能力极强,太难请),而且每个御厨同事只能做一两道菜,每道菜的耗时又很久,在原本勉强能供应上菜肴情况下,被七公偷吃掉一部分后,按时完成工作就岌岌可危了。
那么多找一些御厨?奈何大总管这个建议上报给大内总管贾公公后,先是被一顿臭骂,最后得到一句没么多的预算(皇帝也缺钱啊~),灰溜溜的跑了回去。
加人是不可能加人了,那只能另想他法。忧伤的大总管碎碎得念叨着:“七公祖宗,您别再来了,千万别再来了~”,回到了御膳房,听闻今天又被偷吃了一道“五珍脍”,大总管险些当场晕倒在地,那可要呈给皇后娘娘的晚膳啊。
辛亏立在一旁的小太监眼疾手快,扶住了大总管,大总管定了定神,把制作“五珍脍”御厨张三喊了过来。
“老张,就剩一个时辰了,五珍脍还来得做完吗?”大总管满是希冀地看着张三。
张三的一张脸都要仄成面团了,哭丧道:“来不及了,这五珍脍有十几道工序…“
大总管听闻来不及了,就要被吓得魂飞天外了,想起刚刚才被骂的狗血喷头,心中不由绝望,惧怕之下,整个人不由抽搐起来,连话都说出来了。
”总管,总管大人,有办法的,有办法的,您别急啊“,扶着大总管的小太监大声地喊道。
”有…有什么…什么办法?“不知道是小太监的声音够大还是听闻有办法了,大总管竟然回过神来了,颤颤巍巍地说道。
”咱们可以让张大厨和其他几位大厨一起做这五珍脍啊,虽然说这菜工序多,但是有不少工序是可以同时做的啊,这样不就来得及了吗?“,小太监说道。
“对啊”,大总管得闻妙计,立时就蹦了起来,抓着张三的肩膀,瞪着眼睛道:“老张,这样真得可以吗?”。
“倒也不是不行”,张三看着大总管有些可怖的神情,犹豫道:“只是其她几位娘娘那的菜?”。
“先不管了,先把皇后娘娘的五珍脍做出来,今晚圣上在娘娘那下榻,特意要的这道菜”,大总管满是欢喜的说道:“其她贵人那边就先派人过去打个招呼,说上菜的时间会晚点吧。”
深夜,有惊无险的度过一劫的大总管,坐在房里想到既然“五珍脍”可以由几个人合作完成,每个人做一部分,那么其他菜是不是也可以呢?想到这,大总管管决定明天一大早和御厨们开个会。
会议的过程很顺利,众御厨们一致认为除了小部分的菜品过于特殊,无法分工合作外,其他菜都可以交由多人合作,各司其职,选择原材料、洗菜、切菜、调制高汤、入锅等都交由不同的小团队负责。
此后,御膳房的做菜效率成倍的提升,屡获嘉奖的大总管终于高枕无忧了。
切面与处理
以上故事纯属胡诌,主要是想说明一道工序复杂的菜就像是一个复杂的需求(业务的或者技术的),御厨就是一个功能及其强大的程序,既要选材、切菜、配料、又要负责入锅出菜,做得太多,过于“臃肿”不说,还难以应对突发的情况(菜被偷吃、新的需求),势必会影响开发效率和代码的维护可以维护性。
另外,能负责写这么复杂程序的人也不多,如果一个方法又要处理业务需求,可能还得处理网络、负载、文件IO等等,相信没几个人是可以保证写得好的。
所以在面对一个复杂的流程(工序)的时候,我们找准流程中的不同节点(选材、切菜、配料),进行切入(切点、切面),交由不同的人(程序)负责处理,最终合作完成整个流程(类比工厂的生产流水线)。
综上所知AOP的核心就是:找到切入点(切面),然后设置在这个切换点上的处理(程序或者方法)。
Spring 与 AOP
大家随便在网上Spring与AOP,大致都会告诉你这些内容
- Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point
- Pointcut(切入点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、Spel表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
说句实话,在下第一次看到这些东西的时候也是一脸懵,这是啥?怎么用?
我们结合前面讲述的内容,理解一下Spring aop的处理:
在Spring 启动过程简析中,描述道Spring的本质就是创建对象,那么当我们将程序流程拆解开(例如:记录日志等)需要Spring在创建这些对象时能够将其关联(业务方法和日志记录分别在不同的类里面)。
按照Spring的定义:所有的方法调用,类成员的访问以及异常处理程序块的执行等都可以作为切入点,那么Spring就应该提供某种个方式能够让程序员定义的程序中的那些环节(切点\方法)需要切入,以什么样的方式切入(before\ after \ around),以及说明要做的处理流程(Advice)。让后Spring在创建对象时通过(BeanPostProcessor)识别出这些特征,通过动态代理(java动态代理\Cglib)将其关联在一起。
Spring是如何识别我们定义的特征(类\方法)的呢?我们可以看一下Spring 切入点(Pointcut)的接口定义
public interface Pointcut {
/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never {@code null})
*/
ClassFilter getClassFilter();
/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never {@code null})
*/
MethodMatcher getMethodMatcher();
/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
public interface ClassFilter {
/**
* Should the pointcut apply to the given interface or target class?
* @param clazz the candidate target class
* @return whether the advice should apply to the given target class
*/
boolean matches(Class<?> clazz);
/**
* Canonical instance of a ClassFilter that matches all classes.
*/
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
public interface MethodMatcher {
/**
* Perform static checking whether the given method matches.
* <p>If this returns {@code false} or if the {@link #isRuntime()}
* method returns {@code false}, no runtime check (i.e. no
* {@link #matches(java.lang.reflect.Method, Class, Object[])} call)
* will be made.
* @param method the candidate method
* @param targetClass the target class
* @return whether or not this method matches statically
*/
boolean matches(Method method, Class<?> targetClass);
/**
* Is this MethodMatcher dynamic, that is, must a final call be made on the
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
* runtime even if the 2-arg matches method returns {@code true}?
* <p>Can be invoked when an AOP proxy is created, and need not be invoked
* again before each method invocation,
* @return whether or not a runtime match via the 3-arg
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method
* is required if static matching passed
*/
boolean isRuntime();
/**
* Check whether there a runtime (dynamic) match for this method,
* which must have matched statically.
* <p>This method is invoked only if the 2-arg matches method returns
* {@code true} for the given method and target class, and if the
* {@link #isRuntime()} method returns {@code true}. Invoked
* immediately before potential running of the advice, after any
* advice earlier in the advice chain has run.
* @param method the candidate method
* @param targetClass the target class
* @param args arguments to the method
* @return whether there's a runtime match
* @see MethodMatcher#matches(Method, Class)
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
/**
* Canonical instance that matches all methods.
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
可以看到不论是ClassFilter还是MethodMatcher都是用来匹配那些类或者方法是需要切入的。
而我们常用的切入表达式就是依赖AspectJExpressionPointcut类对于Pointcut的实现。
而所谓的Advice,就是程序执行到你定义的切点(方法)后按照Advice(before\after\around)的定义开始调用Advice描述的程序(方法)。
以上这些合起来就是一个切面(Aspect)的定义了。
总结
AOP就是对流程(程序\方法)做以(功能\流程\工序)点划分或增强(由于一开始没有规划,后续由于需求变更需要加入新的流程)。可以让每一个切面(点)的程序聚焦于这个层面,而不必关心其他的事情(分层架构)。
AOP与动态代理:aop一般都是使用动态代理实现的,但不是唯一的一种实现,可以参考如下文章:
Java代码织入
SpringBoot中使用LoadTimeWeaving技术实现AOP功能
JAVA动态代理
CGLIB详解(最详细)
其他文章: