Spring学习笔记(七、Spring AOP API)

上一篇:Spring学习笔记(六、Spring AOP基本概念)

一、Spring AOP API

  • 这是Spring 1.2历史用法,现在(V4.0)仍然支持。
  • 这是Spring AOP 基础,不得不了解。
  • 现在的用法也是基于历史的,只是更简便了。

1. Pointcut

  • 实现之一:NameMatchMethodPointcut,根据方法名字进行匹配。
  • 成员变量:mappedNames,匹配的方法名集合。
    创建BizLogic接口:
package test16;
/**
 * Created by amber on 2017/6/19.
 */
public interface BizLogic {
    String save();
}

创建实现类BizLogincImpl :

package test16;
/**
 * Created by amber on 2017/6/19.
 */
public class BizLogincImpl implements BizLogic {
    public String save() {
        System.out.println("BizLogincImpl:save");
        return "BizLogincImpl:save";
    }
}

applicationContext:

  <bean id="bizLogincTarget" class="test16.BizLogincImpl"></bean>
    <bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
        <property name="mappedNames">
            <list>
                <value>sa*</value>
            </list>
        </property>
    </bean>

2. Before advice

  • 一个简单的通知类型
  • 只是在进入方法之前被调用,不需要MethodInvocation对象
  • 前置通知可以在连接点执行之前插入自定义行为,但不能改变返回值。
    创建前置通知类BeforeAdvice :
package test16;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
 * Created by amber on 2017/6/19.
 */
public class BeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("我是前置通知,拦截的方法名:" + method.getName() + "  目标类名:" + target.getClass().getName());
    }
}

3. Throws advice

  • 如果连接点抛出异常,throws advice 在连接点返回后被调用。
  • 如果throws-advice的方法抛出异常,那么它将覆盖原有异常
  • 接口org.springframework.aop.ThrowsAdvice 不包含任何方法,仅仅是一个声明,实现类需要实现类似下面的方法:
    void afterThrowing([Method,args,target],ThrowableSubclass);
    如:
public void afterThrowing(Exception ex)
public void afterThrowing(RemoteException ex)
public void afterThrowing(Method method,Object[] args,Object target,Exception ex)
public void afterThrowing(Method method,Object[] args,Object target,ServletException ex)

创建MyThrowsAdvice类:

package test16;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
/**
 * Created by amber on 2017/6/19.
 */
public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(Exception ex) {
        System.out.println("抛出异常通知: afterThrowing 1");
    }
    public void afterThrowing(Method method, Object[] objects, Object target, Exception ex) {
        System.out.println("抛出异常通知: afterThrowing 2,出现异常的方法名:" + method.getName() + "   出现异常的类名:" + target.getClass().getName());
    }
}

4. After Returning advice

  • 后置通知必须实现org.springframework.aop.AfterReturningAdive接口
  • 可以访问返回值(但不能进行修改),被调用的方法,方法的参数和目标。
  • 如果抛出异常,将会抛出拦截器链,替代返回值。
    创建AfterReturningAdvice 类:
package test16;
import java.lang.reflect.Method;
/**
 * Created by amber on 2017/6/19.
 */
public class AfterReturningAdvice implements org.springframework.aop.AfterReturningAdvice {
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
         System.out.println("我是返回后通知, 拦截的方法名:"+method.getName()+"  目标类名:"+target.getClass().getName()+"  返回的值:"+returnValue );
    }
}

5. Interception around advice

  • Spring的切入点模型使得切入点可以独立与advice重用,以针对不同的advice可以使用相同的切入点。
    创建MethodInterception 类:
package test16;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
 * Created by amber on 2017/6/19.
 */
public class MethodInterception implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("MethodInterceptor(类似环绕通知) invoke 1,拦截的方法: "+invocation.getMethod().getName()+"  拦截的目标类名:"+invocation.getStaticPart().getClass().getName());
        Object obj=invocation.proceed();
        System.out.println("MethodInterceptor(类似环绕通知) invoke 2,拦截的目标: "+obj);
        return obj;
    }
}

6. Introduction advice

  • 引用通知概念:一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。
  • Spring 把引入通知作为一种特殊的拦截通知
  • 需要IntroductionAdvisor和IntroductionInterceptor
  • 仅适用于类,不能和任何切入点一起使用
  • 一个Spring test suite例子
  • 如果调用lock()方法,希望所有的setter方法抛出LockedException异常(如使物体不变,Spring典型例子)
  • 需要一个完成繁重任务的IntroductionInterceptor,这种情况下,可以使用org.springframework.aop.support.DelegatingIntroducationInterceptor。
    有时间再做实验!!!

7. Advisor API in Spring

  • Advisor是仅包含一个切入点表达式关联的单个通知的方面。
  • 除了introductions,advisor可以用于任何通知。
  • org.springframework.aop.support.DefaultPointcutAdvisor是最常用的类,它可以与MethodInterceptor,BeforeAdvice或者ThrowsAdvice一起使用。
  • 它可以混合在Spring同一个AOP代理的advisor和advice。

二、ProxyFactoryBean

  • 创建Spring AOP代理的基本方法是使用org.apringframework.aop.framework.ProxyFactoryBean。
  • 这个类可以完全控制切入点和通知(advice)以及它们的顺序。
  • 使用ProxyFactoryBean或者其他IoC相关类,来创建AOP代理的最重要好处是:通知和切入点也可以由IoC管理。
  • 被代理类没有实现任何接口,使用CGLIB代理,否则JDK代理。
  • 通过设置proxyTargetClass为true,可强制使用CGLIB
  • 如果目标类实现了一个(或者多个)接口,那么创建代理的类型将依赖ProxyFactoryBean 的配置。
  • 如果ProxyFactoryBean的proxyInterfaces属性被设置为一个或者多个全限定接口名,基于JDK的代理将被创建。
  • 如果ProxyFactoryBean的proxyInterfaces属性没有被设置,但是目标类实现了一个(或者更多)接口,那么ProxyFactoryBean将自动检测到这个目标类已经实现了至少一个接口,创建一个基于JDK的代理。

关于CGLIB动态代理,参考:CGLib动态代理原理及实现
关于JDK动态代理,参考:JDK动态代理实现原理

1. Proxying interfaces

  • 使用PointcutBean,不指定proxyInterfaces:
    1. applicationContext中:
 <bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="bizLogincTarget" class="test16.BizLogincImpl"></bean>

    <bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
        <property name="mappedNames">
            <list>
                <value>sa*</value>
            </list>
        </property>
    </bean>
    <bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="beforeAdvice"/>
        <property name="pointcut" ref="pointcutBean"/>
    </bean>
    <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target">
            <ref bean="bizLogincTarget"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>defaultAdvisor</value>
                <value>afterReturningAdvice</value>
                <value>methodInterception</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>
  • 测试类:
  @Test
    public void test16() {
        BizLogic logic=super.getBean("bizLogicImpl");
        logic.save();
    }
  • 结果:
Paste_Image.png
  • 不使用PointcutBean,指定proxyInterfaces:
    <bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
    <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>test16.BizLogic</value>
        </property>
        <property name="target">
            <ref bean="bizLogicTarget"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterReturningAdvice</value>
                <value>methodInterception</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>

结果:

Paste_Image.png
  • 可以使用匿名内部bean来隐藏目标和代理之前的区别
<property name="target">
            <ref bean="bizLogicTarget"/>
        </property>
引用beanId替换成直接引用路径。
  <property name="target">
            <bean class="test16.BizLogincImpl"/>
        </property>
 <bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
    <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>test16.BizLogic</value>
        </property>
        <property name="target">
            <bean class="test16.BizLogincImpl"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterReturningAdvice</value>
                <value>methodInterception</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>

结果:

Paste_Image.png

三、Proxing classes

  • 前面的例子如果没有接口,这种情况下Spring会使用CGLIB代理,而不是JDK动态代理。
  • 如果想,可以强制在任何情况下使用CGLIB,即使有接口。
  • CGLIB代理的工作原理是在运行时生成目标类的子类,Spring配置这个生成的子类委托方法调用到原来的目标。
  • 子类是用来实现Decorator模式,织入通知。

1. CGLIB的代理对用户来说是透明的,需要注意:

  • final方法不能被通知,因为他们不能覆盖。
  • 不用把CGLIB添加到classpath中,在Spring3.2中,CGLIB被重新包装并包含在Spring核心的JAR(即基于CGLIB的AOP就像JDK动态代理一样“开箱即用”)

2. 使用global advisors

  • 用*做通配,匹配所有拦截器加入通知链。
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>

    <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>test16.BizLogic</value>
        </property>
        <property name="target">
            <bean class="test16.BizLogincImpl"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterReturningAdvice</value>
                <value>method*</value>//只适用于实现了MethodInterceptor接口的拦截器,其他advice不适用哦!!!
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>

3. 简化的Proxy定义

  • 使用父子bean定义,以及内部bean定义,可能会带来更清洁更更简洁的代理定义(抽象属性标记父bean定义为抽象的这样它不能被实例化)
    修改applicationContext:
    <bean id="beforeAdvice" class="test16.BeforeAdvice"/>
    <bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
    <bean id="methodInterception" class="test16.MethodInterception"/>
    <bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
    <bean id="baseProxyBean" class="org.springframework.aop.framework.ProxyFactoryBean" lazy-init="true" abstract="true"/>
    <bean id="bizLogicImpl" parent="baseProxyBean">//将baseProxyBean作为父类
        <property name="target">
            <bean class="test16.BizLogincImpl"/>//直接引用实现类
        </property>
        <property name="proxyInterfaces">
            <value>test16.BizLogic</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterReturningAdvice</value>
                <value>methodInterception</value>
                <value>myThrowsAdvice</value>
            </list>
        </property>
    </bean>

结果:


Paste_Image.png

4. 使用ProxyFactory

  • 使用Spring AOP而不依赖于Spring IoC。
  • 大多数情况下,最佳实践是用IoC容器创建AOP代理。
  • 虽然可以硬编码方式实现,但是Spring推荐使用配置或注解方式实现。


    Paste_Image.png

5. 使用auto-proxy

  • Spring也允许使用“自动代理”的bean定义,它可以自动代理选定的bean,这是建立在Spring的“bean post processer”功能基础之上的(在加载bean的时候可以修改)。
  • BeanNameAutoProxyCreator。
Paste_Image.png
  • DefaultAdvisorAutoProxyCreator,当前IoC容器中自动应用,不用显示声明引用advisor的bean定义。
Paste_Image.png

下一篇:Spring学习笔记(八、Spring对AspectJ的支持)

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

推荐阅读更多精彩内容