Spring学习8-AOP

什么是AOP

面向切面的程序设计(Aspect-oriented programming ),简单来讲就是把程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

AOP作用及优势

作用:

  • 在程序运行期间,不修改源码对已有方法进行增强。

优势:

  • 减少重复代码
  • 提高开发效率
  • 维护方便

动态代理

回顾上一篇即可

Spring中的AOP

案例总结回顾

首先来整理一下整个案例的思路,最初我们在账户的业务层实现类中实现了对账户的增删改查,每个功能其实都是执行一条SQL语句,问题就是:这里事务被自动控制了。换言之,我们使用了connection对象的setAutoCommit(true) 此方式控制事务,如果我们每次都执行一条sql语句,没有问题,但是如果业务方法一次要执行多条sql语句,这种方式就无法实现功能了。例如在业务层实现类增加一个转账的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer…………");
// 2.1根据名称查询转入账户
Account source = accountDao.findAccountByName(sourceName);
// 2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
// 2.3转出账户减钱
source.setMoney(source.getMoney() - money);
// 2.4转入账户加钱
target.setMoney(target.getMoney() + money);
// 2.5更新转出账户
accountDao.updateAccount(source);

int i = 1/0; //故意加错,模拟转账异常

// 2.6更新转入账户
accountDao.updateAccount(target);
}

这里就会出问题,转出账户钱少了,但是转入账户更新被打断并没有执行,所以转入账户钱没有增加,原因就是每执行一条SQL语句都去连接池获取一次,每次执行持久层方法都是独立事务,导致无法实现事务控制(不符合事务的一致性)。

为了解决上述问题,就加入了事务控制。让业务层来控制事务的提交和回滚。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package com.gsynf.service.impl;

import com.gsynf.dao.IAccountDao;
import com.gsynf.domain.Account;
import com.gsynf.service.IAccountService;
import com.gsynf.utils.TransactionManager;

import java.util.List;

/**
* 账户的业务层实现类
*
* 事务控制应该都在业务层
*/
public class AccountServiceImpl_OLD implements IAccountService{

private IAccountDao accountDao;
private TransactionManager tsManager;

public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}

public void setTsManager(TransactionManager tsManager) {
this.tsManager = tsManager;
}

@Override
public List<Account> findAllAccount() {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
List<Account> accounts = accountDao.findAllAccount();
//3.提交事务
tsManager.commit();
//4.返回结果
return accounts;
}catch (Exception e) {
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}

}

@Override
public Account findAccountById(Integer accountId) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
Account account = accountDao.findAccountById(accountId);
//3.提交事务
tsManager.commit();
//4.返回结果
return account;
}catch (Exception e) {
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}
}

@Override
public void saveAccount(Account account) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
accountDao.saveAccount(account);
//3.提交事务
tsManager.commit();
}catch (Exception e) {
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}

}

@Override
public void updateAccount(Account account) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
accountDao.updateAccount(account);
//3.提交事务
tsManager.commit();
}catch (Exception e) {
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}

}

@Override
public void deleteAccount(Integer acccountId) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
accountDao.deleteAccount(acccountId);
//3.提交事务
tsManager.commit();
}catch (Exception e) {
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}

}

@Override
public void transfer(String sourceName, String targetName, Float money) {
try{
//1.开启事务
tsManager.beginTransaction();

//2.执行操作
// 2.1根据名称查询转入账户
Account source = accountDao.findAccountByName(sourceName);
// 2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
// 2.3转出账户减钱
source.setMoney(source.getMoney() - money);
// 2.4转入账户加钱
target.setMoney(target.getMoney() + money);
// 2.5更新转出账户
accountDao.updateAccount(source);

int i = 1/0; //故意加错,打断

// 2.6更新转入账户
accountDao.updateAccount(target);

//3.提交事务
tsManager.commit();
}catch (Exception e) {
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.gsynf.utils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
* @ClassName: connectionUtils
* @Description: 连接的工具类,用于实现从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

private DataSource dataSource;

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try {
// 1.先从ThreadLocal上获取
Connection conn = tl.get();
// 2.判断当前线程上是否有连接
if (conn == null) {
// 3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
// 4.返回当前线程上的连接
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 将连接和线程解绑
*/
public void removeConnection() {
tl.remove();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.gsynf.utils;



/**
* @ClassName: TransactionManager
* @Description: 和事务管理相关的工具类,包含开启事务、提交事务、回滚事务、释放连接
*/
public class TransactionManager {

private ConnectionUtils connectionUtils;

public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}

/**
* 开启事务
*/
public void beginTransaction() {
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e) {
e.printStackTrace();
}

}
/**
* 提交事务
*/
public void commit() {
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback() {
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release() {
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e) {
e.printStackTrace();
}
}
}

通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。 试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。为了解决这个问题,引入了动态代理,详见上一篇关于动态代理的文章对具体案例的分析。当我们改造完成之后,业务层用于控制事务的重复代码就都可以删掉了。

细节与说明

在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

这里就是通过配置的方式,实现上述案例的功能。

术语

  • Joinpoint(连接点):
    • 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
    • 具体来讲,就是业务层接口中的方法,他们是业务方法和增强方法(事务控制)的连接点,可以使业务方法增加事务控制,形成完整的业务逻辑。
  • Pointcut(切入点):
    • 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
    • 具体来讲,业务层接口中每一个方法都是连接点,但是只有每一个被增强的方法才是切入点,不是所有方法都需要增强,如果有的方法不增强,它就不是切入点。
    • 连接点是动态代理中的所有方法,切入点是其中被增强的方法。
  • Advice(通知/增强):
    • 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
    • 具体来讲,就是invoke方法拦截之后要做的事,这里就是TransactionManger,invoke拦截之后要进行的就是一系列的事务操作。
    • 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
    • 具体来讲,找到明确调用业务层的method.invoke()方法,之前的就是前置通知,之后的就是后置通知,catch中的就是异常通知,finally中的就是最终通知。整个invoke方法在执行就是环绕通知。
  • Introduction(引介):
    • 引介是一种特殊的通知,在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
  • Target(目标对象):
    • 代理的目标对象,也就是被代理对象。
    • 具体来讲,就是accountService
  • Weaving(织入):
    • 是指把增强应用到目标对象来创建新的代理对象的过程。
    • 具体来讲,本来的对象accountService没有事务支持,为了事务支持加入了动态代理,返回了一个代理对象(IAccountService) Proxy.newProxyInstance,在其中加入了事务控制,整个这个过程就是Weaving。
    • spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
  • Proxy(代理):
    • 一个类被AOP织入增强后,就产生一个结果代理类。
    • 具体来讲,就是创建出来的代理对象(IAccountService) Proxy.newProxyInstance
  • Aspect(切面):
    • 是切入点和通知(引介)的结合。

要明确的事

  • a、开发阶段(我们做的)

    • 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
    • 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP编程人员来做。
    • 在配置文件中,声明切入点与通知间的关系,即切面。:AOP编程人员来做。
  • b、运行阶段(Spring框架完成的)

    • Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

基于XML的AOP配置

案例:增加一个打印日志的通知,让其在切入点方法执行之前执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.gsynf.service.impl;

import com.gsynf.service.IAccountService;

/**
* @ClassName: AccountServiceImpl
* @Description: 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("执行了保存");
}

public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}

public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.gsynf.utils;

/**
* @ClassName: Logger
* @Description: 用于记录日志的工具类,里面提供了公共的代码
*/
public class Logger {
/**
* 用于打印日志,计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
*/
public void printLog() {
System.out.println("Logger类中的printLog方法开始记录日志了………………");
}

}

bean.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--配置spring的IoC,把service对象配置进来-->
<bean id="accountService" class="com.gsynf.service.impl.AccountServiceImpl"></bean>

<!--spring中基于XML的AOP配置步骤
1.把通知bean也交给spring管理
2.使用aop:config标签表明开始AOP的配置
3.使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的Id
4.在aop:aspect标签内部使用对应的标签来配置通知的类型
我们现在是实例是让printLog方法在切入点方法执行之前执行,所以是前置通知
aop:before:表示前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名…………类名.方法名(参数列表)
标准的表达式写法:
public void com.gsynf.service.impl.AccountServiceImpl.saveAccount()
改进:
访问修饰符可以省略;
返回值可以使用通配符,表示任意返回值;
包名可以使用通配符,表示任意包,但是有几级包,就需要写几个*. ;
包名可以使用..表示当前包及其子包;
类名和方法名都可以使用*实现统配;
参数列表
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名 java.lang.String
可以使用通配符*表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)

实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.gsynf.service.impl.*.*(..)
-->
<!--配置通知类,也就是Logger类-->
<bean id="logger" class="com.gsynf.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
<!-- <aop:before method="printLog" pointcut="execution(public void com.gsynf.service.impl.AccountServiceImpl.saveAccount())"></aop:before>-->
<!-- <aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>-->
<aop:before method="printLog" pointcut="execution(* com.gsynf.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>

添加一个Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.gsynf.test;

import com.gsynf.service.IAccountService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Locale;
import java.util.Map;

/**
* @ClassName: AOPTest
* @Description: 测试AOP配置
*/
public class AOPTest {
public static void main(String[] args) {
// 1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2.获取对象
IAccountService as = (IAccountService) ac.getBean("accountService");
// 3.执行方法
as.saveAccount();
as.updateAccount(1);
as.deleteAccount();

}
}

测试结果:

1
2
3
4
5
6
Logger类中的printLog方法开始记录日志了………………
执行了保存
Logger类中的printLog方法开始记录日志了………………
执行了更新1
Logger类中的printLog方法开始记录日志了………………
执行了删除

spring中基于XML的AOP配置步骤

详见bean.xml中的注释。

不同类型通知的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
<!--前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>

<!--后置通知:在切入点方法执行之后执行,和异常通知只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>

<!--异常通知:在切入点方法执行产生异常之后执行,和后置通知只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

<!--最终通知:无论切入点方法是否正常执行它都会在其后执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

<!--配置切入点表达式
此标签写在aop:aspect标签内部只能在当前切面使用;写在外部所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.gsynf.service.impl.*.*(..))"/>
</aop:aspect>
</aop:config>

环绕通知

环绕通知这里单独说一下

1
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>

在bean.xml中的配置和其他通知一样,在Logger类中加入aroundPrintLog方法

1
2
3
public void aroundPrintLog() {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了………………");
}

此时运行,会发现当配置了环绕通知之后,aroundPrintLog方法中的语句打印了,而“执行了保存”没有打印,即切入点方法没有执行,而通知方法执行了。原因是什么呢?通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法,即method.invoke(),而此处的代码中没有。那么如何解决呢?Spring框架提供了一个接口:ProceedingJoinPoint.该接口有一个方法proceed(),该方法就相当于明确调用切入点方法。该方法可以作为环绕通知的方法参数,在程序执行时,Spring框架会为我们提供该接口的实现类供我们调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
Object[] args = pjp.getArgs(); //得到方法执行所需的参数

System.out.println("Logger类中的aroundPrintLog方法开始记录日志了………………前置");

rtValue = pjp.proceed(args); //明确调用业务层方法(切入点方法)

System.out.println("Logger类中的aroundPrintLog方法开始记录日志了………………后置");
return rtValue;
} catch (Throwable throwable) {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了………………异常");
throw new RuntimeException(throwable);
} finally {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了………………最终");
}
}

此时运行,会发现结果如下:

1
2
3
4
Logger类中的aroundPrintLog方法开始记录日志了………………前置
执行了保存
Logger类中的aroundPrintLog方法开始记录日志了………………后置
Logger类中的aroundPrintLog方法开始记录日志了………………最终

也就是说,在环绕通知中可以实现前置、后置、异常、最终这几种通知,其实,Spring中环绕通知另一种理解:它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式,换句话说,其他通知除了可以自己配,还可以在环绕通知中配,这就和动态代理中讲到的通知很类似了。

基于注解的AOP配置

普通注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">

<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.gsynf"></context:component-scan>

<!--配置AOP,改为注解-->

<!--配置spring开启注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

业务层实现类加注解@Service("accountService")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
………………Logger.java………………
@Component("logger")
@Aspect //表示当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.gsynf.service.impl.*.*(..))")
private void pt1() {

}
/**
* 前置通知
*/
@Before("pt1()")
public void beforePrintLog() {
System.out.println("前置通知:Logger类中的beforePrintLog方法开始记录日志了………………");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog() {
System.out.println("后置通知:Logger类中的afterReturningPrintLog方法开始记录日志了………………");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog() {
System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始记录日志了………………");
}
/**
* 最终通知
*/
@After("pt1()")
public void afterPrintLog() {
System.out.println("最终通知:Logger类中的afterPrintLog方法开始记录日志了………………");
}
//@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
Object[] args = pjp.getArgs(); //得到方法执行所需的参数

System.out.println("Logger类中的aroundPrintLog方法开始记录日志了………………前置");

rtValue = pjp.proceed(args); //明确调用业务层方法(切入点方法)

System.out.println("Logger类中的aroundPrintLog方法开始记录日志了………………后置");
return rtValue;
} catch (Throwable throwable) {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了………………异常");
throw new RuntimeException(throwable);
} finally {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了………………最终");
}
}
}

此时运行Test,会发现输出

1
2
3
4
前置通知:Logger类中的beforePrintLog方法开始记录日志了………………
执行了保存
最终通知:Logger类中的afterPrintLog方法开始记录日志了………………
后置通知:Logger类中的afterReturningPrintLog方法开始记录日志了………………

这里最终通知和后置通知是反的,这里目前是无法更改的,所以在选用时比较推荐使用环绕方式配置,因为何时调用全由自己控制,所以不会有问题。

纯注解

1
2
3
4
5
@Configuration 
@ComponentScan(basePackages="com.gsynf")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

:转载文章请注明出处,谢谢~