Spring 5 - Advisors和Pointcuts
Spring 5 - Advisors和Pointcuts
- Pointcut
- 有效的Pointcut實現
- 使用DefaultPointcutAdvisor
- 使用StaticMethodMatcherPointcut增加靜態Pointcut
- 使用DyanmicMethodMatcherPointcut增加動態Pointcut
- 簡單的名稱匹配
- 使用正則表示式增加Pointcuts
- 增加註解匹配的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