Spring AOP

  |   0 评论   |   0 浏览

AOP 概述

什么是 AOP?

AOP(Aspect Oriented Programming) 是 SoC 原理 和 DRY 原则的一种应用,它实现了在横向方向上的代码重用。SoC 就是 Separation of Concerns, 即 关注点分离;DRY 是 Don' t Repeat Yourself, 即尽量减少重复代码。

有些代码横跨多个类(模块),并且具有相似的逻辑。比如:日志输出、事务管理、代码跟踪、性能监控等,这些被称为不同的关注点。

简单的说,AOP 就是把这些重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,把这些代码添加进去。

相关概念

Join point(接入点)

程序执行中的某个点,比如方法的执行或者异常的捕获。在 Spring AOP 中,通常指的是方法接入点。

Pointcut(切入点)

可以理解为连接点的集合。在 Spring 中使用正则表达式或者 SpEL 表达式来描述。

Advice(建议)

在接入点添加的代码,在 Spring 中,建议通常是一个方法。

根据接入位置,建议的类型:

  • 前置建议(Before advice)
  • 后置建议(After returning advice)
  • 异常建议(After throwing advice)
  • 最终建议(After (finally) advice)
  • 环绕建议(Around advice)

Aspect(切面)

模块化后的横切关注点(cross-cutting concerns),比如:日志管理、事务管理等。在 Spring 中,通常将横切关注点抽象为一个类。

Introduction( 引介)

引介是一种特殊的建议,在不修改类代码的前提下, 引介可以在运行期为类动态地添加一些方法或 Field。

Target object(目标对象)

接入点所在的对象,也就是被代理的对象。

AOP proxy(AOP 代理)

一个类被 AOP 织入增强后,就产生一个结果代理类。

Weaving(编织)

是指把 Advice 代码嵌入到目标对象来创建新的代理对象的过程。

编织分为两类:

  • 静态编织 -- 通过修改源码或者字节码在编译期、后编译期或加载期嵌入代码。
  • 动态编织 -- 通过代理等技术在运行期实现嵌入。

Spring AOP

Spring 最初提供了一些用来实现 AOP 的原生 API,但是配置起来比较麻烦。

Spring 2.0 开始引入基于 XML 和注解的配置方式。

Spring 默认使用 JDK 动态代理(代理的是接口)。另外,Spring 也提供了 CGLIB 代理(代理的是类),当一个业务对象未实现任何接口时,默认使用 CGLIB 代理。

基于原生 API 实现

Spring 最初提供了一些原生的 API 来实现 AOP。

Pointcut API

用来描述切入点,其主要接口和类:

image20210507220107289.png

org.springframework.aop.Pointcut 是切入点的根本接口。

切点分为:静态切入点和动态切入点。

静态切入点:

  • 正则表达式切入点(org.springframework.aop.support.JdkRegexpMethodPointcut

动态切入点:

  • 控制流程切入点(org.springframework.aop.support.ControlFlowPointcut

Advice API

用来描述建议,其主要的接口和类:
image20210513213101196.png

Spring 原生 API 提供的建议类型:

建议类型 接口 备注
Interception around advice MethodInterceptor 环绕建议
Before advice MethodBeforeAdvice 前置建议
Throws advice ThrowsAdvice 异常建议
After Returning advice AfterReturningAdvice 后置建议
Introduction advice IntroductionInterceptor 只能配合IntroductionAdvisor在类的层面上使用

定义一个建议类,需实现对应的接口。

其中,引介建议是一种特殊的建议。

引介不能应用在任何切入点上,只能配合IntroductionAdvisor在类的层面上使用。

Advisor API

在 Spring 中,Advisor 是一个简单的切面,它仅包含一个建议(单个Advice实例)和一个切入点(单个Pointcut实例)。

其主要的接口和类:

image20210513212329981.png

AOP 代理

Spring 提供了一些工厂类,用来生成代理,其主要的接口和类:

image20210509070944898.png

Spring 提供了 org.springframework.aop.framework.ProxyFactoryBean 来创建目标对象的代理。

ProxyFactoryBean 主要属性有两个:

  • targetName -- 用来指定目标对象(即:将被代理的对象)
  • interceptorNames -- Advisor对象或拦截器的列表

默认情况下,如果指定的目标对象没有实现接口,那么 Spring 将会创建基于 CGLIB 的代理,否则将会创建基于 JDK 的代理。

若要强制生成基于 CGLIB 的代理,则需将 proxyTargetClass 属性值置为 true

配置示例

本示例使用的依赖:

 1<dependencies>
 2    <dependency>
 3        <groupId>org.springframework</groupId>
 4        <artifactId>spring-webmvc</artifactId>
 5        <version>5.2.0.RELEASE</version>
 6    </dependency>
 7    <dependency>
 8        <groupId>junit</groupId>
 9        <artifactId>junit</artifactId>
10        <scope>test</scope>
11    </dependency>
12</dependencies>

目标对象

Person 接口:

1package com.mycompany;
2
3public interface Person {
4    void say();
5}

PersonImpl 类:

 1package com.mycompany;
 2
 3public class PersonImpl implements Person{
 4    private String name;
 5    private Integer age;
 6
 7    public String getName() {
 8        return name;
 9    }
10
11    public void setName(String name) {
12        this.name = name;
13    }
14
15    public Integer getAge() {
16        return age;
17    }
18
19    public void setAge(Integer age) {
20        this.age = age;
21    }
22
23    @Override
24    public void say() {
25        System.out.println("Hello, my name is " + this.name + ", I'm " + this.age + " years old.");
26    }
27}

Advice 对象

 1package com.mycompany;
 2
 3import org.aopalliance.intercept.MethodInterceptor;
 4import org.aopalliance.intercept.MethodInvocation;
 5import org.springframework.aop.AfterReturningAdvice;
 6import org.springframework.aop.MethodBeforeAdvice;
 7
 8import java.lang.reflect.Method;
 9
10public class LogAdvice implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor {
11    @Override
12    public void before(Method method, Object[] args, Object target) throws Throwable {
13        System.out.println("【前置建议】 方法:" + method.getName() + " 即将被调用");
14    }
15
16    @Override
17    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
18        System.out.println("【后置建议】 方法:" + method.getName() + " 已经被调用");
19    }
20
21    @Override
22    public Object invoke(MethodInvocation invocation) throws Throwable {
23        System.out.println("【环绕建议】 方法调用之前...");
24        invocation.proceed();
25        System.out.println("【环绕建议】 方法调用之后...");
26        return null;
27    }
28}

配置文件

配置方式有多种,这里列举三种,同样也是基于原始的 IOC Bean 配置。

配置方式一:把切点和Advice配置为Advisor外部的 Bean

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xsi:schemaLocation="http://www.springframework.org/schema/beans
 5                           http://www.springframework.org/schema/beans/spring-beans.xsd">
 6    <!-- 目标对象-->
 7    <bean id="personTarget" class="com.mycompany.PersonImpl">
 8        <property name="name" value="Tony"/>
 9        <property name="age" value="51"/>
10    </bean>
11
12    <!-- 切点 -->
13    <bean id="cut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
14        <property name="pattern" value=".*say.*"/>
15    </bean>
16
17    <!-- 建议 -->
18    <bean id="advice" class="com.mycompany.LogAdvice"></bean>
19
20    <!-- 切面 = 切点 + 建议-->
21    <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
22        <property name="pointcut" ref="cut"></property>
23        <property name="advice" ref="advice"></property>
24    </bean>
25
26    <!-- 代理对象(由 ProxyFactoryBean 生成) -->
27    <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
28        <property name="target" ref="personTarget"/>
29        <property name="interceptorNames">
30            <list>
31                <value>advisor</value>
32            </list>
33        </property>
34    </bean>
35</beans>

配置方式二:把切点和Advice配置为Advisor内部的 Bean

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xsi:schemaLocation="http://www.springframework.org/schema/beans
 5                           http://www.springframework.org/schema/beans/spring-beans.xsd">
 6    <!-- 目标对象-->
 7    <bean id="personTarget" class="com.mycompany.PersonImpl">
 8        <property name="name" value="Tony"/>
 9        <property name="age" value="51"/>
10    </bean>
11
12    <!-- 切面 = 切点 + 建议-->
13    <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
14        <!-- 切点 -->
15        <property name="pointcut">
16            <bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
17                <property name="pattern" value=".*say.*"/>
18            </bean>
19        </property>
20        <!-- 建议 -->
21        <property name="advice">
22            <bean class="com.mycompany.LogAdvice"></bean>
23        </property>
24    </bean>
25
26    <!-- 代理对象(由 ProxyFactoryBean 生成) -->
27    <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
28        <property name="target" ref="personTarget"/>
29        <property name="interceptorNames">
30            <list>
31                <value>advisor</value>
32            </list>
33        </property>
34    </bean>
35</beans>

配置方式三:直接在Advisor内部配置正则表达式

Spring 提供了 RegexpMethodPointcutAdvisor 类,可以省略切点 Bean 的定义,直接在 Advisor 中配置正则表达式。

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xsi:schemaLocation="http://www.springframework.org/schema/beans
 5                           http://www.springframework.org/schema/beans/spring-beans.xsd">
 6    <!-- 目标对象-->
 7    <bean id="personTarget" class="com.mycompany.PersonImpl">
 8        <property name="name" value="Tony"/>
 9        <property name="age" value="51"/>
10    </bean>
11
12    <!-- 切面 = 切点 + 建议-->
13    <bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
14        <!-- 切点正则表达式 -->
15        <property name="patterns">
16            <list>
17                <value>.*say.*</value>
18            </list>
19        </property>
20        <!-- 建议 -->
21        <property name="advice">
22            <bean class="com.mycompany.LogAdvice"></bean>
23        </property>
24    </bean>
25
26    <!-- 代理对象(由 ProxyFactoryBean 生成) -->
27    <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
28        <property name="target" ref="personTarget"/>
29        <property name="interceptorNames">
30            <list>
31                <value>advisor</value>
32            </list>
33        </property>
34    </bean>
35</beans>

测试方法

 1import com.mycompany.Person;
 2import org.junit.Test;
 3import org.springframework.context.ApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class MyTest {
 7    @Test
 8    public void test1() {
 9        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
10        Person person = context.getBean("person", Person.class);
11        person.say();
12    }
13
14    @Test
15    public void test2() {
16        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
17        Person person = context.getBean("person", Person.class);
18        person.say();
19    }
20
21    @Test
22    public void test3() {
23        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");
24        Person person = context.getBean("person", Person.class);
25        person.say();
26    }
27}

测试的结果都是一样的:

1【环绕建议】 方法调用之前...
2【前置建议】 方法:say 即将被调用
3Hello, my name is Tony, I'm 51 years old.
4【后置建议】 方法:say 已经被调用
5【环绕建议】 方法调用之后...

基于 XML 的 AOP 配置

基于 XML 进行配置,需要先导入 aop schema

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xmlns:aop="http://www.springframework.org/schema/aop"
 5       xsi:schemaLocation="http://www.springframework.org/schema/beans
 6           http://www.springframework.org/schema/beans/spring-beans.xsd
 7           http://www.springframework.org/schema/aop
 8           http://www.springframework.org/schema/aop/spring-aop.xsd">
 9
10</beans>

声明切面(Aspect)

切面通常是一个 Java 类,类中封装了一些通用的代码,比如事务管理、日志管理等。
使用<aop:aspect>标签配置一个切面,使用ref指定一个Bean的 id。

1<aop:config>
2    <aop:aspect id="myAspect" ref="aBean">
3                ...
4    </aop:aspect>
5</aop:config>
6
7<bean id="aBean" class="...">
8        ...
9</bean>

声明切入点(Pointcut)

使用<aop:pointcut>标签配置切入点,它可以放在 <aop:config> 标签下,配置全局切入点。也可以放在 <aop:aspect> 标签下用来配置某个切面专用的切入点。

expression属性指定作为切入点的方法。

1、配置全局切入点:

<aop:config> 标签下配置切入点,所有的切面都可以引用。

1<aop:config>
2    <!-- 使用 AspectJ 切入点表达式 -->
3    <aop:pointcut id="businessService"
4        expression="execution(* com.xyz.myapp.service.*.*(..))"/>
5    <!-- 使用全限定类名+方法名 -->
6    <aop:pointcut id="businessService"
7       expression="com.xyz.myapp.SystemArchitecture.businessService()"/>
8</aop:config>

2、配置内部切入点:

<aop:aspect> 标签内部配置切入点,仅供本切面引用。

1<aop:config>
2
3    <aop:aspect id="myAspect" ref="aBean">
4        <aop:pointcut id="businessService"
5            expression="execution(* com.xyz.myapp.service.*.*(..))"/>
6        ...
7    </aop:aspect>
8
9</aop:config>

声明建议(Advice)

<aop:aspect> 标签下配置建议。

前置建议

配置方式一:引用一个全局切入点 Bean

1<aop:aspect id="beforeExample" ref="aBean">
2    
3    <aop:before pointcut-ref="dataAccessOperation"
4                method="doAccessCheck"/>
5    
6</aop:aspect>

其中,method 属性指向的是 beforeExample Bean 中的方法。pointcut-ref 属性引用的是一个全局的切入点 Bean。

配置方式二:直接使用 EL 表达式指定切入点

1<aop:aspect id="beforeExample" ref="aBean">
2
3    <aop:before pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
4                method="doAccessCheck"/>
5</aop:aspect>

后置建议

后置建议的配置方式与前置建议类似,另外,后置建议可以获取切入点方法的返回值,并将其传给 Advice 方法。

1<aop:aspect id="afterReturningExample" ref="aBean">
2
3    <aop:after-returning pointcut-ref="dataAccessOperation"
4                         returning="retVal"
5                         method="doAccessCheck"/>
6
7</aop:aspect>

其中,retValdoAccessCheck 方法的参数名。

1public void doAccessCheck(Object retVal) {...}

异常建议

异常建议的配置方式与前置建议类似,另外,异常建议可以获取切入点方法抛出的异常,并将其传给 Advice 方法。

1<aop:aspect id="afterThrowingExample" ref="aBean">
2
3    <aop:after-throwing pointcut-ref="dataAccessOperation"
4                        throwing="dataAccessEx"
5                        method="doRecoveryActions"/>
6
7</aop:aspect>

其中,dataAccessExdoRecoveryActions 方法的参数名。

1public void doRecoveryActions(DataAccessException dataAccessEx) {...}

最终建议

最终建议的配置方式与前置建议类似:

1<aop:aspect id="afterFinallyExample" ref="aBean">
2
3    <aop:after pointcut-ref="dataAccessOperation"
4               method="doReleaseLock"/>
5
6</aop:aspect>

环绕建议

环绕建议的配置:

1<aop:aspect id="aroundExample" ref="aBean">
2
3    <aop:around pointcut-ref="businessService"
4                method="doBasicProfiling"/>
5
6</aop:aspect>

其中,Advice 方法的第一个参数的类型必须是 ProceedingJoinPoint

1public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
2        // start stopwatch
3        Object retVal = pjp.proceed();
4        // stop stopwatch
5        return retVal;
6}

通常情况下,环绕建议都是独立使用的。如果它和其它建议同时使用,它会影响其它建议的执行顺序。

环绕建议是 Spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。

示例

 1<aop:config>
 2
 3    <!-- 配置切入点表达式 -->
 4    <aop:pointcut expression="execution(* com.xyz.myapp.service.*.*(..))" id="pt1"/>
 5
 6    <!-- 配置切面 -->
 7    <aop:aspect id="logAdvice" ref="logger">
 8        <!-- 环绕建议 -->
 9        <aop:around method="aroundPrintLog" pointcut-ref="pt1"/>
10    </aop:aspect>
11
12</aop:config>
13<!-- 配置Logger类 -->
14<bean id="logger" class="com.xyz.myapp.utils.Logger"></bean>

对应的方法定义:

 1/**
 2 * Spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕建议的方法参数。
 3 * Spring 提供了该接口的实现,我们可以直接使用。
 4 * @return Object
 5 */
 6public Object aroundPrintLog(ProceedingJoinPoint pjp) {
 7    Object rtValue = null;
 8    try {
 9        System.out.println("Logger中的aroundPrintLog方法开始记录日志了......前置");
10        rtValue = pjp.proceed(pjp.getArgs());
11        System.out.println("Logger中的aroundPrintLog方法开始记录日志了......后置");
12        return rtValue;
13    } catch (Throwable e) {
14        System.out.println("Logger中的aroundPrintLog方法开始记录日志了......异常");
15        throw new RuntimeException(e);
16    }finally {
17        System.out.println("Logger中的aroundPrintLog方法开始记录日志了......最终");
18    }
19}

引介(Introduction)

引介的作用,简单点说就是可以动态的扩展一个类,给它动态添加一个额外的方法。

示例

 1<aop:aspect id="usageTrackerAspect" ref="usageTracking">
 2
 3    <aop:declare-parents types-matching="com.xzy.myapp.service.*+"
 4                         implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
 5                         default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
 6
 7    <aop:before pointcut="com.xyz.myapp.SystemArchitecture.businessService() and this(usageTracked)"
 8                method="recordUsage"/>
 9
10</aop:aspect>
1public void recordUsage(UsageTracked usageTracked) {
2        usageTracked.incrementUseCount();
3}

Advisor

Advisor 的概念是从Spring 1.2版本开始引入的,并且在 AspectJ 中没有直接等效的概念。

Advisor 是一个简单的切面,它只包含一个 advice 实例,并且这个实例必须实现 Spring 提供的 Advice 接口。

配置 Advisor 得使用 <aop:advisor> 标签。

示例

 1<aop:config>
 2
 3     <aop:pointcut id="businessService"
 4                   expression="execution(* com.xyz.myapp.service.*.*(..))"/>
 5
 6     <aop:advisor pointcut-ref="businessService"
 7                  advice-ref="tx-advice"/>
 8
 9</aop:config>
10
11<tx:advice id="tx-advice">
12    <tx:attributes>
13        <tx:method name="*" propagation="REQUIRED"/>
14    </tx:attributes>
15</tx:advice>

配置示例

本示例使用的依赖:

 1<dependencies>
 2    <dependency>
 3        <groupId>org.springframework</groupId>
 4        <artifactId>spring-webmvc</artifactId>
 5        <version>5.2.0.RELEASE</version>
 6    </dependency>
 7    <dependency>
 8        <groupId>org.aspectj</groupId>
 9        <artifactId>aspectjweaver</artifactId>
10        <version>1.9.4</version>
11    </dependency>
12    <dependency>
13        <groupId>junit</groupId>
14        <artifactId>junit</artifactId>
15        <scope>test</scope>
16    </dependency>
17</dependencies>

目标对象

UserService接口:

1package com.demo.service;
2
3public interface UserService {
4    void add();
5    void delete();
6    void update();
7    void query();
8}

UserServiceImpl类:

 1package com.demo.service;
 2
 3public class UserServiceImpl implements UserService{
 4
 5    @Override
 6    public void add() {
 7        System.out.println("新增用户");
 8    }
 9
10    @Override
11    public void delete() {
12        System.out.println("删除用户");
13    }
14
15    @Override
16    public void update() {
17        System.out.println("更新用户");
18    }
19
20    @Override
21    public void query() {
22        System.out.println("查询用户");
23    }
24}

Advice 对象

前置建议:

 1import org.springframework.aop.MethodBeforeAdvice;
 2
 3import java.lang.reflect.Method;
 4
 5public class BeforeLog implements MethodBeforeAdvice {
 6    @Override
 7    public void before(Method method, Object[] args, Object target) throws Throwable {
 8        System.out.println("【前置建议】 方法:" + method.getName() + " 即将被调用...");
 9    }
10}

后置建议:

 1import org.springframework.aop.AfterReturningAdvice;
 2
 3import java.lang.reflect.Method;
 4
 5public class AfterLog implements AfterReturningAdvice {
 6    @Override
 7    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
 8        System.out.println("【后置建议】 方法:" + method.getName() + " 已经被调用...");
 9    }
10}

自定义建议:

 1package com.demo.myadvice;
 2
 3public class UserLog {
 4    public void before(){
 5        System.out.println("方法调用之前...");
 6    }
 7
 8    public void after(){
 9        System.out.println("方法调用之后...");
10    }
11}

配置文件

配置方式一:基于原生API

applicationContext1.xml 文件:

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xmlns:aop="http://www.springframework.org/schema/aop"
 5       xsi:schemaLocation="http://www.springframework.org/schema/beans
 6           http://www.springframework.org/schema/beans/spring-beans.xsd
 7           http://www.springframework.org/schema/aop
 8           http://www.springframework.org/schema/aop/spring-aop.xsd">
 9
10    <!-- 目标对象 -->
11    <bean id="userService" class="com.demo.service.UserServiceImpl"/>
12
13    <!-- 建议 -->
14    <bean id="beforeLog" class="com.demo.log.BeforeLog"/>
15    <bean id="afterLog" class="com.demo.log.AfterLog"/>
16
17    <!-- 配置AOP -->
18    <aop:config>
19        <!-- 切入点 -->
20        <aop:pointcut id="pointcut" expression="execution(* com.demo.service.UserServiceImpl.*(..))"/>
21
22        <!-- 建议 -->
23        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
24        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
25    </aop:config>
26</beans>

配置方式二:自定义建议

applicationContext2.xml 文件:

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xmlns:aop="http://www.springframework.org/schema/aop"
 5       xsi:schemaLocation="http://www.springframework.org/schema/beans
 6           http://www.springframework.org/schema/beans/spring-beans.xsd
 7           http://www.springframework.org/schema/aop
 8           http://www.springframework.org/schema/aop/spring-aop.xsd">
 9
10    <!-- 目标对象 -->
11    <bean id="userService" class="com.demo.service.UserServiceImpl"/>
12
13    <!-- 自定义建议 -->
14    <bean id="userLog" class="com.demo.myadvice.UserLog"/>
15
16    <!-- 配置AOP -->
17    <aop:config>
18        <!-- 切入点 -->
19        <aop:pointcut id="pointcut" expression="execution(* com.demo.service.UserServiceImpl.*(..))"/>
20
21        <!-- 建议 -->
22        <aop:aspect ref="userLog">
23            <aop:before method="before" pointcut-ref="pointcut"/>
24            <aop:after method="after" pointcut-ref="pointcut"/>
25        </aop:aspect>
26    </aop:config>
27</beans>

测试方法

 1import com.demo.service.UserService;
 2import org.junit.Test;
 3import org.springframework.context.ApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class MyTest {
 7    @Test
 8    public void test1(){
 9        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
10        UserService userService = (UserService)context.getBean("userService");
11        userService.add();
12    }
13
14    @Test
15    public void test2(){
16        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
17        UserService userService = (UserService)context.getBean("userService");
18        userService.add();
19    }
20}

test1() 方法的输出结果:

1【前置建议】 方法:add 即将被调用...
2新增用户
3【后置建议】 方法:add 已经被调用...

test2() 方法的输出结果:

1方法调用之前...
2新增用户
3方法调用之后...

基于注解的 AOP 配置

Spring 框架支持@AspectJ注解,该注解是 AspectJ 项目 的一部分。

启用@AspectJ注解支持

1、在 XML 配置文件中启用

1<aop:aspectj-autoproxy/>

2、在 Java 配置类中启用

1@Configuration
2@EnableAspectJAutoProxy
3public class AppConfig {
4
5}

声明切面(Aspect)

在 XML 文件中声明:

1<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
2        <!-- configure properties of aspect here as normal -->
3</bean>

切面类需要使用 @Aspect 修饰

1package org.xyz;
2import org.aspectj.lang.annotation.Aspect;
3
4@Aspect
5public class NotVeryUsefulAspect {
6	// 在这里声切点、建议等
7}

声明切入点(Pointcut)

在切面类的方法上指定切入点:

1@Pointcut("execution(public * *(..))") // the pointcut expression
2private void anyPublicOperation() {} // the pointcut signature
3
4@Pointcut("within(com.xyz.someapp.trading..*)")
5private void inTrading() {}
6
7@Pointcut("anyPublicOperation() && inTrading()")
8private void tradingOperation() {}

声明建议(Advice)

 1@Aspect
 2public class Logger {
 3
 4    @Pointcut("execution(* com.myapp.service.impl.*.*(..))")
 5    private void p1() {}
 6
 7    /**
 8     * 前置建议
 9     */
10    @Before("execution(* com.myapp.service.impl.*.*(..))")
11    public void beforePrintLog() {
12        System.out.println("Logger中的beforePrintLog方法开始记录日志了......");
13    }
14
15    /**
16     * 后置建议
17     */
18    @AfterReturning("com.myapp.service.impl.getAccount()")
19    public void afterReturningPrintLog() {
20        System.out.println("Logger中的afterReturningPrintLog方法开始记录日志了......");
21    }
22
23    /**
24     * 异常建议
25     */
26    @AfterThrowing("p1()")
27    public void afterThrowingPrintLog() {
28        System.out.println("Logger中的afterThrowingPrintLog方法开始记录日志了......");
29    }
30
31    /**
32     * 最终建议
33     */
34    @After("p1()")
35    public void afterPrintLog() {
36        System.out.println("Logger中的afterPrintLog方法开始记录日志了......");
37    }
38
39    /**
40     * 环绕建议
41     * @return
42     */
43    @Around("p1()")
44    public Object aroundPrintLog(ProceedingJoinPoint pjp) {
45        Object rtValue = null;
46        try {
47            System.out.println("Logger中的aroundPrintLog方法开始记录日志了......前置");
48            rtValue = pjp.proceed(pjp.getArgs());
49            System.out.println("Logger中的aroundPrintLog方法开始记录日志了......后置");
50            return rtValue;
51        } catch (Throwable e) {
52            System.out.println("Logger中的aroundPrintLog方法开始记录日志了......异常");
53            throw new RuntimeException(e);
54        }finally {
55            System.out.println("Logger中的aroundPrintLog方法开始记录日志了......最终");
56        }
57
58    }
59}

相关资料

5. Aspect Oriented Programming with Spring

6. Spring AOP APIs