[TOC]
# AOP
AOP 即 `Aspect Oriented Program` 面向切面编程
首先,在面向切面编程的思想里面,把功能分为**核心业务功能**,和**周边功能**。
所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在Spring的面向切面编程AOP思想里,被定义为**切面**
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别**独立进行开发**
然后把切面功能和核心业务功能 "**编织**" 在一起,这就叫AOP
## 步骤 1 : 先运行,看到效果,再学习
先将完整的 spring 项目(向老师要相关资料),配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
## 步骤 2 : 模仿和排错
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较**正确答案** ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,**学习有效果,排错有效率**,可以较为明显地提升学习速度,跨过学习路上的各个槛。
## 步骤 3 : 思路图
1. 功能分两大类,辅助功能和核心业务功能;
2. 辅助功能和核心业务功能**彼此独立**进行开发;
3. 比如登陆功能,即便是没有性能统计和日志输出,也可以正常运行;
4. 如果有需要,就把"日志输出" 功能和 "登陆" 功能 ****编织****在一起,这样登陆的时候,就可以看到日志输出了;
5. 辅助功能,又叫做**切面**,这种能够**选择性的**,**低耦合的**把切面和核心业务功能结合在一起的编程思想,就叫做切面编程。
![](../images/Image009.png)
## 步骤 4 : 准备业务类 ProductService
ProductService
~~~
package com.dodoke.service;
public class ProductService {
public void doSomeService() {
System.out.println("doSomeService");
}
}
~~~
## 步骤 5 : 声明业务对象
在applicationContext.xml中,添加ProductService在xml的配置,即声明业务对象。
~~~
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.dodoke.pojo" />
<bean name="s" class="com.dodoke.service.ProductService"/>
</beans>
~~~
## 步骤 6 : TestSpring
在引入切面之前,调用该业务类
~~~
package com.dodoke.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.dodoke.service.ProductService;
public class TestSpring {
public static void main(String[] args) {
// 读取applicationContext.xml配置文件
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });
ProductService s = (ProductService)context.getBean("s");
s.doSomeService();
}
}
~~~
运行结果:
![](../images/Image008.png)
## 步骤 7 : 准备日志切面 LoggerAspect
该日志切面的功能是 在调用核心功能之前和之后分别打印日志,切面就是原理图中讲的那些辅助功能。
~~~
package com.dodoke.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class LoggerAspect {
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("start log:" + joinPoint.getSignature().getName());
Object object = joinPoint.proceed();
System.out.println("end log:" + joinPoint.getSignature().getName());
return object;
}
}
~~~
> `getSignature()`: 获取切点的签名
> `proceed()` :执行切点本来的业务
## 步骤 8 : applicationContext.xml
~~~
<?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:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.dodoke.pojo" />
<!-- 声明业务对象 -->
<bean name="s" class="com.dodoke.service.ProductService" />
<!-- 声明日志切面 -->
<bean id="loggerAspect" class="com.dodoke.aspect.LoggerAspect" />
<aop:config>
<aop:pointcut expression="execution(* com.dodoke.service.ProductService.*(..))"
id="loggerCutpoint" />
<aop:aspect id="logAspect" ref="loggerAspect">
<aop:around method="log" pointcut-ref="loggerCutpoint" />
</aop:aspect>
</aop:config>
</beans>
~~~
1. 声明日志切面
~~~
<bean id="loggerAspect" class="com.dodoke.aspect.LoggerAspect" />
~~~
2. 结合思路图,配置AOP
指定右边的核心业务功能:
~~~
<aop:pointcut expression="execution(* com.dodoke.service.ProductService.*(..))" id="loggerCutpoint" />
~~~
> 这一句是声明切入点,切入点的 id 叫 loggerCutPoint ,用来标记这个切入点,
> 这个expression表示:满足expression中的方法调用之后,就会去进行切面操作,类似于触发了切面:
> `*` :返回任意类型
> `com.dodoke.service.ProductService.*`:包名以 `com.dodoke.service.ProductService` 开头的类的任意方法
> `(..) `:参数是任意数量和类型
简单说就是,只要`com.dodoke.service`这个包中的ProductService类的任意一个函数被调用,不管你的返回值是什么,都会触发开关,我就会去执行切面,也就是辅助功能,但是辅助功能是什么呢,就是下面两句:
指定左边的辅助功能
~~~
<aop:aspect id="logAspect" ref="loggerAspect">
<aop:around method="log" pointcut-ref="loggerCutpoint" />
</aop:aspect>
~~~
> 这两句是定义了一个切面,上面说只要触发开关,就会去执行切面,就是指的这里的切面,所谓切面,就是一个类中的方法而已。
> id代表这个切面的名字,ref就是指的方法所在的类,method代表的就是方法的名字,`pointcut-ref="loggerCutpoint" `这个就是表示我这个切面是和上面的切点关联起来的(一个切点是可以关联多个切面的,一个切面只能关联一个方法),只要上面的切点被触发,我就会到这里来执行一些辅助功能。
然后通过`aop:config`把业务对象与辅助功能编织在一起。
## 步骤 9 : TestSpring
TestSpring 代码没有发生任何变化,通过配置的方式,把切面和核心业务类编制在了一起。
运行测试,可以发现在编织之后,业务方法运行之前和之后分别会打印日志。
运行结果:
![](../images/Image010.png)
## 步骤 10 : 练习-性能统计切面
参考上述的日志切面,做一个性能统计切面,并编织在业务方法上面
注:在业务方法方法中,做循环,以增加耗时
参考:
核心业务里面就搞个费时的操作。
ProductService:
~~~
package com.dodoke.service;
public class ProductService {
public void doSomeService() {
String str = "";
for (int i = 0; i < 1000; i++) {
str += i;
}
System.out.println("doSomeService1:" + str);
}
}
~~~
LoggerAspect:
~~~
package com.dodoke.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class LoggerAspect {
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("start log:" + joinPoint.getSignature().getName());
// 运行核心业务类之前的时间
long startTime = System.currentTimeMillis();
Object object = joinPoint.proceed();
System.out.println("end log:" + joinPoint.getSignature().getName());
// 运行核心业务类之后的时间
long endTime = System.currentTimeMillis();
// 总运行时间
long time = endTime - startTime;
System.out.println("耗费时间:" + time);
return object;
}
}
~~~