1 Spring IOC
1.1 对于 Spring IOC 理解
容器概念:使用 map 结构来存储对象,在 Spring 中一般存在三级缓存,用来存放 bean 对象。
控制反转:将对象的管理控制权交给容器,例如由Spring框架来创建、管理 bean 对象。
依赖注入(Di):为对象的属性或方法的参数注入值。
3种方法:1. 构造函数注入 2. Setter方法注入 3. 字段注入
1.2 IOC 创建对象的方法
1.2.1 XML配置文件
通过在 XML 配置文件中声明 Bean 的定义和依赖关系,Spring 容器会根据配置信息创建对应 的对象。
<bean id="myBean" class="com.example.MyBean" />
1.2.2 基于注解
2.1 注解
@Component public class MyBean { // ... }
2.2 配置类
@Configuration public class AppConfig { @Bean public MyBean myBean() { return new MyBean(); } }
1.3 Bean 对象的生命周期
1 实例化: bean 在这个阶段,IOC 容器会根据配置信息或注解创建 Bean 的实例。
2 属性赋值:在 Bean 实例化后,IOC 容器会将配置的属性值注入到 Bean 中。这通常涉及依赖注 入(Dependency Injection),通过构造函数、Setter 方法或字段注入来设置 Bean 的属性值。
3 初始化:在属性赋值完成后,可以指定一些特殊的初始化操作。通过实现 InitializingBean 接口或使用 @PostConstruct 注解来定义。可以执行一些预处理、数 据加载或与其他 Bean 的交互等操作。
4 使用:在初始化完成后,Bean 可以正常使用。其他组件或对象可以通过 IOC 容器获取该Bean,并调用它的相关方法。
5 销毁:当容器不再需要 Bean 时,它会触发销毁操作。可以通过实现 DisposableBean 接口或使用 @PreDestroy 注解来定义销毁逻辑。在销毁阶段,可以清理资源、释放连接等操作。
2 Spring AOP
2.1 对于 Spring AOP 理解
面向切面编程,可以通过预编译和运行期间动态代理实现在不修改代码的情况下增加、修改或删除某些功能。
思想就是把类中横切问题点从业务逻辑中分离出来,达到解耦的目的,提高代码的重用性,提高开发效率。一般是非业务代码,例如日志。
2.2 AOP 应用场景
- 日志记录:通过在方法执行前后添加通知,在日志中记录方法的输入参数、返回值和异常信息,方便系统的调试和监控。
- 事务管理:通过在方法执行前后添加事务通知,实现对数据库操作的事务性管理,确保数据的一致性和完整性。
- 安全验证:通过在方法执行前添加权限验证通知,判断用户是否有足够的权限执行该方法,保证系统的安全性。
- 性能监控:通过在方法执行前后添加性能监控通知,记录方法的执行时间和资源消耗等信息,帮助发现系统的瓶颈和优化性能。
- 缓存管理:通过在方法执行前后添加缓存通知,对频繁读取的数据进行缓存,提高系统的响应速度和吞吐量。
- 异常处理:通过在方法执行后添加异常通知,统一处理方法中抛出的异常,避免异常导致系统的不稳定或崩溃。
- 日志审计:通过在方法执行前后添加审计通知,记录敏感操作的日志,用于追踪和监测系统的安全性和合规性。
- 消息通知:通过在方法执行后添加消息通知通知,发送邮件、短信或推送通知,实现业务流程中的消息提醒和通知功能。
2.3 AOP 相关概念
- aspect: 切面,切面有切点和通知组成,即包括横切逻辑的定义也包括连接点的定义
- pointcut: 切点,每个类都拥有多个连接点,可以理解是连接点的集合
- joinpoint: 连接点,程序执行的某个特定位置,如某个方法调用前后等
- weaving: 织入,将增强添加到目标类的具体连接点的过程
- advice: 通知,是织入到目标类连接点上的一段代码,就是增强到什么地方? 增强什么内容?
- target: 目标对象,通知织入的目标类
- aop Proxy: 代理对象,即增强后产生的对象
- Spring AOP 底层实现,是通过JDK动态代理或CGLib代理在运行时期在对象初始化阶段织入代码的。
- 区别:JDK 动态代理基于接口实现 CGLib是基于类的继承实现
2.4 AOP 通知类型
- Before advice:前置通知,即在目标方法调用之前执行。
注意: 即无论方法是否遇到异常都执行 - After returning advice:后置通知,在目标方法执行后执行,
前提是目标方法没有遇到异常,如果有异常则不执行通知 - After throwing advice:异常通知,在目标方法抛出异常时执行,可以获取异常信息
- After finally advice:最终通知,在目标方法执行后执行,无论是否是异常执行
- Around advice:环绕通知,最强大的通知类型,可以控制目标方法的
执行(通过调用ProceedingJoinPoint.proceed()),可以在目标执行全过程中进行执行。
2.5 日志代码实现流程
2.5.1 导入依赖
<spring-boot.version>2.7.14</spring-boot.version><!-- Spring Aop依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>${spring-boot.version}</version> </dependency>
2.5.2 定义切面类 Aspect
package com.example.adminarea.aspect; import com.example.adminarea.annoation.LogAnnotation; import com.example.common.JsonResult.JsonResult; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.Authorization; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Slf4j @Aspect @Component public class LogAspect { // 日志切点 @Pointcut("@annotation(io.swagger.annotations.ApiOperation)") public void logPointCut() { } // 环绕通知 @Around("logPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { // 类名 String className = point.getTarget().getClass().toString(); // 方法名 String name = point.getSignature().getName(); // 通过反射获取注解值,需要强转 MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); ApiOperation annotation = method.getAnnotation(ApiOperation.class); String value = annotation.value(); /*if (annotation != null) { String value = annotation.value(); System.out.println("类名 = " + className); System.out.println("方法名 = " + name); System.out.println("注解值 = " + value); System.out.println("调用的方法的返回值 = " + point.proceed()); // 对返回值进行适当的类型转换,并访问相应的字段 JsonResult result = (JsonResult) point.proceed(); String message = result.getMessage(); System.out.println("返回值的message字段值 = " + message); }*/ log.info("[操作日志]: 执行了"+name+"方法: "+value); long start = System.currentTimeMillis(); Object proceed = point.proceed(); long end = System.currentTimeMillis(); log.info("[方法耗时]: 执行了"+name+"方法: "+(end-start)); return proceed; } }
2.5.3 定义切点 PointCut
2.5.3.1 @Pointcut 注解使用
@Pointcut 注解用于定义切入点表达式,来标识在何处应用切面逻辑。
- @Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
- @Pointcut("execution(* com.example.service.*.*(..))")
// 日志切点 @Pointcut("@annotation(io.swagger.annotations.ApiOperation)") public void logPointCut() { }
2.5.4 定义通知 Advice
// 环绕通知 @Around("logPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { // 类名 String className = point.getTarget().getClass().toString(); // 方法名 String name = point.getSignature().getName(); // 通过反射获取注解值,需要强转 MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); ApiOperation annotation = method.getAnnotation(ApiOperation.class); String value = annotation.value(); /*if (annotation != null) { String value = annotation.value(); System.out.println("类名 = " + className); System.out.println("方法名 = " + name); System.out.println("注解值 = " + value); System.out.println("调用的方法的返回值 = " + point.proceed()); // 对返回值进行适当的类型转换,并访问相应的字段 JsonResult result = (JsonResult) point.proceed(); String message = result.getMessage(); System.out.println("返回值的message字段值 = " + message); }*/ log.info("[操作日志]: 执行了"+name+"方法: "+value); long start = System.currentTimeMillis(); Object proceed = point.proceed(); long end = System.currentTimeMillis(); log.info("[方法耗时]: 执行了"+name+"方法: "+(end-start)); return proceed; }