本文主要描述 Spring AOP
中的 Pointcut
和 Advice
接口。
我们从 ProxyFactory
类开始说起,先来看一个简单的 Demo。
public class ProxyFactoryDemo {
public static void main(String[] args) {
// 1. 构造目标对象
Cat catTarget = new Cat();
// 2. 通过目标对象,构造 ProxyFactory 对象
ProxyFactory factory = new ProxyFactory(catTarget);
// 添加一个方法拦截器
factory.addAdvice(new MyMethodInterceptor());
// 3. 根据目标对象生成代理对象
Object proxy = factory.getProxy();
Animal cat = (Animal) proxy;
cat.eat();
}
public static class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("MyMethodInterceptor invoke 调用 before invocation.proceed");
Object ret = invocation.proceed();
System.out.println("MyMethodInterceptor invoke 调用 after invocation.proceed");
return ret;
}
}
}
运行上面的 main
方法,结果如下所示。
源码分析
首先,我们先来看一下 ProxyFactory
类的 Object target
参数构造函数。
public ProxyFactory(Object target) {
// 1. 设置目标对象,在创建代理对象以及调用代理方法时会用到
setTarget(target);
// 2. 设置代理类需要实现的接口,也就是目标类实现的所有接口和其父接口
setInterfaces(ClassUtils.getAllInterfaces(target));
}
在讨论 addAdvice
方法之前,我们先来看一下 org.aopalliance.intercept.MethodInterceptor
的 UML 类图。
注意:org.aopalliance.intercept.MethodInterceptor
不是 Spring AOP (三) CGLIB 动态代理 中的 net.sf.cglib.proxy.MethodInterceptor
接口,但是和 Spring AOP (四) 多重代理和责任链模式 中定义的 MyMethodInterceptor
接口类似。
然后我们来看一下 addAdvice
的方法内部实现。
public void addAdvice(Advice advice) throws AopConfigException {
int pos = this.advisors.size();
addAdvice(pos, advice);
}
public void addAdvice(int pos, Advice advice) throws AopConfigException {
Assert.notNull(advice, "Advice must not be null");
// 添加一个 DefaultPointcutAdvisor 到 Advisor 集合
addAdvisor(pos, new DefaultPointcutAdvisor(advice));
}
DefaultPointcutAdvisor
UML 类图如下所示。
简单介绍下上图中出现接口和类:
Advisor
:持有org.aopalliance.aop.Advice
对象的基础接口,可以简单的看作是org.aopalliance.aop.Advice
在 Spring 中的等价接口。PointcutAdvisor
:包含切入点的Advisor
,从而可以针对符合Pointcut
规则的连接点进行增强处理。Ordered
:用来确定当前Advisor
在拦截器责任链列表中的位置,主要用在Aspect
中。
切入点 API
Spring 的 Pointcut 模型
使切入点独立于 Advisor
(或者说是 Advice
) 类型。您可以使用相同的切入点来对方法做不能的增强处理。
该 org.springframework.aop.Pointcut
接口是核心接口,用来将 Advice
应用到特定的类和方法。完整的接口如下:
package org.springframework.aop;
/**
* 一个切入点由 ClassFilter 和 MethodMatcher 组成。
*/
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
将 Pointcut
接口拆分为两部分允许重用 ClassFilter
和 MethodMatcher
部分以及细粒度合成操作(可以查看 UnionClassFilter
、IntersectionClassFilter
、UnionMethodMatcher
、IntersectionMethodMatcher
等类的实现细节)。
该 ClassFilter
接口用于将切入点限制为给定的一组目标类。如果 matches()
方法始终返回 true
,则匹配所有目标类。以下清单显示了 ClassFilter
接口定义:
package org.springframework.aop;
public interface ClassFilter {
/**
* 切入点是否应用于给定的接口或目标类?
* @param clazz 目标类
* @return 该 Pointcut 关联的 Advice 是否需要适用于给定的目标类
*/
boolean matches(Class<?> clazz);
}
MethodMatcher
接口通常更重要。完整的接口清单如下所示:
package org.springframework.aop;
/**
* 检查 Pointcut 关联的 Advice 是否需要适用于给定的目标方法
*/
public interface MethodMatcher {
/**
* 对方法进行静态匹配,即不对方法调用的传入的实参进行校验
*/
boolean matches(Method method, Class<?> targetClass);
/**
* 返回当前 MethodMatcher 是否需要进行动态匹配。
* 如果 isRuntime() 方法返回 true,则表示需要调用 matches(Method, Class, Object[])方法对目标方法进行匹配
*/
boolean isRuntime();
/**
* 对方法进行动态匹配,即对方法调用的传入的实参进行校验
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
}
matches(Method, Class)
方法用于测试此切入点是否与目标类上的给定方法匹配。
如果双参数 matches
方法返回 true
,并且 MethodMatcher
的 isRuntime()
方法返回 true
,则在每次方法调用时都会调用三参数 matches(Method, Class, Object[])
方法。这使得切入点可以在执行目标 Advice
做增强处理之前通过传递给方法调用的参数对方法进行匹配。
大多数 MethodMatcher
实现是静态的,这意味着它们的 isRuntime()
方法返回 false
。在这种情况下,matches
永远不会调用三参数方法。
Advisor API
在 Spring 中,Advisor
是一个切面,它包含与切入点表达式关联的单个 Advice
对象。
除了介绍的特殊情况,任何 Advisor
都可以使用任何 Advice
。org.springframework.aop.support.DefaultPointcutAdvisor
是最常用的 Advisor
类。它可以与使用MethodInterceptor
,BeforeAdvice
或 ThrowsAdvice
。
可以在同一个 AOP 代理中混合 Spring 中的 Advisor
和 Advice
类型。例如,您可以在一个代理配置中使用拦截建议,抛出建议和建议之前。Spring 自动创建必要的拦截链。
我们继续来看 DefaultPointcutAdvisor
类的构造方法代码清单。
// TruePointcut 对象,表示匹配所有类的所有方法,即对所有方法进行增强处理
private Pointcut pointcut = Pointcut.TRUE;
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}
由此我们可以得知,通过 factory.addAdvice
添加的 Advice
会对目标对象的所有方法进行增强处理。
我们修改 Animal
接口,为其增加 go
方法定义,并在 Cat
类中实现 go
方法,代码清单如下所示。
public interface Animal {
void eat();
void go();
}
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void go() {
System.out.println("猫在跑");
}
}
然后我们在 ProxyFactoryDemo
类的 main
方法中,新增对其 go
方法的调用逻辑。
public static void main(String[] args) {
// 1. 构造目标对象
Animal catTarget = new Cat();
// 2. 通过目标对象,构造 ProxyFactory 对象
ProxyFactory factory = new ProxyFactory(catTarget);
// 添加一个方法拦截器
factory.addAdvice(new MyMethodInterceptor());
// 3. 根据目标对象生成代理对象
Object proxy = factory.getProxy();
Animal cat = (Animal) proxy;
System.out.println(cat.getClass());
cat.eat();
System.out.println("---------------------------------------");
cat.go();
}
运行 ProxyFactoryDemo
类的 main
方法,我们可以得到如下图所示的打印结果。
接下来,我们定义一个 Pointcut
接口的实现类 MyPointcut
,代码如下所示。
public class MyPointcut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
// 匹配所有的类
return true;
}
};
}
@Override
public MethodMatcher getMethodMatcher() {
// 继承 StaticMethodMatcher,忽略方法实参,只对方法进行动态匹配。
return new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 如果方法名称是 go,则匹配,否则不匹配
if (method.getName().equals("go")) {
return true;
}
return false;
}
};
}
}
然后定义一个 PointcutAdvisor
接口的实现类 MyPointcutAdvisor
,代码清单如下所示。
public class MyPointcutAdvisor implements PointcutAdvisor {
private Pointcut pointcut = new MyPointcut();
private Advice advice;
public MyPointcutAdvisor(Advice advice) {
this.advice = advice;
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
/**
* 此方法暂时忽略,不需要理会
*/
@Override
public boolean isPerInstance() {
return false;
}
}
然后我们修改 ProxyFactoryDemo
类的 main
方法中的逻辑,修改后的方法如下所示。
public static void main(String[] args) {
// 1. 构造目标对象
Animal catTarget = new Cat();
// 2. 通过目标对象,构造 ProxyFactory 对象
ProxyFactory factory = new ProxyFactory(catTarget);
// 添加一个 Advice (DefaultPointcutAdvisor)
factory.addAdvice(new MyMethodInterceptor());
// 新增代码:添加一个 PointcutAdvisor
MyPointcutAdvisor myPointcutAdvisor = new MyPointcutAdvisor(new MyMethodInterceptor());
factory.addAdvisor(myPointcutAdvisor);
// 3. 根据目标对象生成代理对象
Object proxy = factory.getProxy();
Animal cat = (Animal) proxy;
System.out.println(cat.getClass());
cat.eat();
System.out.println("---------------------------------------");
cat.go();
}
运行 ProxyFactoryDemo
类的 main
方法,我们可以得到如下图所示的打印结果。
由上图可知,eat
方法被增强了一次,而 go
方法被增强了两次,说明我们自定义的切入点 MyPointcut
已经生效。
至此,Pointcut
和 Advice
(Advisor
) 以及 PointcutAdvisor
的整体架构脉络我们就都清楚了。
参考文献
(正文完)