美文网首页我爱编程
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