上文讲述了AOP的一些基本概念,下面来讲讲SpringAOP的应用。 ## Joinpoint ## 说到Joinpoint其实需要明确的是什么样的类可以作为一个Joinpoint?在AOP的Joinpoint可以支持需要的类型:构造方法的调用、字段的设置和获取、方法的调用和方法的执行等。 但是在SpringAOP中只支持方法的执行(Method Execution)
Pointcut
前面说了Pointcut的作用就是用来找到系统中的Joinpot。而SpringAOP提供了一系列查找Joinpoint的方法。下面将就各个做简单的介绍,在这之前先就Spring中Pointcut做个介绍。在Spring中Pointcut
是以一个叫Pointcut
的接口来作为最顶层的抽象:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
这里ClassFilter和MethodMatcher主要用来匹配植入对象的类和方法。也就是说如果是对类匹配,我们就需要实现ClassFilter
,我们来看看:
public interface ClassFilter {
boolean matches(Class<?> clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
很明显,如果我们需要对指定的类做匹配,那么方法matches
必须返回true
,如下方法会匹配Foo:
public boolean matches(Class<?> clazz){
return Foo.class.equals(clazz);
}
接下来是MethodMatcher
,之前已经说过SpringAOP主要针对方法的执行进行,那么这个就显得特别重要了。
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
方法,这两个方法分别用在什么场景呢?很明显后者多了args
,根据我们的经验这就是方法的参数。是的,后者是可以获取方法的参数。那么怎样区分呢?这就是方法
isRuntime()
的作用,如果返回true
将调用后者,此时你能够获取方法的参数,也许你能够对方法参数进行某些检验。如:
public void doSomething(String name, int age)
比如上面的方法,传入了两个参数。你可能需要对参数做一些校验才能被执行: return "robin".equals(name) && age > 20
。而这被称为DynamicMethodMatcher
,由于每次都需要对方法参数进行校验,存在一定的效率影响,同时由于参数的不确定性也不能被缓存。
而另一种叫做StaticMethodMatcher
,主要是在isRuntime()
返回false
时被调用,这也是比较常见的情况,由于不需要每次都去校验方法参数,在很多框架内部对其进行了缓存以提高性能。
下图是常见的Pointcut
类图
下面对常见的几种Pointcut进行一些说明:
1、NameMatchMethodPointcut
根据类名可以很容易看出,这是根据方法名来与Jointcut的方法匹配,可以参考以下几种配置:
NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();
nameMatchMethodPointcut.addMethodName("sayHi");//匹配sayhi
nameMatchMethodPointcut.addMethodName("greet");//匹配greet
nameMatchMethodPointcut.setMappedName("meth*");//匹配以meth开头的方法
nameMatchMethodPointcut.setMappedNames(new String[]{"*do*", "meth*end*"});//匹配多个方法,支持简单的模糊匹配
更多实例见这里
2、JdkRegexpMethodPointcut
采用JDK的正则匹配
JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();
jdkRegexpMethodPointcut.setPattern(".*say*.*");
jdkRegexpMethodPointcut.setPatterns(new String[]{});
更多实例见这里
3、AnnotationMatchingPointcut
AnnotationMatchingPointcut通过申明相应的Annotation来进行拦截,具体可见其api说明。
首先声明了需要关注的annotation:
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorAround {
}
在测试用例中,需要指明:
//具体使用哪个构造函数根据需要来定
AnnotationMatchingPointcut pc = new AnnotationMatchingPointcut(null, MonitorAround.class);
更多实例见这里
4、ComposablePointcut
ComposablePointcut是可以对多个Pointcut进行逻辑运算的Pointcut,提供了”并”和”交”两种方式:
ComposablePointcut pc = new ComposablePointcut(ClassFilter.TRUE, new FooMethodMatcher());
//新增一个Pointcut:两个必须都满足
pc.intersection(new BarMethodMatcher());
//新增一个Pointcut:两个只需要其中一个为TRUE即可
pc.union(new BarMethodMatcher());
这里自定义了两个Pointcut
继承StaticMethodMatcher
public class FooMethodMatcher extends StaticMethodMatcher {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return "sayHi".equals(method.getName());
}
}
更多实例见这里
5、ControlFlowPointcut
ControlFlowPointcut简单的说是空值方法调用,比如有方法foo()
,只有在方法method()
调用是才被拦截,那么就是ControlFlowPointcut使用的场景了
//这里只有ControlFlowPointcutTest中的method1调用目标方法时才会进行拦截
ControlFlowPointcut controlFlow = new ControlFlowPointcut(ControlFlowPointcutTest.class, "method1");
更多实例见这里
Advice
Advice是Pointcut植入到Joinpoint处运行逻辑,通常可以区分为BeforeAdvice、AfterAdvice等。下面是UML类图
下面就几种常见的Advice做简单的介绍
1、BeforeAdvice
在SpringAOP中要实现BeforeAdvice
需要实现接口MethodBeforeAdvice
,定义如下:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object target) throws Throwable;
}
BeforeAdvice
适合在方法执行前做一些必要的处理,如权限校验、参数校验或者进行必要的初始化准备等,但是不能获取到方法的返回值的。
2、AfterReturningAdvice
SpringAop中后处理通知需要实现接口AfterReturningAdvice
,通过他我们可以获取到当前Joinpoint的返回值、方法、参数等。如下只是简单列出了方法的返回值:
public class MethodAfterAdviceSample implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After Method " + method.getName() + "(), Return "
+ (returnValue == null ? "null" : returnValue));
}
}
当然,这里是只有方法被正确执行后才会执行AfterReturningAdvice
(如果抛出异常属于后面介绍的Advice)。而且也不能更改方法的返回值,适合的场景比如在进行某些操作后做进一步的校验
3、ThrowsAdvice
SpringAop采用ThrowsAdvice
来描述AOP中的AfterThrowingAdvice
:
public interface ThrowsAdvice extends AfterAdvice {
}
在这个接口中并没有任何方法,但是在实际实现时也是可以灵活处理,只是方法名必须是afterThrowing
如:
public class AfterThrowAdviceSample implements ThrowsAdvice {
//afterThrowing([Method], [args], [target], subclassOfThrowable)
public void afterThrowing(NullPointerException exception) {
//
System.out.println("Exception: " + exception.getClass().getName());
}
public void afterThrowing(Method method, Object[] args, Object target, ClassCastException exception) {
System.out.println("Process Method: " + method.getName() + "() Exception: " + exception.getClass().getName());
}
}
这里可以重载多个afterThrowing
方法,最后的参数是捕获可能抛出的异常
4、AroundAdvice
AroundAdvice
是功能强大的一个Advice了,在SpringAop中并没有单独处理,而是直接运用了AOP Alliance中的MethodInterceptor
:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
与之前的Advice不一样的是这里方法有返回值,这就意味着可以影响Joinpoint方法的返回值。这也是应用很广泛的Advice,比如性能监控、日志记录等。
public class AroundAdviceSample implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Proceed " + invocation.getMethod().getName() + "()");
Object returnValue = invocation.proceed();
return ((Integer) returnValue) * 0.85;
}
}
这里展示了方法改变返回结果的处理,这样的场景可能是在促销时对总价打折处理