写在前面
数据结构的掌握程度决定你的coding之路能走多稳。
可能大家看到这个标题会很有疑问,怎么讲着讲着AOP开始讲起来排序了? 先说一下大家都比较熟悉的东西。回忆一下,我们如果想向容器中加入自定义的切面该怎么做?两种方法:
- 我们自定义Advisor注入到容器中。
- 利用@Aspect标示一个类为切面配置类,并利用@Around, @Before, @After, @AfterReturning, @AfterThrowing定义一些切面的实现,最后将切面配置类注入到容器中。
第一种方法我们后续会进行详细剖析。第二方法我想应该是大家比较常用的方式。而这又跟偏序排序有什么关系呢?
回想下第一篇搞懂AOP之一,拦截器链中我们说到拦截器链是List结构,而List结构注定有序。我们所使用的@Around, @Before, @After, @AfterReturning, @AfterThrowing最终也会被转换成MethodInterceptor加入到拦截器链中。那么问题来了,生成的拦截器链中的@Around, @Before, @After, @AfterReturning, @AfterThrowing顺序是如何的?谁在前,谁在后?(这里的顺序有两层含义,一是同一个切面配置类中的@Around, @Before, @After, @AfterReturning, @AfterThrowing顺序,二是多个切面配置类彼此的@Around, @Before, @After, @AfterReturning, @AfterThrowing顺序)
具体的顺序是什么我想大家在学习spring的时候都有了解过,但是这个顺序具体是如何实现的,就是我们要分析的内容,知其然也要知其所以然嘛。本文先讲一下偏序排序,在了解偏序排序的基础上,下一篇我们再深入偏序排序在AOP中的应用。
偏序关系
偏序关系是什么?我们可以从两个角度去解析。什么是“关系”?什么是“偏序”?
先来恶补一些离散数学的知识。
关系
“关系”是数字中的一个很基本的概念,站在集合的角度来看关系是怎么样的呢?
假设集合A={a,b,c,d,e,f},集合A中的元素表示6个人。其中a是b和c的爸爸,b是d的爸爸,c是e和f的爸爸。
那么集合A中的元素中符合父子关系的两个人可以用有序偶(a,b)、(a,c)、(b,d)、(c,e)、(c,f)表示。那么集合R={(a,b)、(a,c)、(b,d)、(c,e)、(c,f)}可以完整地描述出集合A中元素的父子管子,我们称R为集合A上的一个关系。
再举个栗子,集合A={1,2,3}上的小于关系可以表示为R={(1,2)、(1,3)、(2,3)}
现在我们把集合的个数扩充到两个
有两个集合A={a,b,c,d},B={x,y,z}。其中A表示a,b,c,d四位教师,x,y,z表示语文、数学和英语三门课程。a和b是语文老师、c是数学老师、d是英语老师。那么集合R={(a,x),(b,x),(c,y),(d,z)}表示教师和其所授课程之间的关系。
关系的表示
我们怎么表示关系呢?
1.关系矩阵
给定两个集合A={a1,a2...an}和B={b1,b1...bm},R是A到B的关系,则称
称为R的关系矩阵。其中 当 属于R时, 当 不属于R时,
2.关系图
给定两个集合A={a1,a2...an}和B={b1,b1...bm},R是A到B的关系。在平面上用n个点表示A中的元素,用m个点表示B中的元素。当 属于R时,则从点至画一条有向边,箭头指向,否则不画边。
我们以刚刚教师与所授课程的例子,其对应的关系图为:
再拿我们所举的父子关系的例子,关系图可以这样表示:
3.关系的性质
- 自反性:R是A上的二元关系,如果对于A中每一个元素x,都有(x,x)属于R,则称R是自反的,也称R具有自反性。
例1:A={a,b,c},A上的二元关系R={(a,a),(b,b),(c,c),(a,c),(c,b)},则R是自反的二元关系。 例2:设A={1,2,3,4},A上的二元关系R={(1,1),(2,2),(3,4),(4,2)},因为3∈A,但(3,3),所以R不是A上的自反关系.
- 反自反性:R是A上的二元关系,如果对于A中每一个元素x,都有(x,x)不属于R,则称R是反自反的,也称R具有反自反性。
例1:设A={a,b,c},R={(a,c),(b,a),(b,c),(b,b)}。因为(b,b)属于R,则R不是A上的反自反关系。 例2:设A={1,2,3},R是A上的小于关系,即R={(1,2),(1,3),(2,3)}。由于(1,1),(2,2),(3,3)都不属于R,所以R是A上的反自反关系。
- 对称性:R是A上的二元关系,每当(x,y)属于R时,就一定有(y,x)属于R,则称R是对称的,也称R具有对称性。
例1:设A={a,b,c},R={(a,b),(b,a),(a,c),(c,a)},所以R是对称的。 例2:设A={1,2,3,4}上的二元关系R={(1,1),(1,2),(2,1),(3.3),(4,3),(4,4)},因为(4,3)属于R但(3,4)不属于。所以R不是对称的。
- 反对称性:定义R是A上的二元关系,当x≠y时,如果(x,y)属于R,则必有(y,x)不属于R,称R是反对称的,也称R具有反对称性。
例1:A={1,2,3}上的关系R={(1,2),(2,2),(3,1)},则R是反对称的。但S={(1,2),(1,3),(2,2),(3,1)}不是反对称的.因为1≠3但(1,3)属于S,且(3,1)属于S。
对称关系和反对称关系不是两个相互否定的概念.
存在既是对称的也是反对称的二元关系,也存在既不是对称的也不是反对称的二元关系。
设A={a,b,c,d}上的关系R={(a,a),(b,c),(c,d),(d,c)},S={(a,a),(b,b),(d,d)},
则R既不是对称的(因为(b,c)∈R但(c,b)),也不是反对称的(因为(c,d)∈R且(d,c)∈R)
而S既是对称的,也是反对称的。
- 可传递性:设R是A上的二元关系,每当(x,y)属于R且(y,z)属于R时,必有(x,z)属于R,则称R是可传递的,也称R具有可传递性。
在重新认识了“关系”之后,来看看两种关系,“偏序关系”和“全序关系”。
- 偏序关系:设R是非空集合A上的关系,如果R是自反的,反对称的,和可传递的,则称R是A上的偏序关系。
常常把集合A以及A上的偏序关系R合在一起统称为偏序集,记作(A,R)。 由于偏序关系是自反的、反对称的、传递的二元关系,所以一般用符号“≤”表示偏序。
这里的符号“≤”可不要当做是小于等于哦。为了避免与≤(小于等于号)相混,后续表示偏序关系时,会加上双引号。即“≤”表示偏序。
偏序关系下,当(a, b)∈R,时,常记作a“≤”b,偏序集也常记作(A,“≤”)。 比如小于等于关系,、字典顺序、整除关系和包含关系都是相应集合的偏序关系。
与偏序相关的一个概念是全序。
-
全序关系:如果R是A上的偏序关系,那么对于任意的A集合上的 x,y,都有 x“≤”y,或者 y“≤”x,二者必居其一,那么则称R是A上的全序关系。全序关系中,任意两个元素都是有关系的。全序也叫做线性次序。
比如在整数集合中,大于等于关系是线性序关系。
看了教科书式的定义,那么我们怎么简单地去理解偏序关系和全序关系呢?
- 偏序关系表示了集合内只有部分元素之间在这个关系下是可以比较的(局部)。
- 全序关系表示了集合内任何一对元素在在这个关系下都是相互可比较的(全局)。
- 全序关系也是一种偏序关系。
偏序排序
知道了偏序关系,偏序排序就很简单,按照偏序关系进行排序就是偏序排序啦。
我们日常说的排序一般都是指全序排序。比如说给定数组[8,2,5,1,111,50,0],通过快排使数组有序。这里其实就是指全序排序,而全序关系就是指两个元素的大小关系,因为我们认为数组中任意两个元素都是可以比较大小的。
AOP与偏序排序
前面铺垫了这么多,相信大家对排序又有了更深的理解。下面把思绪拉回到AOP当中,偏序排序在AOP当做有什么应用呢?
我们的项目中,常常用@Aspect定义一个切面配置类,里面的@Before、@Around等定义了各种切面增强。我们知道@Around, @Before, @After, @AfterReturning, @AfterThrowing对具体的连接点的增强的时机是不一样的,为了保证正确的增强顺序,就要求拦截器链中对应的Advisor是有一定顺序的。而多个@Aspect定义的切面配置类,彼此之间的顺序是如何的呢?
举例来说,我们用@Aspect定义了两个切面配置类,分别是LogAspectA和LogAspectB。
LogAspectA
@Component
@Aspect
public class LogAspectA {
@Before("execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))")
public void doBefore(JoinPoint joinPoint) {
System.out.println("A doBefore run...");
}
@After("execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))")
public void doAfter(JoinPoint joinPoint) {
System.out.println("A doAfter run...");
}
@AfterReturning(value = "execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("A doAfterReturning run, result: " + result);
}
@AfterThrowing(value = "execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))", throwing = "ex")
public void doAfterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("A doAfterThrowing catch exception: " + ex.getMessage());
}
@Around("execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) {
System.out.println("A doAround run...");
Object result = null;
try {
System.out.println("A method before invoke...");
result = joinPoint.proceed();
System.out.println("A method invoked, result: " + result);
} catch (Throwable throwable) {
System.out.println("A method throws Exception: " + throwable.getMessage());
throwable.printStackTrace();
}
return result;
}
}
LogAspectB
@Component
@Aspect
public class LogAspectB {
@Before("execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))")
public void doBefore(JoinPoint joinPoint) {
System.out.println("B doBefore run...");
}
@After("execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))")
public void doAfter(JoinPoint joinPoint) {
System.out.println("B doAfter run...");
}
@AfterReturning(value = "execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("B doAfterReturning run, result: " + result);
}
@AfterThrowing(value = "execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))", throwing = "ex")
public void doAfterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("B doAfterThrowing catch exception: " + ex.getMessage());
}
@Around("execution(public * com.xyh.study.provider.spring.AOP.第一小节.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) {
System.out.println("B doAround run...");
Object result = null;
try {
System.out.println("B method before invoke...");
result = joinPoint.proceed();
System.out.println("B method invoked, result: " + result);
} catch (Throwable throwable) {
System.out.println("B method throws Exception: " + throwable.getMessage());
throwable.printStackTrace();
}
return result;
}
}
这里先说明一下,@Around, @Before, @After, @AfterReturning, @AfterThrowing对应最终都会被解析成InstantiationModelAwarePointcutAdvisorImpl
,这是一个Advisor,并加入到拦截器链中。这个解析过程我们后续再详细说,不是本篇重点。
LogAspectA和LogAspectB最终会产生10个Advisor,我们记为: Around-A、Before-A、After-A、AfterReturning-A、AfterThrowing-A、Around-B、Before-B、After-B、AfterReturning-B、AfterThrowing-B。拦截器链是有序的,spring对这10个Advisor的排序,就使用到了偏序排序。
先来看看spring对Advisor进行排序的代码注释(来自AspectJAwareAdvisorAutoProxyCreator
):
/**
* Sort the rest by AspectJ precedence. If two pieces of advice have
* come from the same aspect they will have the same order.
* Advice from the same aspect is then further ordered according to the
* following rules:
* <ul>
* <li>if either of the pair is after advice, then the advice declared
* last gets highest precedence (runs last)</li>
* <li>otherwise the advice declared first gets highest precedence (runs first)</li>
* </ul>
* <p><b>Important:</b> Advisors are sorted in precedence order, from highest
* precedence to lowest. "On the way in" to a join point, the highest precedence
* advisor should run first. "On the way out" of a join point, the highest precedence
* advisor should run last.
*/
@Override
@SuppressWarnings("unchecked")
protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
// ...省略
}
从注释中我们可以得到以下信息:
- Advisor按照从高到低优先级的顺序进行排序。进入连接点时,高优先级的Advisor先执行。离开连接点时,高优先级的Advisor后执行(就是个栈的概念)。
- 来自于同一个@Aspect切面配置类的切面实现Advisor具有相同的优先级顺序。
- 同一个@Aspect切面配置类的切面实现Advisor还会进一步按照特定规则进行排序(后续文章会具体分析)。
总结下来,全局来看,是按照优先级顺序从高到低进行排序。而来自同一个Aspect的Advisor还会再进一步按照特定的规则进行排序。这个进一步排序,就是一个按照偏序关系进行的排序。因为只有满足来自同一个Aspect的条件的情况下,才会进行这个特定规则的排序,并非所有的Advisor都会应用此规则进行排序。
偏序排序的实现
我们找到spring中对Advisor进行排序所使用的方法,即PartialOrder#order
,先纵览下。
/**
* @param objects must all implement PartialComparable
*
* @returns the same members as objects, but sorted according to their partial order. returns null if the objects are cyclical
*
*/
public static <T extends PartialComparable> List<T> sort(List<T> objects) {
if (objects.size() < 2) {
return objects;
}
List<SortObject<T>> sortList = new LinkedList<SortObject<T>>();
for (Iterator<T> i = objects.iterator(); i.hasNext();) {
addNewPartialComparable(sortList, i.next());
}
final int N = objects.size();
for (int index = 0; index < N; index++) {
SortObject<T> leastWithNoSmallers = null;
for (SortObject<T> so: sortList) {
if (so.hasNoSmallerObjects()) {
if (leastWithNoSmallers == null || so.object.fallbackCompareTo(leastWithNoSmallers.object) < 0) {
leastWithNoSmallers = so;
}
}
}
if (leastWithNoSmallers == null) {
return null;
}
removeFromGraph(sortList, leastWithNoSmallers);
objects.set(index, leastWithNoSmallers.object);
}
return objects;
}
下面我们结合之前学习的偏序知识来一步一步解析。
PartialComparable
接口注释表明了入参(待排序的数据)必须要实现PartialComparable接口。那就瞅一眼PartialComparable。
/**
* All classes that want to be part of a partial order must implement PartialOrder.PartialComparable.
* 所有想要成为偏序集合中的一部分都必须要实现此接口
*/
public static interface PartialComparable {
/**
* @returns <ul>
* <li>+1 if this is greater than other</li>
* <li>-1 if this is less than other</li>
* <li>0 if this is not comparable to other</li>
* </ul>
*
* <b> Note: returning 0 from this method doesn't mean the same thing as returning 0 from
* java.util.Comparable.compareTo()</b>
* 返回+1表示当前对象大于入参other
* 返回-1表示当前对象小于入参other
* 返回0表示当前对象和入参对象other不能比较,也就是当前对象和入参对象other之间没有关系(这就是偏序的体现)!与java.util.Comparable.compareTo()返回的0是具有不同含义的,java.util.Comparable.compareTo()返回的0是表示两者相等的意思!
*/
public int compareTo(Object other);
/**
* This method can provide a deterministic ordering for elements that are strictly not comparable. If you have no need for
* this, this method can just return 0 whenever called.
*
* 对于不能比较的元素(也就是compareTo返回0),此方法提供给你进行比较的机会,如果你不需要,返回0即可。
*/
public int fallbackCompareTo(Object other);
}
我怕代码注释字小你们不仔细看,我把重要的东西再拿出来说一遍。 注意返回0表示的含义! 注意返回0表示的含义! 注意返回0表示的含义!
compareTo返回值的含义: 返回+1表示当前对象大于入参other 返回-1表示当前对象小于入参other 返回0表示当前对象和入参对象other不能比较,也就是当前对象和入参对象other之间没有关系(看到没,这就是偏序的体现啊,偏序关系就是集合内只有部分元素之间在这个关系下是可以比较的,部分!部分!部分!)!与java.util.Comparable.compareTo()返回的0是具有不同含义的,java.util.Comparable.compareTo()返回的0是表示两者相等的意思!
SortObject
用于排序的数据结构。
private static class SortObject<T extends PartialComparable> {
// 当前对象
T object;
// 集合中比当前对象小的元素列表
List<SortObject<T>> smallerObjects = new LinkedList<SortObject<T>>();
// 集合中比当前对象大的元素列表
List<SortObject<T>> biggerObjects = new LinkedList<SortObject<T>>();
public SortObject(T o) {
object = o;
}
boolean hasNoSmallerObjects() {
return smallerObjects.size() == 0;
}
boolean removeSmallerObject(SortObject<T> o) {
smallerObjects.remove(o);
return hasNoSmallerObjects();
}
// 添加有向连接,此处通过smallerObjects和biggerObjects就可以确定一条连接
// 这就对应我们之前所说的关系图中的有向连接
void addDirectedLinks(SortObject<T> other) {
int cmp = object.compareTo(other.object);
if (cmp == 0) {
// 看到没,这里当SortObject#compareTo返回0时,表示两个对象无法比较,不会建立有向连接,直接return。
return;
}
if (cmp > 0) {
this.smallerObjects.add(other);
other.biggerObjects.add(this);
} else {
this.biggerObjects.add(other);
other.smallerObjects.add(this);
}
}
public String toString() {
return object.toString(); // +smallerObjects+biggerObjects;
}
}
代码实现
在了解了PartialComparable和SortObject之后,我们再回过头来仔细看PartialOrder#sort
。
public static <T extends PartialComparable> List<T> sort(List<T> objects) {
// 小于两个元素还比较啥呢,直接返回!
if (objects.size() < 2) {
return objects;
}
// 建立偏序排序所需数据结构SortObject
List<SortObject<T>> sortList = new LinkedList<SortObject<T>>();
// 下面这个for循环就是通过建立有向连接去构建关系图!
for (Iterator<T> i = objects.iterator(); i.hasNext();) {
addNewPartialComparable(sortList, i.next());
}
final int N = objects.size();
// 解析关系图,关系图已经包含了大小关系。解析过程中完成排序。
for (int index = 0; index < N; index++) {
// 集合中最小的元素
SortObject<T> leastWithNoSmallers = null;
for (SortObject<T> so: sortList) {
// 如果SortObject没有更小的元素
if (so.hasNoSmallerObjects()) {
if (leastWithNoSmallers == null || so.object.fallbackCompareTo(leastWithNoSmallers.object) < 0) {
leastWithNoSmallers = so;
}
}
}
if (leastWithNoSmallers == null) {
return null;
}
removeFromGraph(sortList, leastWithNoSmallers);
// 设置顺序
objects.set(index, leastWithNoSmallers.object);
}
// 返回排序后的结果
return objects;
}
具体的逻辑都在代码中注释了。 下面我们就举个例子看看吧。
我们想实现,对字符串的首字符按字典顺序降序排序,对于首字符相同的我们认为彼此之间没有关系(从而体现偏序)。另一方面,我们也想用下PartialComparable的fallbackCompareTo功能,对于首字符相同的情况下,我们按照字典升序排序。
新建Token类来承载要排序的元素,按照前文所述,想要成为偏序集合中的一部分都必须要实现PartialComparable接口。
class Token implements PartialOrder.PartialComparable {
private String s;
Token(String s) {
this.s = s;
}
@Override
public int compareTo(Object other) {
// 对字符串的首字符按字典顺序降序排序,对于首字符相同的我们认为彼此之间没有关系
Token t = (Token) other;
int cmp = s.charAt(0) - t.s.charAt(0);
if (cmp == 1) {
return 1;
}
if (cmp == -1) {
return -1;
}
return 0;
}
@Override
public int fallbackCompareTo(Object other) {
// 对于首字符相同的情况下,我们按照字典升序排序
return -s.compareTo(((Token) other).s);
}
@Override
public String toString() {
return s;
}
}
来测试下:
public static void main(String[] args) {
List<Token> l = new ArrayList<Token>();
l.add(new Token("a1"));
l.add(new Token("c2"));
l.add(new Token("b3"));
l.add(new Token("f4"));
l.add(new Token("e5"));
l.add(new Token("d6"));
l.add(new Token("c7"));
l.add(new Token("b8"));
l.add(new Token("z"));
l.add(new Token("x"));
l.add(new Token("f9"));
l.add(new Token("e10"));
l.add(new Token("a11"));
l.add(new Token("d12"));
l.add(new Token("b13"));
l.add(new Token("c14"));
System.out.println(l);
PartialOrder.sort(l);
System.out.println(l);
}
输出结果:
[a1, c2, b3, f4, e5, d6, c7, b8, z, x, f9, e10, a11, d12, b13, c14]
[z, x, a11, a1, b8, b3, b13, c7, c2, c14, d6, d12, e5, e10, f9, f4]
嗯,没问题,是我们想要的。
总结
本文介绍了离散数学中偏序关系的概念,并介绍了偏序排序。以及偏序排序在AOP中的应用,即拦截链排序。有了此基础,我们将在后面继续探讨:
- @Aspect中的@Around, @Before, @After, @AfterReturning, @AfterThrowing是如何转化成Advisor从而加入到拦截器链中的。
- @Around, @Before, @After, @AfterReturning, @AfterThrowing彼此之间的顺序是如何的。