20.普通AOP开发(XML方式)

 

AOP的目的:对类里的方法进行加强

  • 前置加强:在方法执行之前加入相关代码
  • 后置加强:在方法执行之后加入相关代码
  • 环绕加强:在方法执行之前和之后加入相关代码
  • 异常加强:在目标方法发生异常时加入相关代码

1,JoinPoint(连接点):所谓连接点是指那些被拦截的点,而spring中这些点就是指方法,因为spring只支持方法类型的连接点。

2,PointCut(切入点):所谓切入点就是指我们要对那些JoinPoint进行拦截的定义。

3,Advice(通知/增强):所谓通知/增强,就是指拦截到JoinPoint后需要完成的事情。他分为前置通知/增强,后置通知/增强,异常通知/增强,最终通知/增强,环绕通知/增强(切面要完成的功能);

4,Introduction(引介)(用的比较少):引介是一种特殊的Advice,在不修改代码的前提下,引介可以在运行期为类动态的添加一些方法或Field。

5,Target(目标):代理对象的目标对象(要增强的类)

6,Weaving(织入):把Advice应用到Target的过程

7,Proxy(代理):一个类被AOP注入增强后,就产生了一个结果代理类

8,Aspect(切面):是PointCut和Advice(Introduction)的结合

通知的种类:

  1. 前置通知/增强
  2. 后置通知/增强
  3. 环绕通知/增强
  4. 异常通知/增强
  5. 最终通知/增强

目标类
UserDaoImpl.java

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
package com.course.dao.impl;

import java.sql.SQLException;

/**
* 目标类
* @author Bob
*
*/
public class UserDaoImpl {

public void addUser(String userName) {
System.out.println("Save user, userName = " + userName);
}

public void deleteUser(Integer userId) {
System.out.println("delete user, userId = " + userId);
int[] a = new int[10];
a[10] = 20;
}

public void queryUser(Integer userId) {
System.out.println("query user, user[userId=" + userId + ", userName=Jerry]");
}
}

目标类
TimeConsumption.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.course.experiment;

public class TimeConsumption {

public void circulation() {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum = sum + i;
}
System.out.println("sum = " + sum);
}
}

前置通知
BeforeDMLAdvice.java

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

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class BeforeDMLAdvice implements MethodBeforeAdvice{

/**
* Method arg0: 目标方法
* Object[] arg1:目标方法的参数
* Object arg2:目标对象
*/
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
openTransaction();
}

public void openTransaction() {
System.out.println("open transaction");
}
}

后置通知
AfterDMLAdvice.java

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

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class AfterDMLAdvice implements AfterReturningAdvice {

/**
* Method arg0: 目标方法
* Object[] arg1:目标方法的参数
* Object arg2:目标对象
*/
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
commitTransaction();
}

public void commitTransaction() {
System.out.println("commit transaction");
}

}

环绕通知
TransactionDMLAdvice.java

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
package com.course.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class TransactionDMLAdvice implements MethodInterceptor{

/**
* MethodInvocation: 目标方法执行对象
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
openTransaction();
// 执行目标对象方法
Object proceed = methodInvocation.proceed();
commitTransaction();
return proceed;
}

public void openTransaction() {
System.out.println("open transaction");
}

public void commitTransaction() {
System.out.println("commit transaction");
}

}

异常通知
MyExceptionAdvice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.course.advice;

import org.springframework.aop.ThrowsAdvice;

public class MyExceptionAdvice implements ThrowsAdvice {

/**
* 异常增强方法
* @param throwable
* @throws Exception
*/
public void afterThrowing(Throwable throwable) throws Exception {
System.out.println("产生异常:" + throwable.getMessage());
}
}

计算程序执行时间(环绕通知)

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

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class TimeConsumptionAdvice implements MethodInterceptor {

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {

long startTime = System.currentTimeMillis();

Object proceed = methodInvocation.proceed();

long endTime = System.currentTimeMillis();

System.out.println("time consumption = " + (endTime - startTime) + " ms");

return proceed;
}
}

测试类

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
package com.test;

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

import com.course.dao.impl.UserDaoImpl;
import com.course.experiment.TimeConsumption;

public class TestMain {

public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

UserDaoImpl userDaoImpl = (UserDaoImpl) applicationContext.getBean("myUserDaoImp");

/*
* 前置通知
* 后置通知
* 环绕通知
*/
userDaoImpl.addUser("user1");
// open transaction
// Save user, userName = user1
// commit transaction

/*
* 异常通知
*/
userDaoImpl.deleteUser(2);
// delete user, userId = 2
// 产生异常:Index 10 out of bounds for length 10

/*
* 计算程序执行耗时
*/
TimeConsumption timeConsumption = (TimeConsumption) applicationContext.getBean("myTimeConsumption");
timeConsumption.circulation();
// sum = 4950
// time consumption = 20 ms

}
}

applicationContext.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
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">


<!-- 声明目标对象 -->
<bean id="myUserDaoImp" class="com.course.dao.impl.UserDaoImpl"></bean>
<bean id="myTimeConsumption" class="com.course.experiment.TimeConsumption"></bean>

<!-- 声明通知对象 -->
<bean id="myBeforeDMLAdvice" class="com.course.advice.BeforeDMLAdvice"></bean>
<bean id="myAfterDMLAdvice" class="com.course.advice.AfterDMLAdvice"></bean>
<bean id="myAroundDMLAdvice" class="com.course.advice.TransactionDMLAdvice"></bean>
<bean id="MyExceptionAdvice" class="com.course.advice.MyExceptionAdvice"></bean>
<bean id="myTimeConsumptionAdvice" class="com.course.advice.TimeConsumptionAdvice"></bean>

<!-- 进行AOP配置 -->
<aop:config>
<!-- 声明切面 -->
<!--
com.course.dao.impl.*.*(..): com.course.dao.impl包下的所有类的所有方法的所有参数
-->
<aop:pointcut expression="execution(* com.course.dao.impl.*.*(..))" id="aspect1"/>
<aop:pointcut expression="execution(* com.course.experiment.TimeConsumption.circulation(..))" id="aspect2"/>

<!--
advice-ref: 通知对象
pointcut-ref: 织入到哪一个切面
-->
<!-- 织入前置通知 -->
<aop:advisor advice-ref="myBeforeDMLAdvice" pointcut-ref="aspect1" />

<!-- 织入后置通知 -->
<aop:advisor advice-ref="myAfterDMLAdvice" pointcut-ref="aspect1"/>

<!-- 织入环绕通知 -->
<aop:advisor advice-ref="myAroundDMLAdvice" pointcut-ref="aspect1"/>

<!-- 织入异常通知 -->
<aop:advisor advice-ref="MyExceptionAdvice" pointcut-ref="aspect1"/>

<!-- 计算程序执行耗时(环绕通知) -->
<aop:advisor advice-ref="myTimeConsumptionAdvice" pointcut-ref="aspect2"/>
</aop:config>

</beans>

实现环绕通知的两种方式:

  1. 前置通知+后置通知
  2. 环绕通知