Spring AOP(一)

Spring AOP实现原理

  • 动态代理: 利用核心类Proxy和接口InvocationHandler(基于代理模式的思想)
  • 字节码生成: 利用CGLIB动态字节码库

Spring AOP中的关键字

1.Joinpoint

只支持方法执行类型的Joinpoint,力求付出20%的努力来满足80%的开发需求

2.Pointcut

Spring AOP Pointcut 框架结构:

根接口: org.springframework.aop.Pointcut

public interface Pointcut {

    /**
     * Return the ClassFilter for this pointcut.
     * @return the ClassFilter (never {@code null})
     */
    ClassFilter getClassFilter();

    /**
     * Return the MethodMatcher for this pointcut.
     * @return the MethodMatcher (never {@code null})
     */
    MethodMatcher getMethodMatcher();


    /**
     * Canonical Pointcut instance that always matches.
     */
    Pointcut TRUE = TruePointcut.INSTANCE;

}

其中,ClassFilterMethodMatcher分别用于匹配将执行织入操作的对象及其相应的方法。

如果直接获取类型为PointcutTRUE对象时,那么代表Pointcut的匹配将会针对系统所有的目标类以及它们的实例进行。

MethodMatcher是一个重要的接口,其内部结构如下:

public interface MethodMatcher {

    /**
     * Perform static checking whether the given method matches. If this
     * returns {@code false} or if the {@link #isRuntime()} method
     * returns {@code false}, no runtime check (i.e. no.
     * {@link #matches(java.lang.reflect.Method, Class, Object[])} call) will be made.
     * @param method the candidate method
     * @param targetClass the target class (may be {@code null}, in which case
     * the candidate class must be taken to be the method's declaring class)
     * @return whether or not this method matches statically
     */
    boolean matches(Method method, Class<?> targetClass);

    /**
     * Is this MethodMatcher dynamic, that is, must a final call be made on the
     * {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
     * runtime even if the 2-arg matches method returns {@code true}?
     * <p>Can be invoked when an AOP proxy is created, and need not be invoked
     * again before each method invocation,
     * @return whether or not a runtime match via the 3-arg
     * {@link #matches(java.lang.reflect.Method, Class, Object[])} method
     * is required if static matching passed
     */
    boolean isRuntime();

    /**
     * Check whether there a runtime (dynamic) match for this method,
     * which must have matched statically.
     * <p>This method is invoked only if the 2-arg matches method returns
     * {@code true} for the given method and target class, and if the
     * {@link #isRuntime()} method returns {@code true}. Invoked
     * immediately before potential running of the advice, after any
     * advice earlier in the advice chain has run.
     * @param method the candidate method
     * @param targetClass the target class (may be {@code null}, in which case
     * the candidate class must be taken to be the method's declaring class)
     * @param args arguments to the method
     * @return whether there's a runtime match
     * @see MethodMatcher#matches(Method, Class)
     */
    boolean matches(Method method, Class<?> targetClass, Object[] args);


    /**
     * Canonical instance that matches all methods.
     */
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

方法

boolean matches(Method method, Class<?> targetClass

表示不会对被代理对象的方法参数进行捕获,处理。当方法isRuntime()返回false时,表示执行的是该方法。这种类型的处理称之为StaticMethodMathcer,可以对匹配到的结果进行缓存,所以处理性能很高。

同理,当isRuntime()返回true时,代表执行的是方法

boolean matches(Method method, Class<?> targetClass, Object[] args)

表示要对被代理对象的方法参数进行捕获,处理。这种类型的处理称之为DynamicMethodMatcher,因为参数不定,无法进行缓存,所以性能不高。

常见的几种Pointcut实现:

1.NameMatchMethodPointcut:

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("test1");
pointcut.setMappedNames(new String[]{"test1","test2"]};

该类可以直接根据给定的方法名进行匹配(也支持简单的模糊匹配),不过通过上边的代码也可以了解到,该匹配不涉及目标方法的参数,所以对于重载方法则无法进行有效的支持。

2.AbstractRegexpMethodPointcut:

该类支持正则表达式来匹配方法,其下有一个重要的实现类:JdkRegexpMethodPointcut。代码片段如下:

JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*match.*");
pointcut.setPatterns(new String[]{".*match.*",".*matches"});

3.AnnotationMatchingPointcut:

基于注解的方法匹配。代码片段如下:

定义自定义注解:

//类级别的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface ClassLevelAnnotation {
}

//方法级别的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLevelAnnotation {
}

将注解应用在某一个类上:

@ClassLevelAnnotation
public class GenericTargetObject {
  
  @MethodLevelAnnotation
  public void hello() {
    System.out.println("hello");
  }
}

利用AnnotationMatchingPointcut来进行匹配:

类级别的匹配:

AnnotationMatchingPointcut pointcutByClass = new AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);

方法级别的匹配:

AnnotationMatchingPointcut pointcutByMethod = new AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);

或者两个结合使用:

AnnotationMatchingPointcut pointcutByClassAndMethod = new AnnotationMatchingPointcut.forMethodAnnotation(ClassLevelAnnotation.class, MethodLevelAnnotation.class);

4.扩展Pointcut:

通过继承抽象类StaticMethodMatcherPointcutDynamicMethodMatcherPointcut来自定义自己的Pointcut。

3.Advice

Spring Advice可以分为两大类:per-classper-instance

1.per-class 是指该类型的Advice的实例可以在目标对象类的所有实例之间共享,这种类型的Advice通常只是提供方法拦截的功能,不会为目标对象类保存任何状态或者添加新的特性。

其包括:

  • Before Advice

代码片段如下:

public interface MethodBeforeAdvice extends BeforeAdvice {
  void before(Method method, Object[] args, Object object) throws Throwable;
}

BeforeAdvice接口为一个标记接口,需要继承该接口,实现自己的before逻辑

  • ThrowsAdvice

代码片段如下:

public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Throwable t) {
      //普通异常处理
    }
    
    public void afterThrowing(RuntimeException e) {
      //运行时异常处理
    }
}

ThrowsAdvice同样也为一个标记接口,不过在实现它时,方法定义需要遵循如下规则:

void afterThrowing([Method, args, target], ThrowableSubclass);

其中,[]中的可以省略。

  • AfterReturningAdvice

其接口定义如下:

public interface AfterReturningAdvice extends AfterAdvice {

    /**
     * Callback after a given method successfully returned.
     * @param returnValue the value returned by the method, if any
     * @param method method being invoked
     * @param args arguments to the method
     * @param target target of the method invocation. May be {@code null}.
     * @throws Throwable if this object wishes to abort the call.
     * Any exception thrown will be returned to the caller if it's
     * allowed by the method signature. Otherwise the exception
     * will be wrapped as a runtime exception.
     */
    void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;

}

通过该接口方法可以获取到原方法成功执行之后的返回值,不过不可以对其进行修改。

  • Around Advice

Spring没有提供该接口的规范,而是直接使用了AOP Alliance的标准接口,如下:

pubic interface MethodInterceptor extends Interceptor {
  Object invoke(MethodInvocation invocation) throws Throwable;
}

能够cover前面几种类型的Advice,很强大!

2.per-instance类型的Advice不会在目标类所有对象实例之间共享,而是会为不同的实例对象保存它们各自的状态以及相关逻辑。它织入面向的是对象。在Spring AOP中,Introduction就是唯一的一种per-instance型的Advice,对其进行实现的是IntroductionInterceptor

public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
}

IntroductionInterceptor接口还继承了两个重要的接口MethodInterceptorDynamicIntroductionAdvice。其中接口DynamicIntroductionAdvice界定为哪些接口类提供相应的拦截功能,通过接口MethodInterceptor来处理新添加的接口方法调用。

如果要添加切入的逻辑操作,可以直接扩展IntroductionInterceptor中的invoke方法,不过也可以利用Spring提供的两个实现类来完成:

  • DelegatingIntroductionInterceptor (需要设置其scope = prototype)

来简单看一段伪代码演示:

{SubClass} targetObject = new {Class()};
DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(delegate);
//进行织入
{SubClass} proxyObject = {SubClass} weaver.weave(interceptor).getPxory();
  • DelegatePerTargetObjectIntroductionInterceptor

4.Aspect

Aspect在Spring中表示为Advisor,其体系结构分为两类:PointcutAdvisorIntroductionAdvisor

1.PointcutAdvisor接口:

public interface PointcutAdvisor extends Advisor {

    /**
     * Get the Pointcut that drives this advisor.
     */
    Pointcut getPointcut();

}

对该接口的实现有几个比较重要的类:DefaultPointcutAdvisorNameMatchMethodPointcutAdvisorRegexpMethodPointcutAdvisor

来看一下DefaultPointcutAdvisor的构造方法:

    public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
        this.pointcut = pointcut;
        setAdvice(advice);
    }

可以看出,该类是持有一个PointcutAdvice,所以从这一点上也可以将Spring的Advisor理解为封装了PointcutAdvice的一个容器。

然后对于类NameMatchMethodPointcutAdvisorRegexpMethodPointcutAdvisor,联想一下前边了解到的几种类型的Pointcut,不难得出,这两个类对于Pointcut的封装则分别为NameMatchMethodPointcutRegexpMethodPointcut

2.IntroductionAdvisor接口:是对Introduction类型的封装,其内部持有的Pointcut和Advice只能是都是Introduction类型的。

5.Ordered

可以通过Ordered接口来为不同的Advisor配置相应的优先级。

在xml中来为Advisor指定不同的优先级:

<bean id="pemissionAuthAdvisor" class="xxx">
    <property name="order" value="1">
 <bean id="exceptionBarrierAdvisor" class="yyy">
    <property name="order" value="0">   

当然也可以在代码中,通过调用抽象类AbstractPointcutAdvisor

    public void setOrder(int order) {
        this.order = order;
    }

来为不同的Advisor设置相应的优先级

Spring AOP 织入器

上边的流程全部走完一遍之后,接下来就是如何调用Spring AOP提供的接口来获得针对目标对象的一个代理对象啦。有两种方法,分别是ProxyFactoryProxyFactoryBean

  • ProxyFactory

简单看一下ProxyFactory的代码演示:

ProxyFactory weaver = new ProxyFactory();
Advisor advisor = ..;
waever.addAdvisor(advisor);
weaver.setTarget({yourTargetObject});
Object proxyObject = weaver.getProxy();

所以如果要利用ProxyFactory来生成代理对象,需要设置两处:目标对象:yourTargetObjectAdvisor

上边说到Sping AOP的实现包括动态代理字节码生成,那么什么时候采用其中的某种方式呢?

对于动态代理如果Spring AOP检测到targetObject实现了相应的接口,那么就会采用动态代理。可以显示指定接口,比如:

weaver.setInterfaces();

也可以不用指定,框架会自动检测。

但是也可以 通过设置相应的属性来强制转换成字节码生成方式,方式为:

ProxyFactory weaver = new ProxyFactory();
weaver.setproxyTargetClass(true);

另外还有两种方式是要采用字节码生成方式来代理的:

1.如果targetObject没有实现任何接口

2.将ProxyFactory的optimize属性置为true

以前总结的都是针对类级别,即per-class的代理,而对于per-instance级别的代理该如何去做呢?

来简单看一段伪代码演示吧:

ProxyFactory weaver = new ProxyFactory();
/*
 *面向接口的代理
 *weaver.setProxyTargetClass(true);面向类的代理,即CGLIB代理
 */
weaver.setInterfaces(new class[] {Interface1.class, Interface2.class}); 
DemoIntroductionInterceptor advice = new DemoIntroductionInterceptor();
weaver.addAdvice(advice);
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice, advice);
weaver.addAdvisor(advisor);
Object proxy = weaver.getProxy();
  • ProxyFactoryBean

ProxyFactoryBean为IOC容器中的一个织入器。同proxyFactory,它也支持基于接口和基于类的代理。

参考书籍:

1.《Spring揭秘》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容