精品专题:
01.《C语言从不挂科到高绩点》课程详细笔记
https://blog.csdn.net/yueyehuguang/category_12753294.html?spm=1001.2014.3001.5482
02. 《SpringBoot详细教程》课程详细笔记
https://blog.csdn.net/yueyehuguang/category_12789841.html?spm=1001.2014.3001.5482
03.《SpringBoot电脑商城项目》课程详细笔记
https://blog.csdn.net/yueyehuguang/category_12752883.html?spm=1001.2014.3001.5482
04.《VUE3.0 核心教程》课程详细笔记
https://blog.csdn.net/yueyehuguang/category_12769996.html?spm=1001.2014.3001.5482
05. 《SSM详细教程》课程详细笔记
https://blog.csdn.net/yueyehuguang/category_12806942.html?spm=1001.2014.3001.5482
================================
|| 持续分享系列教程,关注一下不迷路 ||
|| 视频教程:墨轩大楼 ||
================================
📚 AOP 概念及优点
AOP为Aspect Oriented Programming的缩写,被称为面向切面编程。
AOP 主要用于处理共通逻辑,例如:记录日志、性能统计、安全控制、事务处理、异常处理等等。AOP可以将这些共通的逻辑从普通业务逻辑代码中分离出来,这样在日后修改这些逻辑的时候,就不会影响普通业务逻辑的代码。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。
AOP 、OOP在名字上虽然非常类似,但却是面向不同领域的两种设计思想。OOP面向对象编程,针对业务处理过程的实体及其属性和行为进行抽象,以获得更加清晰高效的逻辑单元划分。AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
AOP 需要以 OOP为前提和基础。
🌾 什么是方面
面向切面编程,我们首先要知道的一个概念就是方面,也就是把什么东西给隔离出来。方面是指封装处理共通业务的组件,该组件被作用到其他目标组件方法上。
🌾 什么是目标
目标是指被一个或多个方面所作用的对象。
🌾 什么是切入点
切入点是用于指定哪些组件和方法使用方面功能,在Spring中利用一个表达式指定切入目标。
Spring提供了以下常用的切入点表达式:
- 方法限定表达式
execution(修饰符?返回类型 方法名(参数) throws 异常类型?)
- 类型限定表达式
within(包名.类型)
- Bean 名称限定表达式
bean("Bean的id或name属性值")
🌾 什么是通知
通知是用于指定方面组件和目标组件作用的时机,例如方面功能在目标方法之前或之后执行等时机。
Spring框架提供以下几种类型的通知:
- 前置通知:先执行方面功能在执行目标功能
- 后置通知:先执行目标功能再执行方面功能(目标无异常才执行方面)
- 最终通知:先执行目标功能再执行方面功能(目标有无异常都执行方面)
- 异常通知:先执行目标,抛出后执行方面
- 环绕通知:先执行方面前置部分,然后执行目标,最后再执行方面后置部分。
Spring框架提供5种通知,可以按照下面的try-catch-finally结构理解。
try{
// 前置通知--执行方面
// 环绕通知--前置部分
// 执行目标组件方法
// 环绕通知--后置部分
// 后置通知--执行方面
}catch{
// 异常通知--执行方面
}finally{
// 最终通知--执行方面
}
🌾 AOP 实现原理
Spring AOP 实现主要是基于动态代理技术。当Spring采用AOP配置后,Spring容器返回的目标对象,实质上是Spring利用动态代理技术生成的一个代理类型。代理类重写了原目标组件方法的功能,在代理类种调用方面对象功能和目标对象功能。
Spring框架采用了两种动态代理实现:
- 利用cglib工具包:目标没有接口时采用此方法,代理类是利用继承方法生成一个目标子类。
- 利用JDK Proxy API:目标有接口时采用此方法,代理类是采用实现目标接口方法生成一个类。
📚 AOP 开发案例
🌾 AOP 前置通知案例
👉 需求:使用Spring AOP 前置通知,在访问Controller中每个方法前,记录用户的操作日志。
👉 步骤:
- 创建方面组件
- 声明方面组件
- 将方面组件作用到目标组件上
🍒 导入依赖
我们基于前面SpringMVC的基础上去添加AOP功能,所以在前面SpringMVC的环境基础上我们需要追加AOP的依赖。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
🍒 创建Controller
创建一个AOPTestContrller,模拟查询用户数据的Controller,代码如下:
@Controller
public class AOPTestController {
@RequestMapping("/find")
@ResponseBody
public String findUser(){
// 模拟查询用户数据
System.out.println("--》 查询用户数据");
return "查询了用户数据";
}
}
🍒 创建方面组件
创建方面组件OperateLogger,并在该类中创建记录用户操作日志的方法,代码如下:
package com.moxuan.mvc_study.config;
import org.aspectj.lang.ProceedingJoinPoint;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 用于记录日志的方面组件,演示Spring AOP 的各种通知类型
*/
public class OperateLogger {
/**
* 前置通知、后置通知、最终通知使用的方法
*/
public void logUser(JoinPoint p){
// 目标组件的类名
String className = p.getTarget().getClass().getName();
// 调用的方法名
String method = p.getSignature().getName();
// 当前系统时间
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
// 拼日志信息
String msg = "--> 用户在"+time+",执行了"+className+"."+method+"()";
// 记录日志
System.out.println(msg);
}
}
🍒 声明方面组件
在springmvc.xml中,声明该方面组件,关键代码如下:
<bean id="operateLogger" class="com.moxuan.mvc_study.config.OperateLogger"></bean>
🍒 将方面组件作用到目标组件上
在springmvc.xml中,将声明的方面组件作用到controller包下面所有类的所有方法上,关键代码如下:
<aop:config>
<aop:aspect ref="operateLogger">
<!--配置方面组件,作用到的目标方法,
pointcut 方面组件的切入点
-->
<aop:before method="logUser"
pointcut="within(com.moxuan.mvc_study.controller..*)" />
</aop:aspect>
</aop:config>
🍒 测试效果
发送请求:http://localhost:8080/find
可以看到,当配置<aop:before> 前置通知后,方面组件会在执行目标组件的方法时自动触发执行。
🌾 AOP 环绕通知案例
🍒 创建方面组件
依赖和控制器我们延用前置通知案例中的,我们来修改一下方面组件:
public Object logUserRound(ProceedingJoinPoint p) throws Throwable{
// 目标组件的类名
String className = p.getTarget().getClass().getName();
// 调用的方法名
String method = p.getSignature().getName();
// 当前系统时间
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
// 拼日志信息
String msg = "--> 用户在"+time+",执行了"+className+"."+method+"()";
// 记录日志
System.out.println(msg);
// 执行目标组件的方法
Object obj = p.proceed();
//在调用目标组件业务方法后也可以做一些业务处理
System.out.println("---> 已经执行完毕了组件业务了....");
return obj;
}
🍒 配置环绕通知
组件声明我们前面已经做过了,这里我们直接配置一下环绕通知:
<aop:config>
<aop:aspect ref="operateLogger">
<!--配置方面组件,作用到的目标方法,
pointcut 方面组件的切入点
-->
<aop:around method="logUserRound"
pointcut="within(com.moxuan.mvc_study.controller..*)"/>
</aop:aspect>
</aop:config>
🍒 测试效果
请求地址:http://localhost:8080/find
可以看到,方面组件中的前置部分会在方法执行前执行,方法执行完毕之后执行后置部分。
🌾 AOP 异常通知案例
需求:使用AOP异常通知,在每个Controller的方法发生异常时,记录异常日志。
🍒 编写方面组件
/**
* 异常通知使用方法
* @param e
*/
public void logException(Exception e){
StackTraceElement[] elements = e.getStackTrace();
// 将异常信息记录
System.out.println("--》"+elements[0].toString());
}
🍒 配置异常通知
将异常通知方面组件作用到目标组件上
<aop:config>
<aop:aspect ref="operateLogger">
<aop:after-throwing method="logException" throwing="e"
pointcut="within(com.moxuan.mvc_study.controller..*)"/>
</aop:aspect>
</aop:config>
🍒 编写目标组件
@RequestMapping("/find")
@ResponseBody
public String findUser(){
// 模拟查询用户数据
System.out.println("目标组件:--》 查询用户数据");
// 制造一个异常,便于测试异常通知
Integer.valueOf("abc");
return "查询了用户数据";
}
🍒 测试效果
发送请求:http://localhost:8080/find
🌾 AOP 注解使用案例
👉需求: 使用Spring AOP 注解替代XML配置,重构上面三个案例
👉方案:
- @Aspect : 用于声明方面组件
- @Before:用于声明前置通知
- @AfterReturning:用于声明后置通知
- @After:用于声明最终通知
- @Around:用于声明环绕通知
- @AfterThrowing:用于声明异常通知
🍒 开启AOP注解扫描
在springmvc.xml中,去掉方面组件声明以及作用的xml配置,并开启AOP注解扫描,关键代码如下:
<!-- 开启AOP注解扫描-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
🍒 使用注解声明方面组件
在OperateLogger中使用@Aspect注解声明方面组件,并分别用@Before、@Around、@AfterThrowing注解声明三个方法,将方面组件作用到目标组件上,代码如下:
package com.moxuan.mvc_study.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 用于记录日志的方面组件,演示Spring AOP 的各种通知类型
*/
@Component
@Aspect
public class OperateLogger {
/**
* 前置通知、后置通知、最终通知使用的方法
*/
@Before("within(com.moxuan.mvc_study.controller..*)")
public void logUser(JoinPoint p){
System.out.println("^^^^^进入到了前置通知^^^^^^");
// 目标组件的类名
String className = p.getTarget().getClass().getName();
// 调用的方法名
String method = p.getSignature().getName();
// 当前系统时间
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
// 拼日志信息
String msg = "--> 用户在"+time+",执行了"+className+"."+method+"()";
// 记录日志
System.out.println(msg);
System.out.println("^^^^^前置通知结束^^^^^^");
}
@Around("within(com.moxuan.mvc_study.controller..*)")
public Object logUserRound(ProceedingJoinPoint p) throws Throwable{
System.out.println("^^^^^进入环绕通知^^^^^^");
// 目标组件的类名
String className = p.getTarget().getClass().getName();
// 调用的方法名
String method = p.getSignature().getName();
// 当前系统时间
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
// 拼日志信息
String msg = "--> 用户在"+time+",执行了"+className+"."+method+"()";
// 记录日志
System.out.println(msg);
// 执行目标组件的方法
Object obj = p.proceed();
//在调用目标组件业务方法后也可以做一些业务处理
System.out.println("---> 已经执行完毕了组件业务了....");
System.out.println("^^^^^环绕通知结束^^^^^^");
return obj;
}
/**
* 异常通知使用方法
* @param e
*/
@AfterThrowing(pointcut = "within(com.moxuan.mvc_study.controller..*)",throwing ="e")
public void logException(Exception e){
System.out.println("^^^^^进入异常通知^^^^^^");
StackTraceElement[] elements = e.getStackTrace();
// 将异常信息记录
System.out.println("--》"+elements[0].toString());
System.out.println("^^^^^异常通知结束^^^^^^");
}
}
🍒 测试效果
请求路径:http://localhost:8080/find
从结果可以看到,当发生异常之后,环绕通知后置部分将不会执行。