什么是AOP?
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
使用场景:日志记录,性能统计,安全控制,权限管理,事务处理,异常处理,资源池管理
AOP切面编程设计到的一些专业术语:
术语 | 含义 |
---|---|
目标(Target) | 被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
通知(Advice) | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
切面(Aspect) | 切入点(Pointcut)+通知(Advice) |
Weaving(织入) | 将通知应用到目标对象,进而生成代理对象的过程动作 |
Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
AOP切入点表达式
切入点表达式标准格式: 动作关键字(访问修饰符 返回值 包.类/接口名.方法名(参数)异常名)
-
动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
-
访问修饰符:public,private等,可以省略
-
返回值
-
包名
-
类/接口名
-
方法名
-
参数
-
异常名:方法定义中抛出异常,可以省略
实例如下:
package com.qing.dao;
public interface BookDao{
public void update();
}
package com.qing.dao.impl;
public class BookDaoImpl BookDao{
public void update(){
System.out.println("update...")
}
}
描述方式一:执行com.qing.dao包下的BookDao接口中的无参update方法
execution(void com.qing.dao.BookDao.update())
描述方式二:执行com.qing.dao.impl包下的BookDaoImpl类中的无参数update方法
execution(void com.qing.dao.impl.BookDaoImpl.update())
AspectJ定义的五种通知类型
-
Before(前置通知):目标对象的方法调用之前触发
//@Before:前置通知,在原始方法运行之前执行 @Before("pt()") public void before() { System.out.println("before advice ..."); }
-
After (后置通知):目标对象的方法调用之后触发
//@After:后置通知,在原始方法运行之后执行 @After("pt()") public void after() { System.out.println("after advice ..."); }
-
AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
//@AfterReturning:返回后通知,在原始方法执行完毕后运行,且原始方法执行过程中未出现异常现象 @AfterReturning("pt2()") public void afterReturning() { System.out.println("afterReturning advice ..."); }
-
AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
//@AfterThrowing:抛出异常后通知,在原始方法执行过程中出现异常后运行 @AfterThrowing("pt2()") public void afterThrowing() { System.out.println("afterThrowing advice ..."); }
-
Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法。
//@Around:环绕通知,在原始方法运行的前后执行 @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before advice ..."); //表示对原始操作的调用 Object ret = pjp.proceed(); System.out.println("around after advice ..."); return ret; }
@Around注意事项
- 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
- 通知如果未使用ProceedingJoinPoin对原始方法进行调用将跳过原始方法的执行
- 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
- 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
AOP入门案例
案例设定:在接口执行前输出当前系统时间
开发模式:xml or 注解(本案例用注解)
思路分析:
-
导入坐标(pom.xml)
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>
-
制作连接点方法(原始操作、Dao接口、实现类)
定义接口:
package com.qing.dao; public interface BookDao { public void save(); public void update(); }
定义实现类:
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..."); } }
-
制作共性功能(通知类与通知)
定义通知类,制作通知:
package com.qing.aop; public class MyAdvice { //共性功能,输出当前系统时间 public void method(){ System.out.println(System.currentTimeMillis()); } }
-
定义切入点
切入点定义依托一个不具有实际意义的方法进行:
public class MyAdvice { //定义切入点,无实际意义方法 @Pointcut("execution(void com.qing.dao.BookDao.update())") private void pt(){} }
-
绑定切入点与通知关系(切面)
绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
@Component @Aspect public class MyAdvice { @Pointcut("execution(void com.qing.dao.BookDao.update())") private void pt(){} //绑定切入点与通知关系,在接口执行前输出当前系统时间 @Before("pt()") public void method(){ System.*out*.println(System.*currentTimeMillis*()); } }
AOP工作流程
-
Spring容器启动
-
读取所有切面配置中的切入点
-
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
-
获取bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取bean的代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作