这是我参与更文挑战的第13天,活动详情查看:更文挑战
之前我们已经提到,AOP的Joinpoint可以有许多种类型,如构造方法调用、字段的设置及获取、方法调用、方法执行等。但是,在Spring AOP中,仅支持方法级别的Joinpoint。更确切地说,只支持方法执行类型的Joinpoint。
虽然Spring AOP仅提供方法拦截,但是在实际开发中,这已经可以满足80%的开发需求了。所以,我们不用过于担心Spring AOP的能力
Spring AOP之所以如此,主要有一下几个原因:
1.Spring AOP要提供一个简单而强大的AOP框架,并不想因大而全使得框架本身过于臃肿。如果能够仅付出20%的努力,就能够得到80%的需求支持,这难道不是很好吗?
2.对于类中属性级别的Joinpoint,如果提供这个级别的拦截支持,那么就破化了面向对象的封装,而且,完全可以通过对setter和getter方法的拦截达到同样的目的。
3.如果应用需求非常特殊,完全超出了Spring AOP框架提供的那80%的需求支持,不妨求助于现有的其他AOP实现产品,如AspectJ。目前来看,AspectJ是Java平台对AOP支持最完善的产品,同时,Spring AOP也提供了对AspectJ的支持。
PointCut
Spring中以接口定义org.springframework.aop.Pointcut作为其AOP框架中所有Pointcut的最顶层的抽象,该接口定义了两个方法用来帮助捕捉系统中的相应的Jointpoint,并提供了一个TruePointcut类型实例。如果Pointcut类型为TruePointcut,默认会对系统中的所有对象,以及对象上所有被支持的Joinpoint进行匹配。org.springframework.aop.Pointcut定义如下:
public interface Pointcut{
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut True = TruePointcut.INSTANCE;
}
复制代码
ClassFilter和MethodMatcher分别用于匹配将被执行织入操作的对象以及相应的方法。之所以将类型匹配和方法匹配分开定义,是因为可以重用不同级别的匹配定义,并且可以在不同的级别或者相同的级别上进行组合操作,或者强制让某个子类只覆写相应的方法定义等。
ClassFilter接口的作用是对Joinpoin所处的对象进行Class级别的类型匹配,其定义如下:
public interface ClassFilter {
boolean matches(Class clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
复制代码
当织入的目标对象的Class类型与Pointcut所规定的类型相符合时,matches方法将会返回true,否则,返回false,即意味着不会对这个类型的目标对象进行织入操作,比如,如果我们仅希望对系统中Foo类型的类执行织入,则可以如下定义ClassFilter:
public class FooClassFilter {
public boolean matches(Class clazz){
return Foo.class.equals(clazz);
}
}
复制代码
相对于ClassFilter的简单定义,MethodMatcher则要复杂得多。毕竟,Spring主要支持的就是方法级别的拦截。MethodMatcher定义如下:
public interface MethodMatcher {
boolean matches(Method method, Class targetClass);
boolean isRuntime();
boolean matches(Method method, Class targetClass, Object[] args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
复制代码
MethodMatcher通过重载,定义了两个matches方法,而这两个方法的分界线就是isRuntime()方法。在对对象具体方法进行拦截的时候,可以忽略每次方法执行的时候调用者传入的参数,也可以每次都检查这些方法调用参数,以强化拦截条件。假设对一下方法进行拦截:
public boolean login(String username, String password);
复制代码
如果只想在login方法之前插入计数功能,那么login方法的参数对于Joinpoint捕捉就是可以忽略的。而如果想在用户登录的时候对某个用户做单独处理,如不让其登录或者给予特殊权限,那么这个方法的参数就是在匹配Joinpoint的时候必须要考虑的。
1.在前一种情况下,isRuntime返回false,表示不会考虑具体Joinpoint的方法参数,这种类型的MethodMatcher被称之为StaticMethodMatcher。因为不用每次都检查参数,那么对于同样类型的方法匹配结果,就可以在框架内部缓存以提高性能。
2.当isRuntime方法返回true时,表明该MethodMatcher将会每次都对方法调用的参数进行匹配检查,这种类型的MethodMatcher称之为DynamicMethodMatcher。因为每次都要对方法参数进行检查,无法对匹配结果进行缓存,所以,匹配效率相对于StaticMethodMatcher来说要差。
Advice
在Spring中,Advice按照其自身实例能否在目标对象类的所有实例中共享这一标准,可以划分为两大类,即per-class类型的Advice和per-instance类型的Advice。
per-class类型的Advice
per-class类型的Advice是指,该类型的Advice的实例可以在目标对象类的所有实例中共享。这种类型的Advice通常只是提供方法拦截的功能,不会为目标对象类保存任何状态或者添加新的特性。
1.BeforeAdvice,使用场景:对文件系统的指定路径进行统一的检查或者初始化。
2.ThrowsAdvice,使用场景:对系统中的运行时异常进行监控,一旦捕捉到异常,需要马上以某种方式通知系统的监控人员或者运营人员。
3.AfterReturningAdvice:使用场景:为了便于运营人员验证系统的状态,FX的批处理程序在正常完成之后会往数据库的指定表中插入运行状态,运营人员可以通过验证这些状态判断相应的批处理任务是否成功执行。
4.AroundAdvice,使用场景:系统安全验证及检查、系统各处的性能检测、简单的日志记录以及系统附加行为的添加等。
per-instance类型的Advice
在Spring AOP中,Introduction就是唯一的一种per-instance型Advice。
Introduction可以在不改动目标类定义的情况下,为目标类添加新的属性以及行为。这就好比我们开发人员,如果公司人员紧张,没有配备测试人员,那么,通常就会给我们扣上一顶“测试人员”的帽子,让我们同时进行系统的测试工作,实际上,你还是你,只不过多了点事而已。
在Spring中,为目标对象添加新的属性和行为必须声明相应的接口以及相应的实现。这样,再通过特定拦截器将新的接口定义以及实现类中的逻辑附加到目标对象之上。之后,目标对象(确切地说是目标对象的代理对象)就拥有了新的状态和行为。这个特定的拦截器就是org.springframework.aop.IntroductionInterceptor,其定义如下:
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
public interface DynamicIntroductionAdvice extends Advice {
boolean implementsInterface(Class intf);
}
}
复制代码
Ordered的作用
系统中只存在单一的横切关注点的情况很少,大多数时候,都会有多个横切关注点需要处理,那么,系统实现中就会有多个Advisor存在。当其中的某些Advisor的Pointcut匹配了同一个Joinpoint的时候,就会在这同一个Joinpoint处执行多个Advice的横切逻辑。如果这些Advisor所关联的Advice之间没有很强的优先级依赖关系,那么谁先执行,谁后执行都不会造成任何影响。而一旦这几个需要在同一个Joinpoint处执行的Advice逻辑存在优先顺序依赖的话,就需要我们来干预了。
Spring在处理统一Joinpoint处的多个Advisor的时候,实际上会按照指定的顺序和优先级来执行他们,顺序号决定优先级,顺序号越小,优先级越高,优先级排在前面的,将被优先执行。我们可以从0或者1开始指定,因为小于0的顺序号原则上由Spring AOP框架内部使用。默认情况下,我们不明确指定各个Advisor的执行顺序,那么Spring会按照他们的声明顺序来应用他们,最先声明的顺序号最小但优先级最大,其次次之。