如何配置 Spring 的通知(前置通知,后置通知,环绕通知,异常通知)

前置通知

  1. 引入 Spring AOP 的相关 jar 包:
    aopalliance-1.0.jar
    spring-aop-4.2.1.RELEASE.jar
    commons-logging-1.2.jar
    spring-beans-4.2.1.RELEASE.jar
    spring-core-4.2.1.RELEASE.jar
    spring-context-4.2.1.jar
    spring-expression-4.2.1.jar

  2. 编写切面(实现 MethodBeforeAdvice 接口,重写 before 方法):

    package com.aop.advice;
    import java.lang.reflect.Method;
    import org.springframework.aop.MethodBeforeAdvice;
    
    /**
     * 类名称:前置通知的切面
     * 全限定性类名: com.aop.advice.MyBeforeAdvice
     * @author 曲健磊
     * @date 2018年6月30日下午6:45:38
     * @version V1.0
     */
    public class MyBeforeAdvice implements MethodBeforeAdvice {
    
        @Override
        public void before(Method method, Object[] params, Object obj) throws Throwable {
            System.out.println("这是权限验证的代码");
            // 注:代理其实就是copy了一个一模一样的字节码文件
        }
    
    }
  3. 在 Spring 的配置文件中注册切面(系统级服务)

    <?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:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        <!-- 注册StudentServiceImpl -->
        <bean id="studentService" class="com.aop.service.impl.StudentServiceImpl"></bean>   
    
        <!-- 注册切面 -->
        <bean id="beforeAdvice" class="com.aop.advice.MyBeforeAdvice"></bean>
    
    </beans>
  4. 注册代理工厂来生成目标类(StudentService)的代理

    <!-- 注册代理生成器,注入目标类接口(jdk 动态代理),目标类,通知 -->
    <bean id="beforeProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="studentService" />
        <property name="interfaces" value="com.aop.service.IStudentService" />
        <property name="interceptorNames" value="beforeAdvice" />
    </bean>

    这个代理其实就是 new 了一个被代理对象所实现的接口的一个实现类对象,不同的是这个代理对象不仅可以执行原目标类的方法,还额外获得了切面的方法。

  5. 获取代理对象,调用代理对象的方法:

    package com.aop.test;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.aop.service.IStudentService;
    
    /**
     * 类名称:前置通知测试类
     * 全限定性类名: com.aop.test.AdviceTest
     * @author 曲健磊
     * @date 2018年6月30日下午6:43:37
     * @version V1.0
     */
    public class AdviceTest {
    
        private ClassPathXmlApplicationContext ac = null;
    
        @Before
        public void init() {
            ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        }
    
        @Test
        public void testMyBeforeAdvice() {
            IStudentService studentService = (IStudentService) ac.getBean("beforeProxy");
            studentService.saveStudent();
        }
    }

    执行结果如下:
    这里写图片描述

    至此,我们就实现了在不改变原 StudentService 代码的基础上,添加了额外功能,减少代码的重复,松耦合(这其实就是我们按照 AOP 思想编程的目的),这就有利于后期 StudentService 代码的复用。

后置通知

  1. 编写实现了 AfterReturningAdvice 接口的切面:

    package com.aop.advice;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.AfterReturningAdvice;
    
    /**
     * 类名称:后置通知
     * 全限定性类名: com.aop.advice.MyAfterAdvice
     * @author 曲健磊
     * @date 2018年6月30日下午8:48:27
     * @version V1.0
     */
    public class MyAfterAdvice implements AfterReturningAdvice {
    
        /**
         * returnValue:方法的返回值
         * method:方法前面
         * args:参数列表
         * target:被代理的目标对象
         */
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println("Object returnValue:" + returnValue); // 方法的返回值
            System.out.println("Method method:" + method); // 方法的签名,反射获取
            System.out.println("Object[] args:"); // 方法的参数列表
            for (Object obj : args) {
                System.out.println(obj);
            }
            System.out.println("Object target:" + target); // 被代理的对象
        }
    
    }
  2. 在 Spring 中注册切面和代理生成器:

    <!-- 注册后置切面 -->
    <bean id="afterAdvice" class="com.aop.advice.MyAfterAdvice"></bean>
    
    <!-- 注册代理生成器 -->
    <bean id="afterProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="studentService" />
        <property name="interceptorNames" value="afterAdvice" />
    </bean>

    注:因为在 ProxyFactoryBean 中,它的 autodetectInterfaces 属性值为 true,它可以自动侦测被代理对象所实现的接口,所以也可以不注入 interfaces 这个属性。

    扫描二维码关注公众号,回复: 2557928 查看本文章
  3. 获取代理对象,调用代理对象的方法

    /**
     * 测试后置通知
     */
    @Test
    public void testMyAfterAdvice() {
        IStudentService studentService = (IStudentService) ac.getBean("afterProxy");
        studentService.saveStudent("姓名", 666);
    }

    执行结果:
    这里写图片描述

    对比 MethodBeforeAdvice 和 AfterReturningAdvice:他们都可以获得方法的签名,参数列表,被代理的目标对象,但是只有后置通知才可以获得方法的返回值。

环绕通知

环绕通知的切面其实并没有使用 Spring 框架里面的 API,它是直接使用的 aopalliace 里面的 MethodInterceptor 接口,只有最后的代理生成器使用的 Spring 里面的 ProxyFactoryBean。

  1. 编写实现了 MethodInterceptor 接口的切面:

    package com.aop.advice;
    
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    /**
     * 类名称:环绕型通知,可以在方法执行前后插入额外功能
     * 全限定性类名: com.aop.advice.MyAroundAdvice
     * @author 曲健磊
     * @date 2018年7月1日上午10:08:14
     * @version V1.0
     */
    public class MyAroundAdvice implements MethodInterceptor {
    
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("环绕通知执行前.....");
            Object result = invocation.proceed(); // 被代理的对象执行的方法的返回值
            System.out.println("环绕通知执行后.....返回值" + result);
            result = 99;
            return result;
        }
    }
  2. 注册切面以及代理生成器:

    <!-- 注册环绕切面 -->
    <bean id="aurondAdvice" class="com.aop.advice.MyAroundAdvice"></bean>
    
    <!-- 注册代理生成器 -->
    <bean id="aroundProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="studentService" />
        <property name="interceptorNames" value="aurondAdvice" />
    </bean>
  3. 获取代理对象,调用代理对象的方法:

    /**
     * 测试环绕通知
     */
    @Test
    public void testMyAroundAdvice() {
        IStudentService studentService = (IStudentService) ac.getBean("aroundProxy");
        int i = studentService.saveStudent("姓名", 666);
        System.out.println("最终获取的方法的返回值" + i);
    }
  4. 执行结果:
    这里写图片描述

    不难看出虽然我们在 invoke 方法中通过调用 invocation.proceed 方法得到了被代理对象方法的返回值,但是在 invoke 方法返回之前我们仍然可以修改它。

    注:使用环绕型通知 MethodInterceptor 时,就没有办法获取方法的参数列表,方法前面,代理对象的信息了。

异常通知

异常通知(ThrowsAdvice)其实是 AfterAdvice 的一个子接口,它和 AfterReturningAdvice 这个接口都是 AfterAdvice 的子接口。

  1. 编写实现了 ThrowsAdvice 接口的切面:

    package com.aop.advice;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.ThrowsAdvice;
    
    /**
     * 类名称:异常通知
     * 全限定性类名: com.aop.advice.MyThrowsAdvice
     * @author 曲健磊
     * @date 2018年7月1日上午11:33:30
     * @version V1.0
     */
    public class MyThrowsAdvice implements ThrowsAdvice {
    
        public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
            System.out.println("在" + method + "方法上发生了:");
            System.out.println(ex.getMessage() + "异常");
            System.out.println("被代理的目标对象:" + target);
            System.out.println("参数列表为:");
            for (Object obj : args) {
                System.out.println(obj);
            }
        }
    
    }

    具体实现哪个方法需要参考 ThrowsAdvice 的类注释:

    /*
     * Copyright 2002-2008 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package org.springframework.aop;
    
    /**
     * Tag interface for throws advice.
     *
     * <p>There are not any methods on this interface, as methods are invoked by
     * reflection. Implementing classes must implement methods of the form:
     *
     * <pre class="code">void afterThrowing([Method, args, target], ThrowableSubclass);</pre>
     *
     * <p>Some examples of valid methods would be:
     *
     * <pre class="code">public void afterThrowing(Exception ex)</pre>
     * <pre class="code">public void afterThrowing(RemoteException)</pre>
     * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre>
     * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre>
     *
     * The first three arguments are optional, and only useful if we want further
     * information about the joinpoint, as in AspectJ <b>after-throwing</b> advice.
     *
     * <p><b>Note:</b> If a throws-advice method throws an exception itself, it will
     * override the original exception (i.e. change the exception thrown to the user).
     * The overriding exception will typically be a RuntimeException; this is compatible
     * with any method signature. However, if a throws-advice method throws a checked
     * exception, it will have to match the declared exceptions of the target method
     * and is hence to some degree coupled to specific target method signatures.
     * <b>Do not throw an undeclared checked exception that is incompatible with
     * the target method's signature!</b>
     *
     * @author Rod Johnson
     * @author Juergen Hoeller
     * @see AfterReturningAdvice
     * @see MethodBeforeAdvice
     */
    public interface ThrowsAdvice extends AfterAdvice {
    
    }
  2. 注册切面以及代理生成器:

    <!-- 注册异常切面 -->
    <bean id="throwsAdvice" class="com.aop.advice.MyThrowsAdvice"></bean>
    
    <!-- 注册代理生成器 -->
    <bean id="throwsProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="studentService" />
        <property name="interceptorNames" value="throwsAdvice" />
    </bean>
  3. 执行代理对象的方法:

    /**
     * 测试异常通知
     */
    @Test
    public void testMyThrowsAdvice() {
        IStudentService studentService = (IStudentService) ac.getBean("throwsProxy");
        studentService.delStudent(3); // 里面写了一个除零语句
    }
  4. 执行结果:
    这里写图片描述

    注:异常通知和后置通知不一样,后置通知可以获取方法的返回值,异常通知无法获取方法的返回值(因为发生异常了)。后置通知和环绕通知还不一样,后置通知无法改变方法的返回值,环绕通知可以修改方法的返回值,因为环绕通知是基于一种回调的方式来进行的代理。

虽然 Spring 通知(Advice)的优点显而易见,但是也有不少的缺点:
1. 一个代理对象只能代理一个目标对象
2. 从容器中获取的 bean 的 id 是代理对象的 id,而不是目标对象的 id
3. 通知只能切入到目标对象的所有方法,不能指定切入具体的某个方法

顾问(Advisor)由此而生…

猜你喜欢

转载自blog.csdn.net/a909301740/article/details/80868964