1. 什么是AOP
AOP全称是aspect-oriented programing 面向切面编程。用于解决横向关注点的问题,横向关注点是指多个模块或者模块中的多个功能需要共享的功能,如日志记录、事务管理、安全控制等等。即重复性的代码抽象出来,形成可复用的代码模块。
AOP核心术语
1)连接点(join point):指程序执行的某个特定位置,也成为可切入点。这些方法都是连接点,可以切入。
如上图,几个方法就是连接点。
2)切点(pointcut):指实际被切入的那些方法,也就是连接点中,真正被切入的那些点。
如图,这六个方法就是“连接点”,但只有4个红色箭头的方法是“切入点”
3)通知/增强(advice)
指在切入点所执行的相关处理,例如上图中的“安全”、“日志”,就是advice。增强分为五大类,包括before、after、after returning、after throwing、around(方法执行前后)。
4)切面(aspect):由切点+增强组成。
5)织入(weaving):将增强添加到具体切入点的过程,在spring中就是动态代理的实现过程。
6)目标对象(target):指被增强的对象,也就是被切入的方法代码。
举例:数据库操作
2. 动手创建AOP实例
开发思路,分为四个步骤:
- 创建一个服务,其中有一个方法,作为连接点
- 创建一个before advice,就是前置增强
- 在配置文件中配置好连接点、增强,实现调用方法前执行增强逻辑。
- 测试是否生效
2.1 创建连接点
public class ProductService {
public void add() {
System.out.println("添加产品...");
}
}
2.2 创建前置增强
public class LogAspect {
public void beforeMethod() {
System.out.println("执行业务逻辑前,记录日志...");
}
}// aop中的增强就是普通的方法
3.3 配置切面
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="productService" class="com.demo.aop.service.ProductService"/>
<bean id="logAspect" class="com.demo.aop.aspect.LogAspect"/>
// 配置AOP切面
<aop:config>
// 定义aspect切面,关联了前面定义的logAspect这个bean
<aop:aspect id="test" ref="logAspect">
// 定义切点,选择连接点add作为切点
<aop:pointcut id="productServicePointcut" expression="execution(public void com.demo.aop.service.ProductService.add())"/>
// 使用logAspect中的beforeMethod方法作为切点关联的增强
<aop:before method="beforeMethod" pointcut-ref="productServicePointcut"/>
</aop:aspect>
</aop:config>
</beans>
3.4 测试
public class Test {
public static void main(String[] args) {
// 加载配置文件spring.xml,初始化Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 从容器中获取productService这个bean
ProductService productService = context.getBean("productService", ProductService.class);
// 调用add()方法,由于配置了切面,在执行add方法之前,会先执行LogAspect中的before()方法。
productService.add();
}
}
输出:
执行业务逻辑前,记录日志...
添加产品
3. spring注解实现
前面我们做Aspect切面都是使用的配置文件,随着切面的增加,配置信息会越来越多,比较麻烦,后续提高效率就要使用spring AOP注解了,非常简单,下面我们来实践一下:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 组件扫描路径 -->
<context:component-scan base-package="com.demo.aop"/>
<!-- 开启AOP -->
<aop:aspectj-autoproxy/>
</beans>
@Service("transferService")
public class TransferService {
// 转账
public void transfer(String accountFrom, String accountTo, Integer amount) {
if(amount < 1){
throw new NullPointerException("转账金额错误 !!!");
}
System.out.printf("转账业务:账户 %s 向账户 %s 转账 %d 元 \n", accountFrom, accountTo, amount);
}
}
@Aspect
@Component
public class LogAspect2 {
@Pointcut("execution(* com..TransferService.*(..))")
public void pointcutMethod() {
System.out.println("测试会输出什么");
}
// 这个增强用于pointcutMethod这个切入点。
@Before("pointcutMethod()")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("【AOP -- before】业务处理之前,连接点:" +
joinPoint.getSignature().getName());
}
}
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-annotation.xml");
TransferService transferService = context.getBean("transferService", TransferService.class);
transferService.transfer("张三","李四",2000);
}
}
输出:
【AOP -- before】业务处理之前,连接点:transfer
转账业务:账户 张三 向账户 李四 转账 2000 元
4. Aspect顺序性
实际开发环境中,一个连接点可以有多个增强,例如一个方法需要添加安全验证、日志等通用功能,在多个增强之间,可能会有执行顺序的要求,那如何保证多个增强的执行顺序呢?使用@Order注解即可,数字越小,越优先。
@Aspect
@Order(1)
@Component
public class LogAspect2 {
@Pointcut("execution(* com..TransferService.*(..))")
public void pointcutMethod() {
}
// 这个增强用于pointcutMethod这个切入点。
@Before("pointcutMethod()")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("权限验证..." +
joinPoint.getSignature().getName());
}
}
@Aspect
@Order(0)
@Component
public class LogAspect3 {
@Pointcut("execution(* com..TransferService.*(..))")
public void pointcutMethod() {
}
// 这个增强用于pointcutMethod这个切入点。
@Before("pointcutMethod()")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("记录日志..." +
joinPoint.getSignature().getName());
}
}
输出:
记录日志...transfer
权限验证...transfer
转账业务:账户 张三 向账户 李四 转账 2000 元