AOP
§ AOP基础概念
AOP作用
AOP(Aspect Oriented Programming):面向切面编程,一种编程范式,指导开发者如何组织程序结构
作用:在不惊动原始设计的基础上为其进行功能增强
在不惊动一下代码情况下,让
update
和delete
也和save
一样执行一万次
public class BookDaoImpl implements BookDao {
public void save() {
// 记录程序开始时间
Long startTime = System.currentTimeMillis();
// 业务执行一万次
for (int i = 0; i < 10000; i++) {
System.out.println("book dao save");
}
// 记录程序结束是按
Long endTime = System.currentTimeMillis();
// 计算时间差
Long totalTime = endTime - startTime;
// 输出信息
System.out.println("执行消耗时间: " + totalTime+ "ms");
}
public void update() {
System.out.println("book dao update");
}
public void delete() {
System.out.println("bookDao delete");
}
}
AOP核心概念
将上述代码中
save
中的非业务代码抽取
- 连接点:程序执行过程中的任意位置,粒度为执行方法,抛出异常、设置变量等等
- 原始方法
save
、update
、delete
- 切入点:匹配连接点的式子
- 可以只描述一个方法,也可以是多个方法
- 需要添加额外功能的方法如
update
- 通知:抽出来的共性功能
method
,写在通知类中- 切面:将通知和切入点绑定,两者的对应关系
public void method() {
Long startTime = System.currentTimeMillis();
// 业务执行一万次
for (int i = 0; i < 10000; i++) {
// 业务代码
}
// 记录程序结束是按
Long endTime = System.currentTimeMillis();
// 计算时间差
Long totalTime = endTime - startTime;
// 输出信息
System.out.println("执行消耗时间: " + totalTime+ "ms");
}
§ AOP入门案例
导入AOP坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
连接点方法
原始操作,Dao接口与实现类
public interface BookDao {
void save();
void update();
}
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save");
}
public void update() {
System.out.println("book dao update");
}
}
共性功能
通知类与通知
新建包aop,在里面新建类
MyAdvice
使用
@Component
指定其被Ioc管理使用
@Aspect
指定他为切面在配置类中添加注解
@EnableAspectJAutoProxy
告诉Spring该系统使用注解定义切面
// 通知类
@Component
@Aspect
public class MyAdvice {
// 切入点
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt() {}
// 切面
@Before("pt()")
// 通知
public void method() {
System.out.println(System.currentTimeMillis());
}
}
定义切入点
切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际逻辑如
void pt()
使用
@Pointcut("execution(切入点)")
注解指定切入点
绑定切入点与通知关系(切面)
使用注解
@Before
绑定切入点与通知
§ AOP工作流程
-
spring容器启动
-
读取所有切面配置中的切入点(即在切面使用了的,下例中的ptx切点不会被读取)
public class MyAdvice { @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void ptx() {} @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt() {} @Before("pt()") public void method() { System.out.println(System.currentTimeMillis()); } }
-
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建初始对象
- 匹配成功,创建原始对象(目标对象)的代理对象
-
获取bean的执行方法
- 获取bean,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强类容,完成操作
§ AOP切入点表达式
语法格式
- 切入点:要进行增强的方法
- 切入点表达式:要进行增强的方法的描述
描述方式一:执行com.itheima.dao
包下的BookDao
接口的无参数update
方法
execution(void com.itheima.dao.BookDao.update())
描述方式二:执行com.itheima.dao.Impl
包下的BookDaoImpl
接口的无参数update
方法
execution(void com.itheima.dao.Impl.BookDaoImpl.update())
上述两种描述方式效果一样
标准格式execution(public void com.itheima.dao.BookDao.update())
- 动作关键词:描述切入点的动作行为,如
execution
表示执行到指定切入点- 访问修饰符:
public
、private
等,可以省略- 返回值
- 包名
- 类||接口名
- 方法名
- 参数
- 异常名:可以省略
通配符
-
*
:单个任意符号,可以独立出现,也可以作为前缀或后缀的匹配符出现execution(public * com.itheima.*.UserService.find* (*))
匹配com.itheima包下任意包中的UserService类或接口中所有find开头带有一个参数的方法
-
..
:多个让连续的任意符号,可以独立出现,常用于简化包名或参数的书写execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法(参数0或多个)
-
+
:专用于匹配子类类型execution(* *..*Service+.*(..))
匹配所有包下service的实现类的所有方法
书写技巧
§ AOP通知类型
通知类型有以下五类
前置通知
后置通知
环绕通知(重点)
- 通知方法第一个参数必须定义一个固定类型
ProceedingJoinPoint
的参数- 如果没有定义,原始操作不会执行,可以用来隔离原始操作,如权限校验
- 该参数进行原始调用,需要抛出异常,防止原始调用的异常无法获取
- 有返回值最后需要扔出返回值
返回后通知
在后置通知前运行
切入方法抛异常没有返回,不会运行
抛出异常后通知
- 在后置通知前运行
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save");
}
public void update() {
System.out.println("book dao update");
}
@Override
public int select() {
return 100;
}
}
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt() {}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2() {}
// 前置
@Before("pt()")
public void before() {
System.out.println("before");
}
// 后置
@After("pt()")
public void after() {
System.out.println("after");
}
// 环绕
@Around("pt()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before");
pjp.proceed();
System.out.println("around after");
}
// 原始方法有返回值情况
@Around("pt2()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before");
Integer res = pjp.proceed();
System.out.println("around after");
return res + 100;
}
@AfterReturning("pt2()")
public void afterReturning() {
System.out.println("afterReturning");
}
@AfterThrowing("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing");
}
}
§ AOP获取数据
public interface BookDao {
String findName(int id);
}
@Repository
public class BookDaoImpl implements BookDao {
@Override
public String findName(int id) {
System.out.println("id" + id);
return "ahh";
}
}
获取切入点方法的参数
JoinPoint
:适用于前置、后置、返回后、抛出异常后通知ProceedJointPoint
:适用于环绕通知
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(String com.itheima.dao.BookDao.findName(int))")
private void pt3(){}
@Before("pt3()")
public void before2(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
}
@Around("pt3()")
public Object around3(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
// proceed可以传入参数,即可以修改参数来执行
args[0] = 666
Object ret = pjp.proceed(args);
return ret;
}
}
获取切入点方法返回值
- 返回后通知
- 使用注解中的
returning
属性- 有JoinPoint时,JoinPoint必须在第一位参数
- 环绕通知
@AfterReturning(value = "pt3()", returning = "ret")
public void afterReturning3(Object ret) {
System.out.println(ret);
}
获取切入点方法运行异常信息
- 抛出异常后通知
- 同上,使用注解中
throwing
属性- 环绕通知
- try-catch