美文网首页我爱编程
Spring学习笔记(七、Spring AOP API)

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

作者: 鲁克巴克诗 | 来源:发表于2017-06-19 21:51 被阅读971次

    上一篇: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的支持)

    相关文章

      网友评论

        本文标题:Spring学习笔记(七、Spring AOP API)

        本文链接:https://www.haomeiwen.com/subject/zzegqxtx.html