spring 四aop

作者: 6ab94b6b3878 | 来源:发表于2017-05-10 22:37 被阅读73次

spring一 简介
spring二 ioc di
spring三 bean的生命周期以及作用域


aop是什么?

aop(aspect oriented programming) 面向切面编程,对所有对象或者一类对象的编程。
举个例子,我们现在有3个类,每个类都有同样的一个需求,其功能是记录该类的调用时间,那么我们是不是就需要在三个类中写三遍同样的代码,如果类越来越多呢,每个类都写一遍?不现实,那么这里我们就可以使用aop编程,他可以就写一遍该方法,然后将该方法织入给每个类。看下图再初步理解一下


初步理解图

下面我们根据aop中的概念结合例子来看看(先看概念再看例子再看概念)。

aop中的几个概念

  1. 切面:要实现的功能,是系统模块化的一个切面或领域。如调用时间。
  2. 通知:切面的实际实现,他通知系统新的行为。通知在连接点插入到系统中。
  3. 连接点:应用程序执行过程中插入切面的地点。


    连接点示意图

    这里只看连接点的位置,至于通知类型会在后面介绍,还有异常通知,他会在你抛出异常时将通知插入进来。

  4. 切入点:定义了通知应该应用在哪些连接点上,通知可以应用在aop框架支持的任何连接点。可以这样理解,切入点就是当通知插入到某个连接点时,这个连接点就成了切入点。
  5. 引入:为类添加新方法和属性
  6. 目标对象:被通知的对象,既可以是被通知的类,也可以是第三方类。
  7. 代理:将通知应用到目标对象之后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
  8. 织入:将切面应用到目标对象从而创建的一个新的对象的过程,织入发生在目标对象生命周期的多个点上。

aop的实现

aop是通过生成代理对象来实现通知织入的。就像某个品牌的代理,这个品牌是被代理对象,下面有他们的代理商,而代理商需要通过品牌的授权才能代理,这里的品牌就像我们的被代理对象(firstDemo),而授权过程就是织入(applicationContext中配置),代理商就是代理对象(applicationContext中配置)。


实现

例子

该例子使用前置通知来实现在调用类的方法前打印出时间。
新建一个module,如下


aop

我们现在只用到NameDemo,MyMethodBeforeAdvice和FirstDemo这三个java文件,其余文件在后面用到。在FirstDemo中只有一个name属性和一个方法,代码如下。

public class FirstDemo implements NameDemo{

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 该方法继承自NameDemo
     */
    @Override
    public void printName() {
        System.out.println("执行pringName "+name);
    }

}

NameDemo为接口,FirstDemo继承该接口,firstDemo中的方法就是继承自这里。
MyMethodBeforeAdvice为前置通知,实现我们需要的功能(这里就是显示出调用时间)。代码如下。

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("--------------------------------------------------");
        System.out.println("调用"+method+"的时间是"+new Date());
    }
}

现在我们就可以让firstDemo的方法在调用前打印出调用的时间。我们在applicationContext中做如下配置,产生代理对象,我们拿到代理对象就可以实现了。

    <!--配置被代理对象-->
    <bean id="firstDemo" class="cn.pwc.demo.FirstDemo">
        <property name="name" value="xiaomao"/>
    </bean>

    <!--配置前置通知-->
    <bean id="myMethodBeforeAdvice" class="cn.pwc.demo.MyMethodBeforeAdvice"/>

    <!--配置代理对象-->
    <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--代理接口集-->
        <property name="proxyInterfaces">
            <list>
                <value>cn.pwc.demo.NameDemo</value>
            </list>
        </property>
        <!--通知名集-->
        <property name="interceptorNames">
            <list>
                <value>myMethodBeforeAdvice</value>
            </list>
        </property>
        <!--代理哪些对象-->
        <property name="target" ref="firstDemo"/>
    </bean>

我要进行代理肯定要知道代理的是什么(这里的前置通知)和代理谁(被代理对象),然后生成我们的代理对象。生成代理对象时我们需要将前面的通知和被代理对象配置进去。然后我们去test文件中进行测试

public static void main(String[] args){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        NameDemo demo = (NameDemo) applicationContext.getBean("proxyFactoryBean");
        demo.printName();
    }

这里是不是有点不同,我们平时获取bean应该是
FirstDemo firstDemo = (FirstDemo) applicationContext.getBean("firstDemo");
如果这样获取那么我们刚才的代理操作完全白做了,取到的对象没有经过代理的渠道,自然也不会有通知织入进去,所以我们在获取bean的时候要获取代理对象。而且要注意的是我们这里获取到对象要转为接口对象。运行结果

结果
现在返回看刚才的定义,通知就是MyMethodBeforeAdvice类,切入点就是printName调用前,目标对象就是proxyFactoryBean。接下来来看看通知的类型。

通知的类型

通知有五种类型。分别是前置通知,后置通知,环绕通知,异常通知和引入通知

  1. 前置通知就是在方法调用前调用通知类中的方法。
     /**
     *
     * @param method  被调用的方法
     * @param objects 给这个方法传递的参数
     * @param o       目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("--------------------------------------------------");
        System.out.println("调用"+method+"的时间是"+new Date());
    }
  1. 后置通知就是在方法调用后调用通知类中的方法。
public class MyMethodAfterAdvice implements AfterReturningAdvice {
    /**
     *
     * @param o         被调用方法的返回值
     * @param method    被调用的方法
     * @param objects   被调用方法传的参数
     * @param o1        目标对象
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("后置通知被调用啦");
    }
}
  1. 环绕通知,直接看代码注释
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //被调用方法的方法体前的操作
        System.out.println("环绕通知的前切入点");
        //运行被调用方法的方法体
        Object obj = methodInvocation.proceed();
        //被调用方法的方法体后的操作
        System.out.println("环绕通知的后切入点");
        return obj;
    }
}
  1. 异常通知就是在发生异常时执行通知代码(我们手动在方法中抛错)
public class MyMethodThrowAdvice implements ThrowsAdvice {
    public void afterThrowing(Throwable throwable){
        System.out.println("异常通知被调用");
    }
}

前面4种通知测试一遍如图


test

这里多出的一个方法是为了防止抛出错误之后后面的停止运行导致看不到环绕通知的后切入点和后置通知,现在的firstDemo代码为

public class FirstDemo implements NameDemo,AgeDemo {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 该方法继承自NameDemo
     */
    @Override
    public void printName() {
        System.out.println("执行pringName "+name);
    }

    @Override
    public void printAge() {
        System.out.println("我100岁啦!");
        int i = 1/0;
    }
}
  1. 引入通知 只需要在application中配置就行,比如我们的前置通知只对printName有效而对printAge无效,我们可以如下配置
<!--定义切入点-->
    <bean id="before" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice" ref="myMethodBeforeAdvice"/>
        <property name="mappedNames">
            <list>
                <value>printName</value>
            </list>
        </property>
    </bean>

然后将配置对象中的通知名集的myMethodBeforeAdvice改为before,运行如下。


结果

源码地址:springDemo

相关文章

网友评论

    本文标题:spring 四aop

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