本篇内容包括:Spring AOP 概述(AOP 简介、AOP 为什么叫面向切面编程、AOP 主要用来解决的问题 和 AOP 的相关术语)、Spring AOP Demo(xml 方式、注解方式)以及相关知识点(JDK 动态代理和 CGLIB 代理、Spring AOP 和 AspectJ AOP、@Aspect、@Pointcut、@Around 注解)等内容!
AOP(Aspect oriented programming),即面向切面编程,它是一个编程范式,是 OOP(面向对象编程)的一种延续,目的就是提高代码的模块性。
Spring AOP 基于动态代理的方式实现,如果是实现了接口的话就会使用 JDK 动态代理,反之则使用 CGLIB 代理,Spring中 AOP 的应用主要体现在 事务、日志、异常处理等方面,通过在代码的前后做一些增强处理,可以实现对业务逻辑的隔离,提高代码的模块化能力,同时也是解耦。Spring主要提供了 Aspect 切面、JoinPoint 连接点、PointCut 切入点、Advice 增强等实现方式。
切 :指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑
面 :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念
例如:现有三个类,Horse
、Pig
、Dog
,这三个类中都有 eat 和 run 两个方法。
通过 OOP 思想中的继承,我们可以提取出一个 Animal 的父类,然后将 eat 和 run 方法放入父类中,Horse
、Pig
、Dog
通过继承Animal
类即可自动获得 eat()
和 run()
方法。这样将会少些很多重复的代码。
OOP 编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了。这部分重复的代码,一般统称为横切逻辑代码。
横切逻辑代码存在的问题:
AOP 就是用来解决这些问题的:AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离,代码拆分比较容易,难的是如何在不改变原有业务逻辑的情况下,悄无声息的将横向逻辑代码应用到原有的业务逻辑中,达到和原来一样的效果
AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
# 引入依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.22version>
dependency>
# 配置 xml 信息
<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-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="aopTank" class="designpattern.aop.v1.AopTank"/>
<bean id="aopMethod" class="designpattern.aop.v1.AopMethod"/>
<aop:config>
<aop:aspect id="time" ref="aopMethod">
<aop:pointcut id="onmove" expression="execution(public void com.liziheng.demo.api.aop.demo.AopDog.*(..))"/>
<aop:before method="before" pointcut-ref="onmove"/>
<aop:after method="after" pointcut-ref="onmove"/>
aop:aspect>
aop:config>
beans>
# 切入时添加方法
public class AopMethod {
public void before() {
System.out.println("before...");
}
public void after() {
System.out.println("after...");
}
}
# 被切入的类
public class AopDog {
public void eat() {
System.out.println("The dog is eating...");
}
public void drink() {
System.out.println("The dog is drinking water...");
}
}
# 测试
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
AopDog dog = (AopDog) context.getBean("AopDog");
tank.move();
tank.voice();
}
}
# 引入依赖
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.4version>
dependency>
# xml配置同上
<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-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<aop:aspectj-autoproxy/>
<bean id="aopTank" class="designpattern.aop.v1.AopTank"/>
<bean id="aopMethod" class="designpattern.aop.v1.AopMethod"/>
beans>
# 切入时添加方法
public class AopMethod {
@Before("execution(public void com.liziheng.demo.api.aop.demo.AopDog.*(..))")
public void before() {
System.out.println("before...");
}
@After("execution(public void com.liziheng.demo.api.aop.demo.AopDog.*(..))")
public void after() {
System.out.println("after...");
}
}
# 被切入的类 同xml方式
# 测试 同xml方式
JDK 动态代理主要是针对类实现了某个接口,AOP 则会使用 JDK 动态代理。他基于反射的机制实现,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强;
而如果某个类没有实现接口,AOP 则会使用 CGLIB 代理。他的底层原理是基于 asm 第三方框架,通过修改字节码生成成成一个子类,然后重写父类的方法,实现对代码的增强。
Spring AOP 基于动态代理实现,属于运行时增强。
AspectJ 则属于编译时增强,主要有3种方式:
总结下来的话,就是 Spring AOP 只能在运行时织入,不需要单独编译,性能相比 AspectJ 编译织入的方式慢,而 AspectJ 只支持编译前后和类加载时织入,性能更好,功能更加强大。