# Aop 简介
- AOP(面向切面编程)是一种编程范式和一种在软件系统中实现关注点分离的技术。
- AOP 通过在特定的切入点处自动执行预定义的操作,将这些功能与核心业务逻辑进行解耦。
- 它允许您将与核心业务逻辑无关的功能(例如日志记录、事务管理、性能监控等)从源代码中分离出来,并以模块化的方式应用于整个应用程序。
- 在 AOP 中,切面是一种模块化单元,它包含了与特定横切关注点相关的通用功能。
- 切面由切点(定义将在何处应用通用功能)和建议(定义在切点处要执行的操作)组成。
- 切点是一个表达式,描述了在应用程序的执行过程中,具体哪些方法或函数将被拦截以应用通用功能。
- 建议定义了在切点处执行的特定操作,例如在方法调用之前或之后执行代码,甚至替换原始的方法调用。
- 在 Java 中,AOP 常常使用 AspectJ 库实现。
- Spring 框架也提供了对 AOP 的支持,并且使用 AspectJ 作为默认的 AOP 框架。
- Spring AOP 使得在应用程序中使用 AOP 更加简单和直观,可以通过使用注解或 XML 配置定义切面和切点,实现通用功能的应用。
# Aop 优点
- 关注点分离:
- AOP 使得您可以将与核心业务逻辑无关的功能从代码中分离出来,使得代码更加干净和易于维护。
- 代码重用:
- AOP 允许您将通用功能定义在一个地方,并在整个应用程序中多次重用。
- 横向关注点:
- AOP 以横向的方式独立于代码的纵向层次结构,因此它可以跨越多个层次结构应用于不同的模块和类。
- 核心逻辑影响较小:
- 引入 AOP 并不会对核心业务逻辑产生太大的影响,只需要在切入点处应用即可。
# 什么是 AOP
AOP
:Aspect Oriented Programming
(面向切面编程),OOP
是面向对象编程,AOP
是在OOP
基础之上的一种更高级的设计思想。OOP
和AOP
之间也存在一些区别,OOP
侧重于对象的提取和封,AOP
侧重于组件方面,组件可以理解成封装了通用功能的组件,组件可以通过配置方式,灵活地切入到某一批目标对象方法上。AOP
用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等。
# 为什么使用 AOP
- Java 是一个面向对象 (OOP)
的编程语言,但它有个弊端就是当需要为多个不具有继承关系的对象引入一个公共行为时,例如日志记录、权限校验、事务管理、统计等功能,只能在每个对象里都引用公共行为,这样做不便于维护,而且有大量重复代码,AOP 的出现弥补了 OOP 的这点不足。 - 例如:
- 动态代理:
# AOP 的体系结构
- AOP 要做的三件事是:
- 在哪里切入
- 什么时候切入
- 切入后做什么事
- 在哪里切入:就是权限校验等非业务操作在哪些业务代码中执行。
- 什么时候切入:就是业务代码执行前还是执行后。
- 切入后做什么事:比如做权限校验,日志记录等等。
Pointcut
:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面),切点分为execution
方式和annotation
方式,前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。Advice
:处理,包括处理时机和处理内容,处理内容就是要做什么事,比如校验权限和记录日志,处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。Aspect
:切面,即Pointcut
和Advice
。JointPoint
:连接点,是程序执行的一个点,例如,一个方法的执行或者一个异常处理,在Spring AOP
中,一个连接点总是代表一个方法执行。Weaving
:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。
# Aop 原理
- 🚀动态代理
- 之前提到 JDK 代理和 Cglib 代理两种动态代理,优秀的 Spring 框架把两种方式在底层都集成了进去,无需担心自己去实现动态生成代理。
- 那么 Spring 是如何生成代理对象的?创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。
- 如果目标对象有实现接口,使用 jdk 代理。如果目标对象没有实现接口,则使用 Cglib 代理。
- 然后从容器获取代理后的对象,在运行期植入 "切面" 类的方法。
- 如果目标类没有实现接口,且 class 为 final 修饰的,则不能进行 Spring AOP 编程。
- 封装了 JDK 代理和 Cglib 代理,当被代理的类实现了接口,则使用 JDK 代理。
- 如果没有实现接口则使用 Cglib 代理,类没有实现接口且使用 final 修饰 不能使用 AOP。
# AOP 能做什么
Spring
声明式事务管理配置Controller
层的参数校验- 使用
Spring AOP
实现MySQL
数据库读写分离案例分析 - 在执行方法前,判断是否具有权限。
- 对部分函数的调用进行日志记录,监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员,信息过滤,页面转发等等功能。
- 什么是事务?
- 有一系列动作组成的一个单元,这些动作要么全部执行,要么全部不执行。
- 例子:张三给李四转账。
- 1. 张三减钱 2. 李四加钱。
- mysql 是支持事务的。
# Aop 使用
- 创建
SpringBoot
项目 - 引入
SpringAop
依赖 pom.xml <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 自定义注解
LogNotes.java import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义 Aop 注解
* @author LightRain
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogNotes {
String value() default "";
}
- Aop 实现类
LogAspect.java import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 日志 Aop 实现类
*
* @author LightRain
*/
@Slf4j
@Aspect//@Aspect:把当前类标识为一个切面供容器读取
@Component// 交由 Spring 容器管理
public class LogAspect {
/**
* @Pointcut:aop 切入点,表示在什么条件下触发
* @annotation:声明以注解的方式来定义切点,注解为 NeedAuth 的包路径
*/
@Pointcut("@annotation(com.example.aop.aop.NeedAuth)")//
public void logPoint() {// 这就是个标志,爱叫啥叫啥,给下面用的
}
/**
* @@Before:前置增强方法的标识,表示在标有注解的方法前执行切面方法
*/
@Before("logPoint()")
public void beforeAop() {
System.out.println("前置通知...");
}
/**
* @After:后置增强方法的标识,表示在标有注解的方法后执行切面方法
*/
@AfterReturning(value = "logPoint()", returning = "returnValue")
public void afterAop(Object returnValue) {
System.out.println("returnValue = " + returnValue);
System.out.println("后置通知...");
}
/**
* @@Around:环绕增强方法的标识,表示在标有注解的方法前后都执行切面方法
*/
@Around(value = "logPoint()")
public Object aroundAop(ProceedingJoinPoint pj) throws Throwable {
try {
log.info("{},开始执行!", pj.getSignature());
System.out.println("环绕通知前...");
// pj.proceed ();// 这个有点东西,几个注解的执行顺序有关
System.out.println("pj.getTarget() = " + pj.getTarget());
System.out.println("pj.getThis() = " + pj.getThis());
System.out.println("环绕通知后...");
System.out.println(pj.getSignature().getName());
Object[] args = pj.getArgs();
for (Object arg : args) {
System.out.println("参数:" + arg.toString());
}
log.info("{},执行完成!", pj.getSignature());
} catch (Throwable ignored) {
}
return pj.proceed();
}
@AfterThrowing(value = "logPoint()", throwing = "e")
public void afterThrowing(Exception e) {
System.err.println(e.getMessage());
System.out.println("AOP处理异常");
Throwable throwable = e.fillInStackTrace();
System.err.println(throwable);
}
}
- Aop 测试,在需要切入 Aop 的方法上添加自定义注解即可执行切入
TestController.java import com.example.aop.aop.NeedAuth;
import com.example.aop.service.TestServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* aop 测试 Controller
* @author LightRain
*/
@RestController
@RequestMapping("/aop")
public class TestController {
@Resource
TestServiceImpl test;
@RequestMapping("/test")
@NeedAuth()// 切入点
public String anoMessage(String value) {
return test.anoMessage(value);
}
}
TestServiceImpl.java /**
* @author LightRain
*/
public interface TestServiceImpl {
String anoMessage(String value);
}
TestService.java /**
* @author LightRain
*/
@Service
public class TestService implements TestServiceImpl{
public String anoMessage(String value){
System.out.println("执行方法needAuth");
return "测试";
}
}