[08] AOP基本概念和使用
1、什么是AOP
AOP = Aspect Oriental Programing,即面向切面编程。什么概念,我们看如下的图片:
三个方法中,重复使用了代码A和代码B,典型的场景比如“开启事务,数据处理,提交事务”。这些重复的代码大多是所谓的权限管理、日志登陆、事务管理等必需却又污染着业务逻辑代码的内容,我们自然希望将它们提取出来,还业务逻辑一个清新的世界。
你知道Servlet过滤器,可我们目前对象的粒度已经不是整个方法了,而是更加细化到了方法中的代码片段。你当然可以曲线救国地使用匿名内部类来抽取重复代码,但是它并不怎么优雅,而AOP,就可以通过横向切割的方式来抽取代码,达到我们的目的。
2、Spring AOP
首先来看一下Spring AOP中一些核心组件的概念:
- 切面:封装通用业务逻辑的组件,即我们想要插入的代码内容
- 切入点:指定哪些Bean组件的哪些方法使用切面组件
- 通知:用于指定具体作用的位置,是方法之前或之后等等
- 前置通知(before) - 在目标方法被调用之前调用通知功能
- 后置通知(after) - 在目标方法完成之后调用通知(不论程序是否出现异常),此时不会关心方法的输出是什么
- 返回通知(after-returning) - 在目标方法成功执行之后调用通知
- 异常通知(after-throwing) - 在目标方法抛出异常后调用通知
- 环绕通知(around) - 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
那么在Spring中使用AOP就意味着你需要:
- 目标程序,某个需要被插入通用代码片段的方法
- 切面程序,即通用代码,用来插入方法的那些代码片段(无返回类型,参数类型与通知类型有关)
- 配置文件,用来指定切入点和通知
3、Demo示例和说明
下面就来看一个简单的Spring AOP的Demo
3.1相关配置环境
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
11
1
<dependency>
2
<groupId>org.springframework</groupId>
3
<artifactId>spring-context</artifactId>
4
<version>4.3.16.RELEASE</version>
5
</dependency>
6
7
<dependency>
8
<groupId>org.springframework</groupId>
9
<artifactId>spring-aspects</artifactId>
10
<version>4.3.16.RELEASE</version>
11
</dependency>
3.2 准备一个目标程序
编写某个目标程序,并注册为Spring的Bean组件
public class Target {
public void print(String param) {
System.out.println("I'm a target function and my param is [" + param + "]" );
}
}
7
1
public class Target {
2
3
public void print(String param) {
4
System.out.println("I'm a target function and my param is [" + param + "]" );
5
}
6
7
}
3.3 编写切面程序
编写切面程序,并注册为Spring的Bean组件
- 切面程序必须无返回值,即void
- org.aspectj.lang.JoinPoint 封装了切面方法的参数和对象等,可通过它获取相关内容
- 切面程序的参数除JoinPoint外,还与通知类型有关,如后置通知则可以获取目标程序返回值(需要配置,此处不展开)
public class Section {
public void writeLog(JoinPoint point) {
System.out.println("write logs start");
System.out.println("获取目标函数的参数: " + Arrays.toString(point.getArgs()));
System.out.println("获取目标函数的反射对象: " + point.getSignature());
System.out.println("获取目标函数的所在对象: " + point.getTarget());
System.out.println("write logs end");
}
}
13
1
public class Section {
2
3
public void writeLog(JoinPoint point) {
4
System.out.println("write logs start");
5
6
System.out.println("获取目标函数的参数: " + Arrays.toString(point.getArgs()));
7
System.out.println("获取目标函数的反射对象: " + point.getSignature());
8
System.out.println("获取目标函数的所在对象: " + point.getTarget());
9
10
System.out.println("write logs end");
11
}
12
13
}
3.4 编写AOP配置文件
配置文件applicationContext(注册Bean和AOP配置)
<bean id="target" class="dulk.learn.aop.Target"/>
<bean id="section" class="dulk.learn.aop.Section"/>
<!-- AOP配置的根标签,所有AOP配置都在其内部 -->
<aop:config>
<!-- 配置AOP的切入点,expression为切入点表达式 -->
<aop:pointcut id="targetPointCut" expression="execution(* dulk.learn.aop.Target.print(*))" />
<!-- 配置切面,ref 切面对象 -->
<aop:aspect ref="section">
<!-- 配置通知为前置,method为方法,pointcut-ref作用在哪些切入点 -->
<aop:before method="writeLog" pointcut-ref="targetPointCut"/>
</aop:aspect>
</aop:config>
14
1
<bean id="target" class="dulk.learn.aop.Target"/>
2
<bean id="section" class="dulk.learn.aop.Section"/>
3
4
<!-- AOP配置的根标签,所有AOP配置都在其内部 -->
5
<aop:config>
6
<!-- 配置AOP的切入点,expression为切入点表达式 -->
7
<aop:pointcut id="targetPointCut" expression="execution(* dulk.learn.aop.Target.print(*))" />
8
<!-- 配置切面,ref 切面对象 -->
9
<aop:aspect ref="section">
10
<!-- 配置通知为前置,method为方法,pointcut-ref作用在哪些切入点 -->
11
<aop:before method="writeLog" pointcut-ref="targetPointCut"/>
12
</aop:aspect>
13
14
</aop:config>
3.4.1 切入表达式
如上说明,则该demo中的 execution(* dulk.learn.aop.Target.print(*)) 表示 “Target类中的print方法(不论参数,即包括其重载)”
3.4.2 切面配置说明
<!-- 配置切面,ref 切面对象的beanId -->
<aop:aspect ref="section">
<!-- before表通知为前置,method为插入的方法,pointcut-ref作用在哪些切入点(aop:pointcut id) -->
<aop:before method="writeLog" pointcut-ref="targetPointCut"/>
</aop:aspect>
1
<!-- 配置切面,ref 切面对象的beanId -->
2
<aop:aspect ref="section">
3
<!-- before表通知为前置,method为插入的方法,pointcut-ref作用在哪些切入点(aop:pointcut id) -->
4
<aop:before method="writeLog" pointcut-ref="targetPointCut"/>
5
</aop:aspect>
3.5 测试程序和结果
public class AOPTest {
@Test
public void testAOP(){
ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
Target target = (Target) context.getBean("target");
target.print("balabala");
}
}
10
1
public class AOPTest {
2
3
4
public void testAOP(){
5
ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
6
Target target = (Target) context.getBean("target");
7
target.print("balabala");
8
}
9
10
}