一、AOP概述
1、简介
AOP(面向切面向切面编程,Aspect-Oriented Programming)是一种编程范式,它关注于将横切关注点(cross-cutting concerns)与业务逻辑分离。横切关注点通常是一些与业务逻辑无关,但又需要应用到多个模块的功能,如日志记录、安全控制、事务管理等。
在传统的 OOP(面向对象编程)中,代码通常通过继承、接口和多态来组织和复用。AOP 通过“切面”(Aspect)来实现功能的模块化,使得这些横切关注点能够单独提取出来,并可以在不修改现有代码的情况下,插入到程序的执行流程中。
用一个例子来更好的理解AOP。
假设你是一家餐厅的主厨(被增强的类),负责完整的做菜流程:
1.接收订单 → 2. 准备食材 → 3. 烹饪 → 4. 摆盘 → 5. 出菜
突然卫生部门要求:
- 每次处理食材前要记录操作人员
- 每个步骤完成后要消毒操作台
- 出菜前要拍照留存
- 每个环节需要计时统计效率
如果采用传统方式OOP,需要在做菜五步的每一步手动插入对应的操作,这样会导致:
- 流程被非核心逻辑污染
- 重复代码遍布各处
- 修改卫生规则需要改动所有步骤
但是我们把做这些工作交个别的部门(切面类)来处理,餐厅新招了两个后厨卫生监督员来完成卫生部门要求的这些工作,监督员的检查就相当于AOP中的通知,主厨被检查的动作就相当于AOP中的切点。
2、优势
1.解耦
AOP 允许将横切关注点(如日志、安全、事务等)从主业务逻辑中分离出来,避免了在每个业务类中重复写这些功能的代码,从而降低了代码耦合度。
2.提高代码复用性
横切关注点被独立成一个切面,可以在多个类中复用,减少了代码冗余。
3.提高代码的可维护性
将横切关注点从业务逻辑中剥离开,简化了业务逻辑的实现,使代码更加清晰和易于理解。
4.增强可拓展性
可以动态地给现有的类和方法增加额外的功能,而无需修改原有代码
3、底层原理
1. 动态代理:
AOP 通过创建目标对象的代理对象来增强目标方法的行为。Spring AOP 默认使用 JDK 动态代理(针对接口)和 CGLIB 代理(针对类)来生成代理对象。
JDK 动态代理:如果目标类实现了接口,则通过反射机制创建一个代理类来增强目标方法的执行。
CGLIB 代理:如果目标类没有实现接口,则通过继承目标类生成一个子类,并对方法进行增强。
2. 字节码增强:
这是 AOP 的一种更底层的实现方式,通过对目标类的字节码进行修改来增强方法的行为。字节码增强常见的工具有 ASM、Javassist 等。
3. 通知的织入(Weaving):
织入是指将切面(Aspect)应用到目标对象的过程。织入可以发生在编译时、类加载时或运行时(Spring AOP 是运行时织入)。
在运行时,Spring 通过代理机制动态地将增强的功能织入到目标方法中。
4、相关术语
切面(Aspect):切面是一个关注点的模块,包含了通知和切点的逻辑。例如,日志切面会包含日志记录的功能,事务切面包含事务管理的功能。
通知(Advice):通知定义了在切点发生时所执行的具体操作。根据执行时机的不同,通知可分为多种类型:
- @Before:方法执行前通知
- @After:方法执行后通知(无论方法是否抛出异常)
- @AfterReturning:方法执行成功后通知
- @AfterThrowing:方法抛出异常时通知
- @Around:环绕通知,可以在方法执行前后加入自定义逻辑
切点(Pointcut):切点定义了哪些方法应该被通知,即哪些方法的执行符合通知的应用条件。切点通常使用表达式来描述目标方法的匹配规则。
切点表达式:
- execution([修饰符]返回值类型包名.类名.方法名(参数))
- 修饰符可以省略不写,不是必须要出现的。
- 返回值类型是不能省略,根据你的方法来编写返回值。可以使用*代替。
- 包名例如:com.tx.demo3.BookDaoImpl
- 首先com是不能省略不写的,但是可以使用*代替
- 中间的包名可以使用*号代替
- 如果想省略中间的包名可以使用“..”
- 类名也可以使用*号代替,也有类似的写法:*DaoImpl
- 方法也可以使用*号代替
- 参数如果是一个参数可以使用*号代替,如果想代表任意参数使用“..”
连接点(Joinpoint):连接点是程序执行过程中的一个特定位置,比如方法调用、字段修改、构造函数调用等。连接点是 AOP 执行的潜在位置。
目标对象(Target Object):目标对象是被代理的对象,即最终执行业务逻辑的对象。它是 AOP 中功能增强的主要对象。
代理(Proxy):代理是指一个对象,它通过代理方式增强目标对象的行为,通常是在目标对象的基础上创建一个新的代理对象,通过这个代理对象来执行增强后的业务逻辑。
织入(Weaving):织入是指将切面应用到目标对象的过程。在 Spring AOP 中,这通常发生在运行时。
引入(Introduction):引入允许在不修改目标对象的情况下,向目标对象添加新的方法或属性。Spring AOP 通过 @DeclareParents 或接口来实现引入。
二、配置文件方式实现AOP
1、创建maven项目,引入坐标依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--AOP联盟-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!--SpringAspects-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--aspectj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
</dependencies>
2、配置Spring的配置文件
maven坐标依赖没引入成功的话,新建xml文件时不会出现 Spring Config 的文件类型提示
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
3、创建接口和实现类
UserService接口
package com.goose.demo1;
public interface UserService {
public void save();
}
UserServiceImpl实现类
package com.goose.demo1;
import org.springframework.stereotype.Component;
/**
* @Author: Goose
* @Description: TODO
* @Date: 2025/5/13 22:16
* @Version: 1.0
*/
@Component
public class UserServiceImpl implements UserService{
@Override
public void save() {
System.out.println("业务层:保存用户........");
}
}
将要增强的目标类配置到Spring.xml文件中
<bean id="userService" class="com.goose.demo1.UserServiceImpl"/>
4、定义切面类
package com.goose.demo1;
/**
* @Author: Goose
* @Description: 定义的切面类
* @Date: 2025/5/13 22:19
* @Version: 1.0
*/
public class MyXmlAspect {
/*
* 通知
* */
public void log(){
/*
* 发送手机短信
* 发送邮件/记录日志/事务管理
* */
System.out.println("增强的方法执行了......");
}
}
将切面类配置到Spring.xml文件中
<bean id="MyXmlAspect" class="com.goose.demo1.MyXmlAspect"/>
5、完成AOP的配置
<!--配置AOP的增强-->
<aop:config>
<!--配置切面=切入点+通知组成-->
<aop:aspect ref="MyXmlAspect">
<!--前置通知:UserServiceImpl的save方法执行前,会增强-->
<aop:after method="log" pointcut="execution(public void com.goose.demo1.UserServiceImpl.save())"/>
</aop:aspect>
</aop:config>
6、test文件夹中进行测试
package com.goose;
import com.goose.demo1.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @Author: Goose
* @Description: TODO
* @Date: 2025/5/13 22:22
* @Version: 1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class Demo1 {
@Autowired
private UserService userService;
/*
* 测试方法
* */
@Test
public void run1(){
userService.save();
}
}
三、注解方式实现AOP
要在spring配置文件中添加bean
1、配置文件中开启自动扫描
1.编写切面类
package com.goose.demo2;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* @Author: Goose
* @Description: TODO
* @Date: 2025/5/13 22:38
* @Version: 1.0
*/
@Component // 交给IOC去处理
@Aspect // 声明是切面类
public class MyAnnoAspect {
/*
* 通知的方法
* */
@Before(value = "execution(public void com.goose.demo2.UserServiceImpl2.save())")
public void log(){
System.out.println("增强的方法执行了......");
}
}
2.开启自动代理
<!--开启自动代理-->
<aop:aspectj-autoproxy/>
3.测试类中测试
package com.goose;
import com.goose.demo1.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @Author: Goose
* @Description: TODO
* @Date: 2025/5/13 22:43
* @Version: 1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class Demo2 {
@Autowired
private UserService userService;
/*
* 测试方法
* */
@Test
public void run2(){
userService.save();
}
}
2、配置类实现AOP
1.配置类
package com.goose.demo2;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @Author: Goose
* @Description: TODO
* @Date: 2025/5/13 22:49
* @Version: 1.0
*/
@Configuration
@ComponentScan(value = "com.goose.demo2")
@EnableAspectJAutoProxy //开启自动代理
public class SpringConfig {}
2.测试类
package com.goose;
import com.goose.demo2.SpringConfig;
import com.goose.demo2.UserServiceImpl2;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @Author: Goose
* @Description: 使用配置类实现AOP
* @Date: 2025/5/13 23:07
* @Version: 1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class Demo3 {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
UserServiceImpl2 bean = applicationContext.getBean(UserServiceImpl2.class);
bean.save();
}
}