鲁春利的工作笔记,好记性不如烂笔头



基于XML配置方式声明切面


基于Schema的AOP从Spring2.0之后通过“aop”命名空间来定义切面、切入点及声明通知。

前面我们用到了<aop:aspectj-autoproxy/>标签,Spring在aop的命名空间里面还提供了其他的配置元素:

<aop:advisor>            定义一个AOP通知者
<aop:after>              后置通知
<aop:after-returning>    返回通知
<aop:after-throwing>     异常通知
<aop:around>             环绕通知
<aop:aspect>             定义一个切面
<aop:before>             前置通知
<aop:config>             AOP顶级配置元素,其他元素都位于该元素之下
<aop:pointcut>           定义一个切点,可重用

接口

package com.invicme.apps.aop;

/**
 * 
 * @author lucl
 * 
 * 数学计算接口类
 *
 */
public interface ArithmeticCalculate {
    public int add (int i, int j);
    public int div (int i, int j);
    public String validateNum (String level, int i);
}

实现类

package com.invicme.apps.aop.schema;

import org.apache.log4j.Logger;

import com.invicme.apps.aop.ArithmeticCalculate;

/**
 * 
 * @author lucl
 * 
 * 数学计算实现类
 *
 */
public class ArithmeticCalculateImpl implements ArithmeticCalculate {
    
    private static final Logger logger = Logger.getLogger(ArithmeticCalculateImpl.class);
    
    private int i = 0;
    private int j = 0;
    
    public ArithmeticCalculateImpl () {
        this(0, 0);
    }
    
    public ArithmeticCalculateImpl (int i, int j) {
        this.i = i;
        this.j = j;
    }
    
    @Override
    public int add(int i, int j) {
        logger.info("The method add was invoke with args [" + i + ", " + j + "]");
        int sum = i + j;
        logger.info("The method add ends with result [" + sum + "]");
        return sum;
    }

    @Override
    public int div(int i, int j) {
        logger.info("The method div was invoke with args [" + i + ", " + j + "]");
        int result = i / j;
        logger.info("The method div ends with result [" + result + "]");
        return result;
    }

    @Override
    public String validateNum(String level, int i) {
        logger.info("The method validateNum was invoke with args [" + level + ", " + i + "]");
        String result = this.getMsg(i);
        logger.info("The method validateNum ends with result [" + result + "]");
        return result;
    }

    private String getMsg (int i) {
        if (i > 0) {
            return "正数";
        }
        return "负数";
    }
}

切面POJO

package com.invicme.apps.aop.schema;

import java.util.Arrays;
import java.util.List;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

/**
 * 
 * @author lucl
 * 
 */
public class LogAdapter {
    private static final Logger logger = Logger.getLogger(LogAdapter.class);
     
    /**
     * Pointcut
     * 定义Pointcut,Pointcut的名称为poincutMethod(),此方法没有返回值和参数
     * 该方法就是一个标识,不进行调用
     */
    public void poincutMethod () {
        // ......
    }
    
    /**
     * 前置通知,在目标方法执行之前被调用(JoinPoint参数不是必须的,传入是为了获取目标对象的相关属性)
     */
    public void beforeAdvice (JoinPoint joinPoint) {
        // 目标对象
        Object target = joinPoint.getTarget();
        // 目标方法
        String methodName = joinPoint.getSignature().getName();
        // 方法参数
        List<Object> asList = Arrays.asList(joinPoint.getArgs());
        /**
         * target.getClass().getName() : 获取的是全路径名(包名+类名)
         * target.getClass().getSimpleName() : 获取类名
         */
        logger.info("[<aop:before>]" + target.getClass().getSimpleName() + "@" + methodName + " was invoke with args " + asList + ".");
    }
    
    /**
     * 后置通知(在目标方法执行之后被执行,无论该方法是否抛出异常)
     */
    public void afterAdvice (JoinPoint joinPoint) {
        // 目标对象
        Object target = joinPoint.getTarget();
        // 目标方法
        String methodName = joinPoint.getSignature().getName();
        logger.info("[<aop:after>]" + target.getClass().getSimpleName() + "@" + methodName + " ends.");
    }
    
    /**
     * 返回通知(在方法正常执行后执行,若出现异常不会被执行)
     * 返回通知可以获取到目标方法的返回值
     */
    public void afterReturningAdvice (JoinPoint joinPoint, Object result) {
        // 目标对象
        Object target = joinPoint.getTarget();
        // 目标方法
        String methodName = joinPoint.getSignature().getName();
        logger.info("[<aop:after-returning>]" + target.getClass().getSimpleName() + "@" + methodName + " ends with result " + result + ".");
    }
    
    /**
     * 异常通知(当目标方法出现异常时会被执行,可以访问到异常,也可以通过指定异常类型,
     * 如Exception ex,也可以为NullPointerException ex则只有空指针异常才会被执行)
     */
    public void afterThrowingAdvice (JoinPoint joinPoint, Exception ex) {
        // 目标对象
        Object target = joinPoint.getTarget();
        // 目标方法
        String methodName = joinPoint.getSignature().getName();
        logger.info("[<aop:after-throwing>]" + target.getClass().getSimpleName() + "@" + methodName + " occurs exception : " + ex + ".");
    }
    
    /**
     * 环绕通知
     * 说明:
     *         环绕通知需要携带ProceedingJoinPoint类型的参数
     *         环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型参数可以决定是否执行目标方法
     *         环绕通知必须有返回值,且返回值为目标方法的返回值
     */
    public Object aroundAdvice (ProceedingJoinPoint joinPoint) {
        // 目标对象
        Object target = joinPoint.getTarget();
        // 目标方法
        String methodName = joinPoint.getSignature().getName();
        // 参数
        List<Object> asList = Arrays.asList(joinPoint.getArgs());
        // 
        Object result = null;
        // 执行目标方法
        try {
            // 前置通知
            result = joinPoint.proceed();
            // 返回通知
        } catch (Throwable e) {
            throw new RuntimeException(e);
            // 异常通知
            // 或者
            // 直接throw出去(否则程序会执行到最后的return result,而result为null,目标方法可能需要类型转换,当试图将null转化为特定类型时,出错)
        }
        // 后置通知
        logger.info("[<aop:around>]" + target.getClass().getSimpleName() + "@" + methodName + " was invoke with args " + asList + ", ends with " + result + " .");
        // 注意这里的result实际上是目标方法的返回值,如果出现问题返回值不匹配会出现错误
        return result;
    }
}

Spring配置文件spring-context-aop-schema.xml

<?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"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        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">
    <!-- 业务类 -->
    <bean id="calculate" class="com.invicme.apps.aop.schema.ArithmeticCalculateImpl" />
    
    <!-- 切入的日志类 -->
    <bean id="logAdapter" class="com.invicme.apps.aop.schema.LogAdapter" />
    
    <!-- 
        proxy-target-class 
            true : 表示声明的切面使用CGLib动态代理技术;
            false : 默认值,使用JDK动态代理技术。
        一个配置文件可以有多个<aop:config>,不同的<aop:config>采用不同的代理技术。
    -->
    <aop:config proxy-target-class="false">
        <!-- 声明一个切点 -->
        <aop:pointcut id="logPointcut" expression="execution(public * com.invicme.apps.aop.schema.ArithmeticCalculateImpl.*(..))"/>
        <!-- 定义切面 -->
        <aop:aspect id="logAspect" ref="logAdapter">
            <!-- 在<aop:aspect>中也可以定义<aop:pointcut>,但建议在外层定义,在通知处引用 -->
            <!-- 
                <aop:before>
                    pointcut和pointcut-ref:二者选一,指定切入点;
                    method:指定前置通知实现方法名,如果是多态需要加上参数类型,多个用“,”隔开,如beforeAdvice(java.lang.String);
                    arg-names:指定通知实现方法的参数名字,多个用“,”分隔,可选;如arg-names="param"表示beforeAdvice(String param)。
            -->
            <aop:before method="beforeAdvice" pointcut-ref="logPointcut"/>
            <aop:after method="afterAdvice" pointcut-ref="logPointcut"/>
            <aop:after-returning method="afterReturningAdvice" pointcut-ref="logPointcut" returning="result"/>
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="logPointcut" throwing="ex"/>
            <aop:around method="aroundAdvice" pointcut-ref="logPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

测试类

package com.test.apps.spring.aop;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.invicme.apps.aop.ArithmeticCalculate;

/**
 * 
 * @author lucl
 *
 */
public class TestSpringAopBySchema {
    @Test
    public void testSpringAopBySchema() {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-context-aop-schema.xml");
        ArithmeticCalculate calculate = context.getBean("calculate", ArithmeticCalculate.class);
        calculate.add(1, 2);
        System.out.println("----------------------------------------------------------------");
        try {
            Thread.sleep(1 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        calculate.div(1, 0);
    }
}

运行结果:

Spring学习笔记AOP(三)_aop