Spring 5 - Advisors和Pointcuts
ProxyFactory类提供了获取和配置AOP代理实例的简单的办法。ProxyFactory.addAdvice()方法配置代理的advice,它代表后面的addAdvisor(),增加DefaultPointcutAdvisor实例,使用pointcut配置它。这样,advice作用于目标的所有方法。有时候,比如你使用AOP做日志,可能希望这样,但是有时候,你想限制advice作用的方法。
当然,你可以检查方法是否适合advised,但是,这样做有几个缺点。首先,接受方法的硬编码降低了advice的可重用性。二,会导致性能问题。有时候,需要硬编码检查,比如以前的检查弱key的例子。
Pointcut
实现Pointcut接口可以增加Pointcuts:
package org.springframework.aop;
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
Pointcut接口定义了两个方法,getClassFilter()和getMethodMatcher(),他们分别返回ClassFilter和MethodMatcher的实例。Spring提供了一些实现,一般不用自己定制。
当决定Pointcut是否应用于某方法的时候,Spring使用ClassFilter先检查方法的类。
package org.springframework.aop;
public interface ClassFilter {
boolean matches(Class<?> clazz);
}
如果可用,返回true。MethodMatcher接口比较复杂:
package org.springframework.aop;
public interface MethodMatcher {
boolean matches(Method m, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method m, Class<?> targetClass, Object[] args);
}
Spring支持两种MethodMatcher-静态和动态-由isRuntime()决定。使用MethodMatcher之前,Spring调用isRuntime()做决定-
如果MethodMatcher是静态的,返回false;如果是动态的,返回true。
对于静态pointcut,Spring为目标的每个方法调用matches(Method, Class)方法,并且缓存返回值。这样,每个方法只检查一次。
对于动态pointcuts,方法第一次调用的时候,Spring仍然使用matches(Method, Class)执行静态检查,决定方法的整体适用性。如果静态检查返回true,Spring使用matches(Method, Class, Object[])为每次调用执行进一步的检查。这样,动态的MethodMatcher基于特定的方法调用决定是否应用pointcut。比如,只在某整数参数大于100时才应用某pointcut,每次调用时,都要做检查。
所以,尽可能使用静态检查。
有效的Pointcut实现
Spring提供了Pointcut的八个实现:两个抽象类(用来快捷创建静态和动态pointcuts),六个具体类。
Implementation Class | Description |
---|---|
org.springframework.aop.support.annotation.AnnotationMatchingPointcut | 查找类或者方法的特定注解 |
org.springframework.aop.aspectj.AspectJExpressionPointcut | 使用AspectJ weaver评估pointcut表达式 |
org.springframework.aop.support.ComposablePointcut | 使用union()和intersection()这样的方法,组合两个或者多个pointcuts |
org.springframework.aop.support.ControlFlowPointcut | 检查控制流内的所有方法,就是说,要调用一个方法,而直接或者间接调用的其他任何方法 |
org.springframework.aop.support.DynamicMethodMatcherPointcut | 用来构造动态pointcuts |
org.springframework.aop.support.JdkRegexpMethodPointcut | 使用正则表达式定义pointcuts |
org.springframework.aop.support.NameMatchMethodPointcut | 简单的方法名称列表的匹配 |
org.springframework.aop.support.StaticMethodMatcherPointcut | 用来构造静态pointcuts |
使用DefaultPointcutAdvisor
使用任何Pointcut实现前,你首先要增加一个Advisor接口(或者PointcutAdvisor)的实例。Spring的Advisor代表一个aspect-耦合的advice和pointcuts-管理那些方法应该被advised,以及怎么advised。Spring提供了一些PointcutAdvisor的实现,现在我们只关心一个DefaultPointcutAdvisor-它是个简单的PointcutAdvisor,关联一个Pointcut和一个Advice。
使用StaticMethodMatcherPointcut增加静态Pointcut
下来,我们扩展StaticMethodMatcherPointcut,增加一个简单的静态pointcut。StaticMethodMatcherPointcut扩展了StaticMethodMatcher类,
它实现了MethodMatcher接口,你需要实现matches(Method, Class<?>)。同时,我们覆盖了getClassFilter()方法,确保方法所在的类型正确。
public class GoodGuitarist implements Singer {
@Override public void sing() {
System.out.println("Who says I can't be free \n" +
"From all of the things that I used to be");
}
}
public class GreatGuitarist implements Singer {
@Override public void sing() {
System.out.println("I shot the sheriff, \n" +
"But I did not shoot the deputy");
}
}
interface Singer {
void sing();
}
下来是SimpleStaticPointcut类,使DefaultPointcutAdvisor只能应用于GoodGuitarist类的sing()方法:
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import java.lang.reflect.Method;
public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> cls) {
return ("sing".equals(method.getName()));
}
@Override
public ClassFilter getClassFilter() {
return cls -> (cls == GoodGuitarist.class);
}
}
看matches(Method, Class<?>),只有方法名称是sing才返回true。
下来是SimpleAdvice,在方法调用前后写消息:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class SimpleAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println(">> Invoking " + invocation.getMethod().getName());
Object retVal = invocation.proceed();
System.out.println(">> Done\n");
return retVal;
}
}
下来是测试,使用SimpleAdvice和SimpleStaticPointcut增加一个DefaultPointcutAdvisor的实例。因为两个类实现了相同的接口,代理可以基于接口增加:
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
public class StaticPointcutDemo {
public static void main(String... args) {
GoodGuitarist johnMayer = new GoodGuitarist();
GreatGuitarist ericClapton = new GreatGuitarist();
Singer proxyOne;
Singer proxyTwo;
Pointcut pc = new SimpleStaticPointcut();
Advice advice = new SimpleAdvice();
Advisor advisor = new DefaultPointcutAdvisor(pc, advice);
ProxyFactory pf = new ProxyFactory();
pf.addAdvisor(advisor);
pf.setTarget(johnMayer);
proxyOne = (Singer) pf.getProxy();
pf = new ProxyFactory();
pf.addAdvisor(advisor);
pf.setTarget(ericClapton);
proxyTwo = (Singer) pf.getProxy();
proxyOne.sing();
proxyTwo.sing();
}
}
输出是
>> Invoking sing
Who says I can't be free
From all of the things that I used to be
>> Done
I shot the sheriff,
But I did not shoot the deputy
使用DyanmicMethodMatcherPointcut增加动态Pointcut
增加动态pointcut和增加静态的差不多:
class SampleBean {
void foo(int x) {
System.out.println("Invoked foo() with: " + x);
}
void bar() {
System.out.println("Invoked bar()");
}
}
比如,我们只advise foo()方法,传入的整数参数不等于100。
DynamicMethodMatcherPointcut有一个抽象方法,matches(Method, Class<?>, Object[]),还必须实现matches(Method, Class<?>)方法控制静态检查行为:
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
import java.lang.reflect.Method;
public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> cls) {
System.out.println("Static check for " + method.getName());
return "foo".equals(method.getName());
}
@Override
public boolean matches(Method method, Class<?> cls, Object... args) {
System.out.println("Dynamic check for " + method.getName());
if (args.length == 0) {
throw new RuntimeException("args length error.");
} else {
int x = (int) args[0];
return x != 100;
}
}
@Override
public ClassFilter getClassFilter() {
return cls -> (cls == SampleBean.class);
}
}
使用matches(Method, Class<?>)做方法检查,使用matches(Method, Class<?>, Object[])做参数检查。通过覆盖matches(Method method, Class<?> cls)方法,我们只对SampleBean类的foo(int x)方法做动态检查,而不会对bar()方法做动态检查。
现在做测试:
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
public class DynamicPointcutDemo {
public static void main(String... args) {
SampleBean target = new SampleBean();
Advisor advisor = new DefaultPointcutAdvisor(new SimpleDynamicPointcut(), new SimpleAdvice());
ProxyFactory pf = new ProxyFactory();
pf.setTarget(target);
pf.addAdvisor(advisor);
SampleBean proxy = (SampleBean)pf.getProxy();
proxy.foo(1);
proxy.foo(10);
proxy.foo(100);
proxy.bar();
proxy.bar();
proxy.bar();
}
}
输出是
Static check for foo
Static check for bar
Static check for toString
Static check for clone
Static check for foo
Dynamic check for foo
>> Invoking foo
Invoked foo() with: 1
>> Done
Dynamic check for foo
>> Invoking foo
Invoked foo() with: 10
>> Done
Dynamic check for foo
Invoked foo() with: 100
Static check for bar
Invoked bar()
Invoked bar()
Invoked bar()
正如我们期望的,只有前两个foo()是advised。bar()调用都不是动态的。有意思的是,foo()方法执行两次静态检查:初始化期间所有方法都被检查,和第一次调用的时候。
简单的名称匹配
我们增加pointcut的时候,经常是只匹配方法的名称,而忽略它的签名和返回类型。现在,我们使用NameMatchMethodPointcut(StaticMethodMatcherPointcut的子类)匹配方法的名称列表。
先看GrammyGuitarist类:
class GrammyGuitarist implements Singer {
@Override
public void sing() {
System.out.println("sing: Gravity is working against me\n" +
"And gravity wants to bring me down");
}
public void sing(Guitar guitar) {
System.out.println("play: " + guitar.play());
}
void rest() {
System.out.println("zzz");
}
void talk() {
System.out.println("talk");
}
}
我们打算匹配sing()、sing(Guitar)和rest()方法:
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
public class NamePointcutDemo {
public static void main(String... args) {
GrammyGuitarist johnMayer = new GrammyGuitarist();
NameMatchMethodPointcut pc = new NameMatchMethodPointcut();
pc.addMethodName("sing");
pc.addMethodName("rest");
Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());
ProxyFactory pf = new ProxyFactory();
pf.setTarget(johnMayer);
pf.addAdvisor(advisor);
GrammyGuitarist proxy = (GrammyGuitarist) pf.getProxy();
proxy.sing();
proxy.sing(new Guitar());
proxy.rest();
proxy.talk();
}
}
不需要增加类,简单地增加一个NameMatchMethodPointcut实例就可以了。我们使用addMethodName()方法给两个方法增加了pointcut。运行结果是:
>> Invoking sing
sing: Gravity is working against me
And gravity wants to bring me down
>> Done
>> Invoking sing
play: G C G C Am D7
>> Done
>> Invoking rest
zzz
>> Done
talk
可以看到,只有talk()是unadvised。
使用正则表达式增加Pointcuts
也可以使用JdkRegexpMethodPointcut,通过正则表达式匹配方法名,增加pointcut:
class Guitarist implements Singer {
@Override
public void sing() {
System.out.println("Just keep me where the light is");
}
void sing2() {
System.out.println("Just keep me where the light is");
}
void rest() {
System.out.println("zzz");
}
}
测试代码:
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
public class RegexpPointcutDemo {
public static void main(String... args) {
Guitarist johnMayer = new Guitarist();
JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut();
pc.setPattern(".*sing.*");
Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());
ProxyFactory pf = new ProxyFactory();
pf.setTarget(johnMayer);
pf.addAdvisor(advisor);
Guitarist proxy = (Guitarist) pf.getProxy();
proxy.sing();
proxy.sing2();
proxy.rest();
}
}
我们也不需要增加类。正则表达式的前导“.*”表示匹配任何包和类的方法。输出是
>> Invoking sing
Just keep me where the light is
>> Done
>> Invoking sing2
Just keep me where the light is
>> Done
zzz
增加注解匹配的Pointcuts
如果你的程序是基于注解的,就可能想使用注解定义pointcuts。为此,Spring提供了AnnotationMatchingPointcut。我们修改一下前面的例子,看怎么使用注解定义pointcut。
先定义一个注解,叫AdviceRequired,用它声明pointcut:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AdviceRequired {
}
修改Guitarist类:
class Guitarist implements Singer {
@Override
public void sing() {
System.out.println("Dream of ways to throw it all away");
}
@AdviceRequired
void sing(Guitar guitar) {
System.out.println("play: " + guitar.play());
}
void rest() {
System.out.println("zzz");
}
}
现在做测试:
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
/**
* Created by leishu on 18-11-26.
*/
public class AnnotationPointcutDemo {
public static void main(String... args) {
Guitarist johnMayer = new Guitarist();
AnnotationMatchingPointcut pc = AnnotationMatchingPointcut.forMethodAnnotation(AdviceRequired.class);
Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());
ProxyFactory pf = new ProxyFactory();
pf.setTarget(johnMayer);
pf.addAdvisor(advisor);
Guitarist proxy = (Guitarist) pf.getProxy();
proxy.sing(new Guitar());
proxy.rest();
}
}
forMethodAnnotation()方法应用于方法级。如果使用forClassAnnotation(),可应用于类级。输出是
>> Invoking sing
play: G C G C Am D7
>> Done
zzz