spring的核心思想(二):AOP-环绕通知

之前练习了前置通知

1.创建前置通知类
package aop;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/**
 * User: 晨风
 * <p>
 * Author: 晨风
 * Date: 22:59
 */
// 自定义自己录业务方法名称的前置通知  前置通知:目标方法执行之前先执行的额外操作
public class MyBeforeAdvice implements MethodBeforeAdvice {

    // before  参数 1.当前执行方法对象     2.当前执行方法的参数     3.目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("当前执行方法:" + method.getName());
        System.out.println("当前执行方法参数:" + args[0]);
        System.out.println("目标对象:" + target);
    }
}
2.配置文件中,组装切面
<!-- 前置通知-->
<!-- 注册通知-->
<bean class="com.pcf.aop.MyBeforeAdvice" id="myBeforeAdvice"></bean>

<!--组装切面-->
<aop:config>
    <!-- 配置切入点pointcut
            id:切入点在工厂的唯一标识
            expression:用来制定切入项目中那些组件中哪些方法
                        execution(返回值 包.类名.*(..))
    -->
    <aop:pointcut id="pc" expression="execution(* aop.EmpServiceImpl.*(..))"/>
    <!-- 配置切面
           advice-ref:工厂中通知id   pointcut-ref:工厂切入点唯一标识
    -->
    <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pc"/>

</aop:config>

可见实现了MethodBeforeAdvice接口,在before重写方法中定义自己在执行业务功能之前要实现的额外操作

环绕通知

1.创建环绕通知类
package com.pcf.advices;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.util.Date;

/**
 * User: 晨风
 * <p>
 * @Author: 晨风
 * @Date: 22:58
 *
 * 自定义环绕通知用来记录目标方法的执行时长
 */
public class MethodInvokeTimeAdvice implements MethodInterceptor {
    /**
     * Interceptor 就是相当于拦截器
     * @param invocation 获取当前执行方法  获取当前执行方法参数  获取目标对象  放行目标方法
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("==========进入环绕通知==========");
        try {
            long startTime = System.currentTimeMillis();
          
            // 放行目标方法
            Object proceed = invocation.proceed();
            long endTime = System.currentTimeMillis();
            long processTime = endTime - startTime;
            System.out.println("方法 :" + invocation.getMethod().getName() + "执行了 [" + processTime + "]ms !");
            System.out.println("==========离开环绕通知==========");
            return proceed;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("环绕通知--异常通知");
        }
        return null;
    }
}
2.配置文件中,组装切面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

    <bean class="com.pcf.dao.DeptDAOImpl" id="deptDAO"></bean>

    <bean class="com.pcf.service.DeptServiceImpl" id="deptService">
        <property name="deptDAO" ref="deptDAO"></property>
    </bean>

    <!-- 注册通知-->
        <!-- 前置通知-->
    <bean class="com.pcf.aop.MyBeforeAdvice" id="beforeAdvice"></bean>

    <bean class="com.pcf.advices.MethodInvokeTimeAdvice" id="invokeTimeAdvice"></bean>

    <!-- 组装切面-->
    <aop:config>
        <!-- 配置切入点pointcut
                id:切入点在工厂的唯一标识
                expression:用来制定切入项目中那些组件中哪些方法
                            execution(返回值 包.类名.*(..))
        -->
        <!-- 配置切面-->
        <aop:pointcut id="invokeTimePc" expression="execution(* com.pcf.service.*ServiceImpl.*(..))"></aop:pointcut>
        <!-- 组装切面-->
        <aop:advisor advice-ref="invokeTimeAdvice" pointcut-ref="invokeTimePc"></aop:advisor>
    </aop:config>

</beans>

在这里我们看到,环绕通知和前置通知不一样之处在于,创建环绕通知类时实现的是MethodInterceptor接口,而重写的方法invoke。回顾一下之前学习的动态代理的代码实现

 @Override
            /**
             *  @Param proxy Object
             *          当前创建好的代理对象
             *  @Param method Method
             *          当前代理对象执行的方法对象
             *  @Param args Object[]
             *          当前代理对象执行的方法的参数
             *
             * @Note  通过动态代理对象调用自己里面代理方法时会优先执行InvocationHandle类中的invoke
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 调用目标类中业务方法前的操作
                System.out.println("==============InvocationHandle类中的invoke=================");
                System.out.println("当前执行的方法: " + method.getName());
                System.out.println("当前执行的方法的参数: " + args[0]);
                // 调用目标类中业务方法通过反射机制  调用目标类中当前方法
                Object invoke = method.invoke(new UserServiceImpl(), args);
                // 调用目标类中业务方法前的操作
                ...
                return invoke;
            }

没错,几乎一模一样。环绕通知类实现的MethodInterceptor接口中重写的invoke方法,正是我们之前学习动态代理时的invoke方法。当时我们还做出了注释

            /**
             * @Note  通过动态代理对象调用自己里面代理方法时会优先执行InvocationHandle类中的invoke
             */

所有的通知,即附加功能都是在InvocationHandle类中的invoke方法中实现的。

只是,我们之前学习动态代理时,并不知道除了执行目标方法之外的语句都叫做通知。
在环绕通知类中,有这样一行注释: // 放行目标方法 这是为什么呢?

在前置通知中,框架完全可以通过执行顺序,去先执行前置类,再执行目标方法。

但是在环绕通知中,前置通知和后置通知都存在的时候,前置通知要在执行目标方法前执行,后置通知要在执行目标方法后执行。所以,在invoke中,会先拦截目标方法的执行,当你前置通知执行完了,通过invocation.proceed()放行目标方法后,目标方法才会执行,再之后执行后置方法。

从接口名称中也可以看出来,MethodInterceptor是继承了Interceptor的,因此这个接口本质上也是一个拦截器。

**

至于环绕通知实现了MethodInterceptor接口和InvocationHandle接口的关系,目前咱这水平暂时探究不到。这里先记个待办!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容