欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring 04 AOP

程序员文章站 2022-06-05 13:46:27
...

1、代理设计模式

概念

GoF95 一共定义了23种设计模式,代理设计模式是非常重要的模式之一

作用:对目标对象访问进行控制,在访问目标对象的前后进行功能的扩展

可以进行功能扩展的技术:
过滤器,拦截器,代理 等

静态代理

只能代理一种类型的对象,代理类需要自定义开发

动态代理

可以代理所有的类型对象,代理类由代理工具类动态生成

JDK动态代理

 ⑴ 基于接口进行代理
 ⑵ 目标对象必须实现相关的接口,才能使用这种代理方式
 ⑶ 代理类和目标类实现了相同的接口
 ⑷ 使用InvocationHandler【调用处理器】

Cglib/Javassist

  ⑴ 基于继承方式进行代理
  ⑵ 代理类是目标类的子类

代理流程

使用代理前:
A(客户端) → D(目标程序【业务代码 + 非业务代码(日志、事务、校验、权限 等)】)

使用代理后:
A(客户端) → B(代理对象) → C(调用处理器【非业务代码(日志、事务、权限、校验 等)】) → D(目标程序【业务代码】)

代理可以解决的问题

日志打印,事务处理,权限控制,数据校验 等

2、代理设计模式案例

【目标接口】

public interface Calc {

    int add(int a, int b);

    int sub(int a, int b);

    int mul(int a, int b);

    int div(int a, int b);

}

【目标类】

public class SimpleCalc implements Calc {

    @Override
    public int add(int a, int b) {
        int result = a + b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    @Override
    public int sub(int a, int b) {
        int result = a - b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    @Override
    public int mul(int a, int b) {
        int result = a * b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    @Override
    public int div(int a, int b) {
        int result = a / b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

}

【生成代理对象的类】

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class CalcProxy {
    // 目标对象
    private Object targetObj;

    public CalcProxy(Object targetObj) {
        this.targetObj = targetObj;
    }

    /**
     * 生成代理对象
     * 
     * @return 代理对象
     */
    public Object getProxyObject() {
        /**
         * ClassLoader loader 类加载器。用于加载代理类到JVM中运行。一般传递目标对象的类加载器
         * 
         * Class<?>[] interfaces 接口类型数组。JDK动态代理,是基于接口的。
         *                      目标类和代理类实现共同的接口,需要通过目标对象来获取接口类型
         * 
         * InvocationHandler h 调用处理器。代理类是在内存中动态生成的。无法将扩展代码写在代理类中。
         *                      所以需要通过InvocationHandler接口,来编写实现类,完成功能的扩展
         */
        return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(), targetObj.getClass()
                .getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object returnVal = null;

                try {
                    System.out.println("【" + method.getName() + "方法开始】【参数:"
                            + (args == null ? "" : Arrays.asList(args)) + "】");

                    returnVal = method.invoke(targetObj, args);

                    System.out.println("【" + method.getName() + "方法返回】【返回值:】" + returnVal);
                } catch (Exception e) {
                    e.printStackTrace();

                    System.out.println("【" + method.getName() + "方法出异常了】【异常信息:"
                            + e.getCause().getMessage() + "】");
                } finally {
                    System.out.println("【" + method.getName() + "方法结束】");
                }

                return returnVal;
            }
        });
    }

}

【测试类】

public class TestCalc {

    @Test
    public void testSimpleCalc() {
        Calc calc = new SimpleCalc();
        calc.add(10, 5);
        calc.sub(10, 5);
        calc.mul(10, 5);
        calc.div(10, 5);
    }

    @Test
    public void testLogCalc() {
        Calc calc = new LogCalc();
        calc.add(10, 5);
        System.out.println("-----------------------------");
        calc.sub(10, 5);
        System.out.println("-----------------------------");
        calc.mul(10, 5);
        System.out.println("-----------------------------");
        calc.div(10, 5);
    }

    @Test
    // 测试代理
    public void testCalcProxy() {
        Calc calc = new SimpleCalc();
        Calc calcProxy = (Calc) new CalcProxy(calc).getProxyObject();
        // class com.sun.proxy.$Proxy2
        System.out.println(calcProxy.getClass());

        calcProxy.add(10, 5);
        System.out.println("-----------------------------");
        calcProxy.sub(10, 5);
        System.out.println("-----------------------------");
        calcProxy.mul(10, 5);
        System.out.println("-----------------------------");
        calcProxy.div(10, 5);
    }

    @Test
    // 测试异常
    public void testException() {
        Calc calc = new SimpleCalc();
        Calc calcProxy = (Calc) new CalcProxy(calc).getProxyObject();
        calcProxy.div(10, 0);
    }

}

3、AOP概述

概念

Aspect-Oriented Programming 面向切面编程

横切关注点

表示解决程序中什么样的问题

例如:解决日志打印,数据校验,权限控制,事务处理 等

切面类

将横切关注点代码模块化,形成一个一个独立的组件程序,称为切面类
切面类中定义多个通知方法,通知方法中编写的就是非业务代码(包括日志打印,事务处理,权限控制,数据校验 等)

通知

将横切关注点的代码封装到一个一个的方法中,在执行目标过程中,进行动态的执行

通知的5种方式

⑴ 前置通知(@Before)
在目标方法执行前,执行扩展

⑵ 方法返回通知(@AfterReturning)
在目标方法执行后,返回结果时,执行扩展

⑶ 异常通知(@AfterThrowing)
在目标方法抛出异常时,执行扩展

⑷ 后置通知【最终通知】(@After)
目标方法不管是否存在异常,都必须执行扩展。即一定会被执行的扩展

⑸ 环绕通知(@Around)
相当于以上4种通知的各种组合,功能更强大。但使用较少

连接点

通知方法需要执行的位置

在Spring的AOP中,连接点就是目标对象的方法

切入点

使用切入点表达式,来查找连接点,被查找到的连接点,统一称为切入点
切入点就是连接点的集合

代理对象

切面类的通知(方法)是被代理对象调用执行的

Spring采用两种代理方式
⑴ 默认采用JDK动态代理,只要目标对象有接口,就采用JDK动态代理
⑵ 目标对象没有接口,就采用Cglib动态代理

目标对象

该对象用于完成具体业务逻辑代码

4、AOP的具体实现

步骤

⑴ 导入jar包
除了:

 commons-logging-1.1.3.jar
 spring-beans-4.0.0.RELEASE.jar
 spring-context-4.0.0.RELEASE.jar
 spring-core-4.0.0.RELEASE.jar
 spring-expression-4.0.0.RELEASE.jar

必须的jar包

spring-aop-4.0.0.RELEASE.jar

用注解方式声明bean

还需:

spring-aspects-4.0.0.RELEASE.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

⑵ 在核心配置文件中配置
① 先增加context和aop名称空间

xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"

会在xsi:schemaLocation 中增加:

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"

② 配置

    <!-- 设置自动扫描包 -->
<context:component-scan base-package="要扫描的包及其子包" />
<!-- 启用AOP功能:扫描AOP注解 -->
<aop:aspectj-autoproxy />

⑶ 定义目标类

 @Component // 声明组件对象
 public class ?? {
 }

⑷ 定义切面类

 @Aspect // 声明这是一个切面类
 @Component // 声明对象
 public class ?? {
 }

⑸ 在切面类中定义通知(方法)

 // 该方法是一个前置通知
 @Before(value = "execution(切入点表达式)")
 public void ?? {
 }

 // 该方法是一个方法返回通知
 @AfterReturning(value = "execution(切入点表达式)")
 public void ?? {
 }

 // 该方法是一个异常通知
 @AfterThrowing(value = "execution(切入点表达式)")
 public void ?? {
 }

 // 该方法是一个后置通知
 @After(value = "execution(切入点表达式)")
 public void ?? {
 }

示例(测试JDK动态代理)

【核心配置文件】

<context:component-scan base-package="???.???.???" />
<aop:aspectj-autoproxy />

【目标接口】

public interface Calc {

    int add(int a, int b);

    int sub(int a, int b);

    int mul(int a, int b);

    int div(int a, int b);

}

【目标类】

@Component
public class SimpleCalc implements Calc {

    @Override
    public int add(int a, int b) {
        int result = a + b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    @Override
    public int sub(int a, int b) {
        int result = a - b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    @Override
    public int mul(int a, int b) {
        int result = a * b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    @Override
    public int div(int a, int b) {
        int result = a / b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

}

【切面类】

@Aspect
@Component
public class LogAspect {

    @Before(value = "execution(public int *..SimpleCalc.add(int, int))")
    public void beforeLog() {
        System.out.println("【add方法开始执行】");
    }

}

【测试类】

private ApplicationContext ioc = new ClassPathXmlApplicationContext("???.xml");

@Test
// JDK动态代理(有接口)
public void testJDK() {
    Calc calc = ioc.getBean(Calc.class);
    // class com.sun.proxy.$Proxy8
 // 包名[email protected] 【目标类有接口,采用JDK动态代理,代理类是目标类的兄弟类】
    System.out.println(calc.getClass());

    calc.add(10, 5);
    System.out.println("-------------------");
    calc.sub(10, 5);
    System.out.println("-------------------");
    calc.mul(10, 5);
    System.out.println("-------------------");
    calc.div(10, 5);
}

测试Cglib动态代理

【添加目标类】

@Component
public class SimpleCalc2 {

    public int add(int a, int b) {
        int result = a + b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    public int sub(int a, int b) {
        int result = a - b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    public int mul(int a, int b) {
        int result = a * b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    public int div(int a, int b) {
        int result = a / b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

}

【添加切面类】

@Aspect
@Component
public class LogAspect2 {

    @Before(value = "execution(public int *..SimpleCalc2.add(int, int))")
    public void beforeLog() {
        System.out.println("【add方法开始执行】");
    }

}

【添加测试】

@Test
// Cglib动态代理(没有接口)
public void testCglib() {
    SimpleCalc2 simpleCalc2 = (SimpleCalc2) ioc.getBean("simpleCalc2");
    // class com.test.spring.calc.impl.SimpleCalc2$$EnhancerByCGLIB$$8008b12c 【目标对象没有接口,采用Cglib动态代理,代理类是目标类的子类】
    System.out.println(simpleCalc2.getClass());

    simpleCalc2.add(10, 5);
    System.out.println("-------------------");
    simpleCalc2.sub(10, 5);
    System.out.println("-------------------");
    simpleCalc2.mul(10, 5);
    System.out.println("-------------------");
    simpleCalc2.div(10, 5);
}

5、切入点表达式

作用

用于查找连接点

语法

execution([修饰符][返回值类型][全类名][方法名][形参类型列表])

execution(方法签名)

在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来
语法:
execution(方法签名) && execution(方法签名)
execution(方法签名) || execution(方法签名)
!execution(方法签名)

Tips:
⑴ [返回值类型]可以用* 来匹配
⑵ [修饰符][返回值类型]可以用* 来匹配
⑶ [全类名]可以用* 来匹配
⑷ [全类名]可以用 ??.??..??来匹配
⑸ [全类名][方法名]可以用. 来匹配
⑹ [方法名]可以用??* 来匹配
⑺ [形参类型]可以用.. 来匹配
⑻ [形参类型]可以用??, ..来匹配
⑼ 最模糊查询 execution(* .(..))

示例

【目标类】
com.test.spring.MyClass
有一个方法:
public String method(String a, int b) {}

package com.test.spring;

import org.springframework.stereotype.Component;

@Component
public class MyClass {

    public String method(String a, int b) {
        return "";
    }

}

【切面类】
com.test.spring.MyAspect
有一个方法:
public void test() {}

package com.test.spring;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    @Before(value = "execution(方法签名)")
    public void test() {
    }

}

【切入点表达式】
⑴ 最精确匹配

execution(public String com.test.spring.MyClass.method(String, int))

⑵ 模糊修饰符

execution(* com.test.spring.MyClass.method(String, int))

⑶ 模糊包

execution(public String com..MyClass.method(String, int))

⑷ 模糊全类名和方法名

execution(public String *.*(String, int))

⑸ 模糊参数列表

execution(public String com.test.spring.MyClass.method(..))

⑹ 最模糊查询,匹配所有方法【通用】

execution(* *.*(..))

6、通知

JoinPoint

JoinPoint放在通知(方法)的形参位置,以便调用
一般用来获取方法和参数列表

    // 获取方法签名
    Signature signature = joinPoint.getSignature();
    // 获取方法名
    String methodName = signature.getName();
    // 获取参数列表
    Object[] args = joinPoint.getArgs();

Tips:【将参数列表转换为String类型】
Arrays.asList(args).toString();
使用Arrays工具类,将Object[] 数组转换为ArrayList集合,再调用ArrayList集合的重写过的toString 方法,即可

注意:在转换将数组转集合前,需要判断数组是否为空,可以使用三元运算符

示例:
(null == args)? “” : Arrays.asList(args).toString()

ProceedingJoinPoint

ProceedingJoinPoint是JoinPoint的子接口
Spring 04 AOP

所以它除了可以获取方法名,参数列表外,还可以
执行目标方法:
public Object proceed() throws Throwable;
该方法有Object类型的返回值,即目标方法的返回值

前置通知

@Before(value = "execution(* *.*(..))")
public void beforeLog(JoinPoint joinPoint) throws Throwable {
    // 获取方法签名
    Signature signature = joinPoint.getSignature();
    // 获取方法名
    String methodName = signature.getName();
    // 获取参数列表
    Object[] args = joinPoint.getArgs();

    System.out.println(methodName + "方法开始,参数:" + (null == args ? "" : Arrays.asList(args)));
}

方法返回通知

可以在通知的形参位置,添加一个Object类型的参数,用于接收目标方法的返回值
同时需要给@AfterReturning注解,添加一个returning的属性,这个String类型的值就是定义的参数的名字

注意:一定要让returning的value值和通知的Object参数的名相同,否则报错:
java.lang.IllegalStateException: Returning argument name ‘???’ was not bound in advice arguments

@AfterReturning(value = "execution(* *.*(..))", returning = "result")
public void returnLog(JoinPoint joinPoint, Object result) {
    String methodName = joinPoint.getSignature().getName();

    System.out.println(methodName + "方法返回,返回值:" + result);
}

异常通知

可以在通知的形参位置,添加一个Throwable或Exception类型的参数,用于接收目标方法的返回值
同时需要给@AfterThrowing注解,添加一个throwing的属性,这个String类型的值就是定义的参数的名字

注意:一定要让throwing的value值和通知的Throwable或Exception参数的名相同,否则报错:
Caused by: java.lang.IllegalStateException: Throwing argument name ‘???’ was not bound in advice arguments

Tips:可以通过具体的Exception,来指定出现特定的异常时,执行通知(方法)

@AfterThrowing(value = "execution(* *.*(..))", throwing = "e")
public void throwLog(JoinPoint joinPoint, Throwable e) {
    String methodName = joinPoint.getSignature().getName();

    System.out.println(methodName + "方法出异常了,异常信息:" + e.getMessage());
}

可以获取任意类型的异常对象

@AfterThrowing(value = "execution(* *.*(..))", throwing = "e")
public void throwLog(JoinPoint joinPoint, NullPointerException e) {
    String methodName = joinPoint.getSignature().getName();

    System.out.println(methodName + "方法出异常了,异常信息:" + e.getMessage());
}

只能获取空指针异常对象

后置通知

@After(value = "execution(* *.*(..))")
public void afterLog(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();

    System.out.println(methodName + "方法执行完毕");
}

环绕通知

注意:
⑴ 环绕通知(方法)的返回值为Object类型,用于返回目标方法的返回值
⑵ 形参须为ProceedingJoinPoint,以便调用目标方法joinPoint.proceed(); 如果执行目标方法,则无法获取目标方法的返回值【null】
⑶ 在catch中,一定要将异常对象抛出,否则其他切面将无法获取异常

@Around(value = "execution(* *.*(..))")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
    // 目标方法返回值
    Object returnVal = null;
    String methodName = joinPoint.getSignature().getName();
    Object[] args = joinPoint.getArgs();

    try {
        System.out.println("[" + methodName + "方法开始][参数:"
                + (null == args ? "" : Arrays.asList(args)) + "]");

        // 执行目标方法,并接收返回值
        returnVal = joinPoint.proceed();

        System.out.println("[" + methodName + "方法返回][返回值:" + returnVal + "]");
    } catch (Exception e) {
        System.out.println("[" + methodName + "方法出异常了][异常信息:" + e.getMessage() + "]");
        // 如果是JUnit测试,则打印堆栈信息
        // e.printStackTrace();

        // 一定要抛出异常,否则等同于在这里处理了
        throw e;
    } finally {
        System.out.println("[" + methodName + "方法执行完毕]");
    }

    return returnVal;
}

7、重用切入点表达式

概念

当一些通知(方法)的切入点相同时,可以定义一个公共的切入点表达式,以便让这些通知(方法)来调用

需要使用@Pointcut注解,其value属性值就是切入点表达式

步骤

⑴ 在切面类中定义一个无返回值,无参数,无方法体的方法
⑵ 在本类通知中,通过方法名() 引用即可
⑶ 在其他类的通知中,通过全类名.方法名() 引用即可

注意:该方法的访问修饰符,决定了其能否被其他的切面类引用

示例

【切面类 一】

package com.test.spring;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    @Pointcut(value = "execution(* com.test.spring.目标类.*(..))")
    public void myPointcut() {}

    @Before(value = "myPointcut()")
    public void before() {
        System.out.println("==========MyAspect==========");
    }

}

【切面类 二】

package com.test.spring;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect2 {

    @Before(value = "com.test.spring.MyAspect.myPointcut()")
    public void before() {
        System.out.println("----------MyAspect2----------");
    }

}

8、设置切面类的优先级

实现方法

当存在多个切面类,以及相同的多个通知(前置或后置等),可以给切面类设置优先级,来控制通知的执行顺序

通过给切面类添加@Order 注解来实现,其value属性值,是int类型,值越小,优先级越高。可以是负数

示例

【目标类】

package com.test.spring.target;

import org.springframework.stereotype.Component;

@Component
public class TargetObject {

    public String targetMethod(Integer i, String str) {
        System.out.println("方法内部执行");
        return str;
    }

}

【切面类 一】

package com.test.spring.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
// 设置优先级
@Order(value = 2)
@Component
public class LogAspect {

    @Pointcut(value = "execution(* com.test.spring.target.TargetObject.targetMethod(Integer, String))")
    public void myPointcut() {
    }

    @Before(value = "myPointcut()")
    public void beforeLog() {
        System.out.println("[LogAspect]【目标方法执行前】");
    }

    @After(value = "myPointcut()")
    public void afterLog() {
        System.out.println("[LogAspect]【目标方法执行完毕】");
    }

}

【切面类 二】

package com.test.spring.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
// 设置优先级
@Order(value = 1)
@Component
public class LogAspect2 {

    @Before(value = "com.test.spring.aspect.LogAspect.myPointcut()")
    public void beforeLog() {
        System.out.println("----------[LogAspect2]目标方法执行前----------");
    }

    @After(value = "com.test.spring.aspect.LogAspect.myPointcut()")
    public void afterLog() {
        System.out.println("----------[LogAspect2]目标方法执行完毕----------");
    }

}

【测试类】

@Test
public void test() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("???.xml");
    TargetObject to = ioc.getBean(TargetObject.class);
    to.targetMethod(1, "字符串");
}

【执行结果】

----------[LogAspect2]目标方法执行前----------
[LogAspect]【目标方法执行前】
方法内部执行
[LogAspect]【目标方法执行完毕】
----------[LogAspect2]目标方法执行完毕----------

9、基于XML的AOP配置

相关的8个标签

 <aop:config><aop:config>

用于声明切面类对象和目标类对象的关系。在其里面声明切面类及其通知

 <aop:aspect id="切面类对象的id" ref="引用的切面类的id" order="切面类的通知执行的优先级别"></aop:aspect>

声明切面对象

 <aop:pointcut expression="execution(方法签名)" id="切入点表达式的id" />

用于声明切入点表达式。提供id,以便其他切面类来引用

 <aop:before method="对应切面类的通知(方法)" pointcut-ref="所引用的切入点表达式的id" />

用于声明前置通知

 <aop:after-returning method="对应切面类的通知(方法)" returning="通知(方法)里的Object的参数的名字【用于接收目标方法的返回值】" pointcut-ref="所引用的切入点表达式的id" />

用于声明方法返回通知

 <aop:after-throwing method="对应切面类的通知(方法)" throwing="通知(方法)里的Throwable类型或Exception类型及其子类型的参数的名字【用于接收目标方法的异常对象】" pointcut-ref="所引用的切入点表达式的id" />

用于声明异常通知

 <aop:after method="对应切面类的通知(方法)" pointcut-ref="所引用的切入点表达式的id" />

用于声明后置通知

 <aop:around method="对应切面类的通知(方法)" pointcut-ref="所引用的切入点表达式的id" />

用于声明环绕通知

具体步骤

⑴ 通过bean标签,声明目标类和切面类
⑵ 通过aop:config 标签,来声明切面类对象和目标类对象之间的关系
⑶ 通过aop:aspect 标签,来声明切面类
⑷ 通过aop:pointcut 标签,来声明切入点表达式
⑸ 通过aop:before、app:after 等标签,来声明对应的通知(方法)。如果通知(方法)有接收目标对象的返回值或异常对象,则还需配置对应的属性【returning或throwing】

使用示例

【核心配置文件】

<?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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <!-- 声明目标类对象 -->
    <bean id="targetObject" class="com.test.spring.target.TargetObjectImpl" />

    <!-- 声明切面类对象 -->
    <bean id="logAspect" class="com.test.spring.aspect.LogAspect" />
    <bean id="logAspect2" class="com.test.spring.aspect.LogAspect2" />

    <!-- 声明切面类对象和目标类对象之间的关系 -->
    <aop:config>
        <!-- 声明全局的切入点表达式,可以被任何的切面类使用 -->
        <aop:pointcut expression="execution(* *.*(..))" id="pointcutId" />

        <!-- 声明切面类 -->
        <!-- 切面类的优先级别为2 -->
        <aop:aspect id="logAspectId" ref="logAspect" order="2">
            <!-- 声明局部的切入点表达式,只能被当前的切面类使用 -->
            <aop:pointcut expression="execution(* com.test.spring.target.TargetObjectImpl.*(..))" id="logAspectPointcutId" />

            <!-- 声明前置通知 -->
            <aop:before method="beforeLog" pointcut-ref="logAspectPointcutId" />
            <!-- 声明方法返回通知 -->
            <aop:after-returning method="returningLog" returning="result" pointcut-ref="logAspectPointcutId" />
            <!-- 声明异常通知 -->
            <aop:after-throwing method="throwingLog" throwing="e" pointcut-ref="logAspectPointcutId" />
            <!-- 声明后置通知 -->
            <aop:after method="afterLog" pointcut-ref="logAspectPointcutId" />

            <!-- 声明环绕通知 -->
            <aop:around method="aroundLog" pointcut-ref="logAspectPointcutId" />
        </aop:aspect>

        <!-- 切面类的优先级别为1 -->
        <aop:aspect id="logAspectId2" ref="logAspect2" order="1">
            <aop:pointcut expression="execution(* com.test.spring.target.TargetObjectImpl.*(..))" id="logAspect2PointcutId" />

            <aop:before method="beforeLog" pointcut-ref="logAspect2PointcutId" />
            <aop:after method="afterLog" pointcut-ref="logAspect2PointcutId" />
        </aop:aspect>
    </aop:config>

</beans>

【目标接口】

package com.test.spring.target;

public interface TargetObject {

    int add(int a, int b);

    int div(int a, int b);

}

【目标类】

package com.test.spring.target;

public class TargetObjectImpl implements TargetObject {

    @Override
    public int add(int a, int b) {
        int result = a + b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

    @Override
    public int div(int a, int b) {
        int result = a / b;
        System.out.println("方法内部打印:" + result);
        return result;
    }

}

【切面类 一】

package com.test.spring.aspect;

import java.util.Arrays;

import org.aspectj.lang.ProceedingJoinPoint;

public class LogAspect {

    public void beforeLog() {
        System.out.println("【方法执行前】");
    }

    public void returningLog(Object result) {
        System.out.println("【方法返回】【返回值:" + result + "】");
    }

    public void throwingLog(Throwable e) {
        System.out.println("【方法出异常了】【异常信息:" + e.getMessage() + "】");
    }

    public void afterLog() {
        System.out.println("【方法结束了】");
    }

    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Object returnVal = null;

        try {
            System.out.println("[" + methodName + "方法开始][参数:"
                    + (null == args ? "" : Arrays.asList(args)) + "]");

            returnVal = joinPoint.proceed();

            System.out.println("[" + methodName + "方法返回][返回值:" + returnVal + "]");
        } catch (Exception e) {
            e.printStackTrace();

            System.out.println("[" + methodName + "方法出异常了][异常信息:" + e.getMessage() + "]");

            throw e;
        } finally {
            System.out.println("[" + methodName + "方法执行完毕]");
        }

        return returnVal;
    }
}

【切面类 二】

package com.test.spring.aspect;

public class LogAspect2 {

    public void beforeLog() {
        System.out.println("----------方法开始前----------");
    }

    public void afterLog() {
        System.out.println("----------方法执行完毕----------");
    }

}

【测试类】
    @Test
    public void test() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("???.xml");
        TargetObject to = ioc.getBean(TargetObject.class);
        to.add(10, 5);
        System.out.println("========================================");
        to.div(10, 5);
    }

【执行结果】

----------方法开始前----------
【方法执行前】
[add方法开始][参数:[10, 5]]
方法内部打印:15
[add方法返回][返回值:15]
[add方法执行完毕]
【方法结束了】
【方法返回】【返回值:15】
----------方法执行完毕----------
========================================
----------方法开始前----------
【方法执行前】
[div方法开始][参数:[10, 5]]
方法内部打印:2
[div方法返回][返回值:2]
[div方法执行完毕]
【方法结束了】
【方法返回】【返回值:2】
----------方法执行完毕----------