Spring——AOP理论

概述

AOP ,即面向切面编程,可以用来管理一些和主业务无关的周边业务,如日志记录,事务管理等;

相关概念

Join point :连接点,表示程序执行期间的一个点,在 Spring AOP 表示的就是一个方法,即一个方法可以看作是一个 Join point

pointcut :切点,就是与连接点匹配的谓词,什么意思呢,就是需要执行 Advice 的连接点就是切点

Advice :增强,在连接点执行的操作,分为前置、后置、异常、最终、环绕增强五种

Aspect :切面,由 pointcut 和 Advice 组成,可以简单的认为 @Aspect 注解的类就是一个切面

Target object :目标对象,即 织入 advice 的目标对象

AOP proxy :代理类,在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象

Weaving :织入,将 Aspect 应用到目标对象中去

demo

要想使用 Spring AOP ,首先先得在 Spring 配置文件中配置如下标签:

1
<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>

expose-proxy : 是否需要将当前的代理对象使用 ThreadLocal 进行保存,默认为false。

例如 Aop 需要对某个接口下的所有方法进行拦截,但是有些方法在内部进行自我调用:

1
2
3
4
5
6
7
1    public void test_1()
2 {
3 this.test_2();
4 }
5 public void test_2()
6 {
7 }

调用 test_1,此时 test_2 将不会被拦截进行增强。所以该属性 expose-proxy 就是用来解决这个问题的,即 AOP 代理的获取。

proxy-target-class :是否使用 CGLIB 进行代理,默认为false,使用JDK代理。当需要代理的类没有实现任何接口的时候才会使用 CGLIB 进行代理。如果想都是用 CGLIB 进行代理,可以把该属性设置为 true 即可。

接口:

1
2
3
4
5
6
7
1public interface IUserService {
2 void add(User user);
3 User query(String name);
4 List<User> qyertAll();
5 void delete(String name);
6 void update(User user);
7}

接口实现:

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
1@Service("userServiceImpl")
2public class UserServiceImpl implements IUserService {
3
4 @Override
5 public void add(User user) {
6 System.out.println("添加用户成功,user=" + user);
7 }
8
9 @Override
10 public User query(String name) {
11 System.out.println("根据name查询用户成功");
12 User user = new User(name, 20, 1, 1000, "java");
13 return user;
14 }
15
16 @Override
17 public List<User> qyertAll() {
18 List<User> users = new ArrayList<>(2);
19 users.add(new User("zhangsan", 20, 1, 1000, "java"));
20 users.add(new User("lisi", 25, 0, 2000, "Python"));
21 System.out.println("查询所有用户成功, users = " + users);
22 return users;
23 }
24
25 @Override
26 public void delete(String name) {
27 System.out.println("根据name删除用户成功, name = " + name);
28 }
29
30 @Override
31 public void update(User user) {
32 System.out.println("更新用户成功, user = " + user);
33 }
34}

定义AOP切面 前置增强:

1
2
3
4
5
6
7
8
9
10
1@Component
2@Aspect
3public class UserAspectj {
4
5 // 在方法执行之前执行
6 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))")
7 public void before_1(){
8 System.out.println("log: 在 add 方法之前执行....");
9 }
10}

如果想要获取目标方法执行的参数等信息呢,我们可在 切点的方法中添参数 JoinPoint ,通过它了获取目标对象的相关信息:

1
2
3
4
5
6
7
8
9
1    @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))")
2 public void before_2(JoinPoint joinPoint){
3 Object[] args = joinPoint.getArgs();
4 User user = null;
5 if(args[0].getClass() == User.class){
6 user = (User) args[0];
7 }
8 System.out.println("log: 在 add 方法之前执行, 方法参数 = " + user);
9 }

后置增强:

1
2
3
4
5
1    // 在方法执行之后执行
2 @After("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))")
3 public void after_1(){
4 System.out.println("log: 在 add 方法之后执行....");
5 }

返回增强:

1
2
3
4
1@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object")
2public void after_return(Object object){
3 System.out.println("在 query 方法返回后执行, 返回值= " + object);
4}

异常增强:

1
2
3
4
1@AfterThrowing(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", throwing = "ex")
2public void after_throw(Exception ex){
3 System.out.println("在 query 方法抛异常时执行, 异常= " + ex);
4}

环绕增强:

1
2
3
4
5
6
7
1@Around("execution(* main.tsmyk.mybeans.inf.IUserService.delete(..))")
2public void test_around(ProceedingJoinPoint joinPoint) throws Throwable {
3 Object[] args = joinPoint.getArgs();
4 System.out.println("log : delete 方法执行之前, 参数 = " + args[0].toString());
5 joinPoint.proceed();
6 System.out.println("log : delete 方法执行之后");
7}

也可以现在可以使用 @Pointcut 来声明一个可重用的切点表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
 1// 声明 pointcut
2@Pointcut("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))")
3public void pointcut(){
4}
5
6@Before("pointcut()")
7public void before_3(){
8 System.out.println("log: 在 query 方法之前执行");
9}
10@After("pointcut()")
11public void after_4(){
12 System.out.println("log: 在 query 方法之后执行....");
13}

指示符

  1. execution : 它用来匹配方法执行的连接点,也是 Spring AOP 使用的主要指示符,在切点表达式中使用了通配符 () 和 (.. ),其中,( )可以表示任意方法,任意返回值,(..)表示方法的任意参数。

  2. within : 匹配特定包下的所有类的所有 Joinpoint(方法),包括子包,注意是所有类,而不是接口,如果写的是接口,则不会生效。

  3. @within : 匹配所有持有指定注解类型的方法,如 @within(Secure),任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用。

  4. target : 匹配的是一个目标对象,target(main.tsmyk.mybeans.inf.IUserService)匹配的是该接口下的所有 Join point 。

  5. @target : 匹配一个目标对象,这个对象必须有特定的注解,如 @target(org.springframework.transaction.annotation.Transactional) 匹配任何 有 @Transactional 注解的方法。

  6. this : 匹配当前AOP代理对象类型的执行方法,this(service.IPointcutService),当前AOP对象实现了 IPointcutService接口的任何方法。

  7. arg : 匹配参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 匹配只有一个参数 name 的方法
@Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name)")
public void test_arg(){

}

// 匹配第一个参数为 name 的方法
@Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name, ..)")
public void test_arg2(){

}

// 匹配第二个参数为 name 的方法
@Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(*, name, ..)")
public void test_arg3(){

}
  1. @arg : 匹配参数,参数有特定的注解,@args(Anno)),方法参数标有Anno注解。

  2. bean : 匹配特定的 bean 名称的方法

1
2
3
4
5
6
7
8
9
10
11
1    // 匹配 bean 的名称为 userServiceImpl 的所有方法
2 @Before("bean(userServiceImpl)")
3 public void test_bean(){
4 System.out.println("===================");
5 }
6
7 // 匹配 bean 名称以 ServiceImpl 结尾的所有方法
8 @Before("bean(*ServiceImpl)")
9 public void test_bean2(){
10 System.out.println("+++++++++++++++++++");
11 }
  1. @annotation : 匹配特定注解
    @annotation(org.springframework.transaction.annotation.Transactional) 匹配 任何带有 @Transactional 注解的方法。

相关

上文描述了用注解方式实现AOP的方法,一下是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
<?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.xsd">

<bean id="arithmeticCalculator" class="com.spring.aop.AtitheticCalculatorImp"></bean>
<!--配置切面的BEan-->
<bean id="loggingAspect"
class="com.spring.aop.impl.LoggingAspect"></bean>
<bean id="vlidationAspect"
class="com.spring.aop.impl.Aspect2"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切点表达式-->
<aop:pointcut id="pointcut" expression="execution(* com.spring.aop.*.*(int,int))"/>
<!--配置切面以及通知-->
<aop:aspect ref="loggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
</aop:aspect>
<aop:aspect ref="vlidationAspect" order="1">
<aop:before method="validationArgs" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>

声明式事务配置:

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
<!-- 配置c3po连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 注入属性值 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/wangyiyun"></property>
<property name="user" value="root"></property>
<property name="password" value="153963"></property>
</bean>

<!-- 第一步:配置事务管理器 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 第二步:配置事务增强 -->
<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
<!-- 做事务操作 -->
<tx:attributes>
<!-- 设置进行事务操作的方法匹配规则 -->
<!-- account开头的所有方法 -->
<!--
propagation:事务传播行为;
isolation:事务隔离级别;
read-only:是否只读;
rollback-for:发生那些异常时回滚
timeout:事务过期时间
-->
<tx:method name="account*" propagation="REQUIRED"
isolation="DEFAULT" read-only="false" rollback-for="" timeout="-1" />
</tx:attributes>
</tx:advice>

<!-- 第三步:配置切面 切面即把增强用在方法的过程 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* cn.itcast.service.OrdersService.*(..))"
id="pointcut1" />
<!-- 切面 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut1" />
</aop:config>
文章目录
  1. 1. 概述
  2. 2. 相关概念
  3. 3. demo
  4. 4. 指示符
  5. 5. 相关
|