Spring基本用法7——AOP的支持(二)

       前言:AOP,也就是面向切面编程,作为面向切面编程的一种补充,已经比较成熟,如果是OOP是从静态角度考虑程序结构,那么AOP则是从动态角度考虑程序运行。本文旨在介绍Spring对AOP的支持,简述其用法。

本篇文章重点关注以下问题:

  • SpringAOP访问目标方法的参数
  • 增强处理的执行顺序

1. SpringAOP访问目标方法的参数

         访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。JoinPoint里包含了如下几个常用的方法:

  • Object[] getArgs:返回目标方法的参数
  • Signature getSignature:返回目标方法的签名
  • Object getTarget:返回被织入增强处理的目标对象
  • Object getThis:返回AOP框架为目标对象生成的代理对象
提示:当使用@Around处理时,我们需要将第一个参数定义为ProceedingJoinPoint类型,该类是JoinPoint的子类。

2. 输出DEMO

          测试项目目录如下图:

Spring基本用法7——AOP的支持(二)

2.1 准备XML配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-4.3.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    
    <!-- 指定自动搜索Bean组件、自动搜索切面类 -->
    <context:component-scan base-package="com.wj.chapter6.aop2">
        <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/>
    </context:component-scan>
    
    <!-- 启动@AspectJ支持 -->
    <aop:aspectj-autoproxy/>
    
</beans>

       说明:

  • <aop:aspectj-autoproxy/>用来启动Spring对@AspectJ的支持
  • 有关<context:component-scan>使用说明可参考http://super-wangj.iteye.com/blog/2387655

2.2 编写切面类       

       下面的切面类中定义了Before、Around、AfterReturning和After 4中增强处理,并分别在4种增强处理中访问被织入增强处理的目标方法、目标方法的参数和被织入增强处理的目标对象等:

package com.wj.chapter6.aop2.aspect;

import java.util.Arrays;

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.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * 定义一个切面,用于测试对目标方法参数的访问
 */
@Aspect
public class FourAdviceTest {
    
    @Around("execution(* com.wj.chapter6.aop2.service.*.*(..))")
    public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable {
        System.out.println("【Around增强】执行目标方法之前,模拟开始事务...");
        // 访问执行目标方法的参数
        Object[] args = jp.getArgs();
        // 当执行目标方法的参数存在,
        // 且第一个参数是字符串参数
        if (args != null && args.length > 0
            && args[0] instanceof String) {
            // 修改目标方法调用参数的第一个参数
            args[0] = "【增加的前缀】" + args[0];
        }
        //执行目标方法,并保存目标方法执行后的返回值
        Object rvt = jp.proceed(args);
        System.out.println("【Around增强】执行目标方法之后,模拟结束事务...");
        // 如果rvt的类型是Integer,将rvt改为它的平方
        if(rvt != null && rvt instanceof Integer)
            rvt = (Integer)rvt * (Integer)rvt;
        return rvt;
    }
    
    @Before("execution(* com.wj.chapter6.aop2.service.*.*(..))")
    public void authority(JoinPoint jp) {
        System.out.println("【Before增强】模拟执行权限检查");
        // 返回被织入增强处理的目标方法
        System.out.println("【Before增强】被织入增强处理的目标方法为:" + jp.getSignature().getName());
        // 访问执行目标方法的参数
        System.out.println("【Before增强】目标方法的参数为:" + Arrays.toString(jp.getArgs()));
        // 访问被增强处理的目标对象
        System.out.println("【Before增强】被织入增强处理的目标对象为:" + jp.getTarget());
    }
    
    @AfterReturning(pointcut="execution(* com.wj.chapter6.aop2.service.*.*(..))", returning="rvt")
    public void log(JoinPoint jp , Object rvt) {
        System.out.println("【AfterReturning增强】获取目标方法返回值:" + rvt);
        System.out.println("【AfterReturning增强】模拟记录日志功能...");
        // 返回被织入增强处理的目标方法
        System.out.println("【AfterReturning增强】被织入增强处理的目标方法为:" + jp.getSignature().getName());
        // 访问执行目标方法的参数
        System.out.println("【AfterReturning增强】目标方法的参数为:" + Arrays.toString(jp.getArgs()));
        // 访问被增强处理的目标对象
        System.out.println("【AfterReturning增强】被织入增强处理的目标对象为:" + jp.getTarget());
    }

    // 定义After增强处理
    @After("execution(* com.wj.chapter6.aop2.service.*.*(..))")
    public void release(JoinPoint jp) {
        System.out.println("【After增强】模拟方法结束后的释放资源...");
        // 返回被织入增强处理的目标方法
        System.out.println("【After增强】被织入增强处理的目标方法为:" + jp.getSignature().getName());
        // 访问执行目标方法的参数
        System.out.println("【After增强】目标方法的参数为:" + Arrays.toString(jp.getArgs()));
        // 访问被增强处理的目标对象
        System.out.println("【After增强】被织入增强处理的目标对象为:" + jp.getTarget());
    }
}

         从上面的代码可以看出,在Before、Around、AfterReturning、After四种增强处理中,其实都可以通过相同的代码访问被增强的对象、目标方法和方法的参数。但只有Around增强处理可以改变方法的参数和返回值。

2.3 编写业务类代码

       接口+实现,业务相关接口就不上代码了,可以通过附件查看,下面是业务实现类:

package com.wj.chapter6.aop2.service;

import org.springframework.stereotype.Component;

@Component("addService")
public class AddService implements IAddService {
    
    @Override
    public boolean addUser(String name , String pass){
        System.out.println("【AddService.addUser】添加用户:" + name);
        return true;
    }
    
    @Override
    public void addProduct(String name) {
        System.out.println("【AddService.addProduct】添加商品:" + name);
    }

    @Override
    public void addException() {
        throw new NullPointerException();
    }
}

 2.4 编写测试代码、查看运行结果

package com.wj.chapter6.aop2;

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

import com.wj.chapter6.aop2.service.IAddService;

public class BeanTest {
    // 1.指明xml配置文件位置,便于Spring读取,从而知道Bean的相关信息
    private static final String PATH_XML = "com/wj/chapter6/aop2/applicationContext.xml";
    
    @SuppressWarnings("resource")
    public static void main(String[] args) {
        // 2.根据xml配置文件,创建Spring IOC容器的上下文
        ApplicationContext ctx = new ClassPathXmlApplicationContext(PATH_XML);
        
        IAddService addService = ctx.getBean("addService" , IAddService.class);
        addService.addUser("熊燕子", "123456");
    }
}

         测试结果为:

Spring基本用法7——AOP的支持(二)

3. 访问目标方法参数的简单方式

        我们可以在程序中使用args来绑定目标方法的参数。如果在一个 args表达式中指定了一个或多个参数,该切入点将只匹配具有对应形参的方法,且目标方法的参数值将被传入增强处理方法。下面辅以例子说明:

package com.wj.chapter6.aop3.aspect;

import java.util.Date;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AccessArgAspect {
    // 下面的args(arg0,arg1)会限制目标方法必须有2个形参
    @AfterReturning(returning="rvt" , pointcut="execution(* com.wj.chapter6.aop3.service.*.*(..)) && args(date,name,pass)")
    // 此处指定date为Date类型,name、pass为String类型
    // 则args(date,name,pass)还要求目标方法的第一个参数为Date类型,后两个形参都是String类型
    public void access(Object rvt, Date date, String name , String pass) {
        System.out.println("调用目标方法第1个参数为:" + date);
        System.out.println("调用目标方法第2个参数为:" + name);
        System.out.println("调用目标方法第3个参数为:" + pass);
        System.out.println("获取目标方法返回值:" + rvt);
        System.out.println("模拟记录日志功能...");
    }
}

       上面的程序中,定义pointcut时,表达式中增加了args(date,name,pass)部分,意味着可以在增强处理方法(access方法)中定义date,name,pass三个属性——这三个形参的类型可以随意指定,但一旦指定了这三个参数的类型,则这三个形参类型将用于限制该切入点只匹配第一个参数类型为Date,第二、三个参数类型为String的方法(方法参数个数和类型若有不同均不匹配)。

       args表达式有如下两个作用:

  • 提供了一种简单的方式来访问目标方法的参数

  • 可用于对切入点表达式作额外的限制

      测试结果为:

相关推荐