石家庄企业建站哪家好wordpress游戏小程序
- 作者: 五速梦信息网
- 时间: 2026年03月21日 08:47
当前位置: 首页 > news >正文
石家庄企业建站哪家好,wordpress游戏小程序,辽宁鞍山刚刚发布,seo点击工具帮你火21星热情SpringBoot核心框架之AOP详解
一、AOP基础
1.1 AOP概述
AOP#xff1a;Aspect Oriented Programming#xff08;面向切面编程#xff0c;面向方面编程#xff09;#xff0c;其实就是面向特定方法编程。 场景#xff1a;项目部分功能运行较慢#xff0c;定位执行耗时…SpringBoot核心框架之AOP详解
一、AOP基础
1.1 AOP概述
AOPAspect Oriented Programming面向切面编程面向方面编程其实就是面向特定方法编程。 场景项目部分功能运行较慢定位执行耗时较长的业务方法此时就需要统计每一个业务的执行耗时。思路给每个方法在开始前写一个开始计时的逻辑在方法结束后写一个计时结束的逻辑然后相减得到运行时间。
思路是没问题的但是有个问题一个项目是有很多方法的如果挨个增加逻辑代码会相当繁琐造成代码的臃肿所以可以使用AOP编程将计时提出成一个这样的模板 获取方法运行开始时间运行原始方法获取方法运行结束时间计算执行耗时 原始方法就是我们需要计算时间的方法并且可以对原始方法进行增强其实这个技术就是用到了我们在Java基础部分学习的动态代理技术。
实现动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术旨在管理bean对象的过程中主要是通过底层的动态代理机制对特点的方法进行编程。
1.2 AOP快速入门
统计各个业务层方法执行耗时
导入依赖在pom.xml中导入AOP的依赖。
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId
/dependency编写AOP程序针对于特定方法根据业务需要进行编程。
Slf4j // 日志
Component // 将当前类交给spring管理
Aspect // 声明这是一个AOP类
public class TimeAspect {Around(execution(* com.example.service..(..)))// Around表示这是一个环绕通知。// execution(* com.example.service..(..))切入点表达式它定义了哪些方法会被这个环绕通知所拦截。这个后面会详细讲解。// execution(* …)表示拦截执行的方法。// * com.example.service..(..)表示拦截 com.example.service 包下所有类的所有方法* 表示任意字符的通配符。// ..表示方法可以有任意数量和类型的参数。public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {// ProceedingJoinPoint是 Spring AOP 中的一个接口在使用环绕通知时需要// 它继承自 JoinPoint 接口并添加了 proceed() 方法。// 这个方法是 AOP 代理链执行的关键部分它允许你在切面中执行自定义逻辑后继续执行原始方法。// 1. 记录开始时间long start System.currentTimeMillis();// 2. 调用原始方法Object result joinPoint.proceed(); // 执行被通知的方法。如果不调用 proceed()被通知的方法将不会执行。// 3. 记录结束时间计算耗时long end System.currentTimeMillis();// getSignature()返回当前连接点的签名。log.info(joinPoint.getSignature()方法执行耗时{}ms,end - start);return result;}
}查看结果
这样我们就完成了一个AOP的小例子但是AOP的功能远不能这些他还有更多的实用的功能。比如记录操作日志可以记录谁什么时间操作了什么方法传了什么参数返回值是什么都可以很方便的实现。还有比如权限控制事务管理等等。
我们来总结一下AOP的优势
代码无侵入减少重复代码提高开发效率维护方便
1.3. AOP核心概念
连接点JoinPoint可以被连接点控制的方法暗含方法执行时的信息。 在此例中就是需要被计算耗时的业务方法。 通知Advice指那些重复的逻辑也就是共性功能最终体现为一个方法。在此例中就是计算耗时的逻辑代码。 切入点PointCut匹配连接点的条件通知仅会在切入点方法执行时被应用。在此例中就是com.example.service 包下所有类的所有方法。 切面Aspect描述通知与切入点的对应关系通知切入点。在此例中就是TimeAspect方法。 目标对象Target通知所应用的对象。在此例中就是通知com.example.service 包下所有类的所有方法。
1.4. AOP的执行流程
因为SpringAOP是基于动态代理实现的所有在方法运行时就会先为目标对象基于动态代理生成一个代理对象为什么说AOP可以增强方法就是因为有一个代理方法然后在AOP执行时Spring就会将通知添加到代理对象的方法前面也就是记录开始时间的那个逻辑代码然后调用原始方法也就是需要计时的那个方法此时代理对象已经把原始方法添加到代理对象里面了然后执行调用原始方法下面的代码在此例中就是计算耗时的那部分AOP会把这部分代码添加到代理对象的执行方法的下面这样代理对象就完成了对目标方法的增强也就是添加了计时功能最后在程序运行时自动注入的也就不是原来的对象而是代理对象了不过这些都是AOP自动完成我们只需要编写AOP代码即可。
二、AOP进阶
2.1. AOP支持的通知类型
通知类型
环绕通知Around Advice 重点
使用 Around 注解来定义。包围目标方法的执行可以在方法执行前后执行自定义逻辑并且可以控制目标方法的执行。通过 ProceedingJoinPoint 参数的 proceed() 方法来决定是否执行目标方法。
前置通知Before Advice
使用 Before 注解来定义。在目标方法执行之前执行无论方法是否抛出异常都会执行。不能阻止目标方法的执行。
后置通知After Advice 也叫最终通知
使用 After 注解来定义。在目标方法执行之后执行无论方法是否抛出异常都会执行。通常用于资源清理工作返回通知After Returning Advice 了解使用 AfterReturning 注解来定义。在目标方法成功执行之后执行即没有抛出异常时执行。可以获取方法的返回值。
异常通知After Advice了解
使用 AfterThrowing 注解来定义。在目标方法抛出异常后执行。可以获取抛出的异常对象。
注意事项
环绕通知需要自己调用joinPoint.proceed()来让原始方法执行其他通知则不需要。环绕通知的返回值必须是Object,来接受原始方法的返回值。
Slf4j
Component
Aspect
public class MyAspect {// 因为示例中的切入点都是一样的所以不用写多次切入表达式创建一个方法即可。// 此方法也可在其他AOP需要切入点的地方使用。Pointcut(execution(* com.example.service.impl.DeptServiceImpl.(..)))public void pt(){}// 前置通知Before(pt())public void Before(){log.info(before …);}// 环绕通知Around(pt())public Object Around(ProceedingJoinPoint joinPoint) throws Throwable {log.info(around after …);// 调用原始方法Object proceed joinPoint.proceed();log.info(around after …);return proceed;}// 后置通知After(pt())public void After(){log.info(after …);}// 返回通知AfterReturning(pt())public void Returning(){log.info(returning …);}// 异常通知AfterThrowing(pt())public void Throwing(){log.info(throwing …);}
}2.2. 多个通知之间的执行顺序
当有多个切面的切入点都匹配到了目标方法目标方法运行时多个通知方法都会执行。那么顺序是怎么的呢 我们先创建三个AOP程序分别给他们创建一个前置通知和后置通知然后启动程序观察他们的输出情况。
// MyAspect2
Slf4j
Component
Aspect
public class MyAspect2 {Before(execution( com.example.service.impl.DeptServiceImpl.(..)))public void befor(){log.info(befor2 …);}After(execution( com.example.service.impl.DeptServiceImpl.(..)))public void after(){log.info(after2 …);}
}
// MyAspect3
Slf4j
Component
Aspect
public class MyAspect3 {Before(execution( com.example.service.impl.DeptServiceImpl.(..)))public void befor(){log.info(befor3 …);}After(execution( com.example.service.impl.DeptServiceImpl.(..)))public void after(){log.info(after3 …);}
}
// MyAspect4
Slf4j
Component
Aspect
public class MyAspect4 {Before(execution( com.example.service.impl.DeptServiceImpl.(..)))public void befor(){log.info(befor4 …);}After(execution( com.example.service.impl.DeptServiceImpl.(..)))public void after(){log.info(after4 …);}
}// 输出结果com.example.aop.MyAspect2 : befor2 …com.example.aop.MyAspect3 : befor3 …com.example.aop.MyAspect4 : befor4 …com.example.aop.MyAspect4 : after4 …com.example.aop.MyAspect3 : after3 …com.example.aop.MyAspect2 : after2 …// 然后我们把MyAspect2改成MyAspect5但输出内容不变我们来看一下输出结果com.example.aop.MyAspect3 : befor3 …com.example.aop.MyAspect4 : befor4 …com.example.aop.MyAspect5 : befor2 …com.example.aop.MyAspect5 : after2 …com.example.aop.MyAspect4 : after4 …com.example.aop.MyAspect3 : after3 …2.2.1 默认情况
执行顺序是和类名有关系的对于目标方法前的通知字母越靠前的越先执行目标方法后的通知则相反字母越靠前的越晚执行这和Filter拦截器的规则是一样的。
2.2.2 也可以使用注解的方式指定顺序。使用Order(数字)加在切面类上来控制顺序。
目标方法前的通知数字小的先执行。 目标方法后的通知数字小的后执行。
Slf4j
Component
AspectOrder(10)public class MyAspect3 {…
}2.3. 切入点表达式
切入点表达式描述切入点方法的一种表达式。 作用主要决定项目中哪些方法需要加入通知。 常见形式
execution(…)根据方法的签名来匹配。annotation根据注解匹配。
2.3.1 execution(…)
execution主要是通过方法的返回值类名包名方法名方法参数等信息来匹配语法为 execution(访问修饰符 返回值 包名.类名.?方法名(方法参数) throws 异常)
其中带 ? 的表示可以省略的部分
访问修饰符可省略比如public private …包名.类名可省略 但不推荐throws 异常可省略 注意是方法上声明可抛出的异常不是实际抛出的异常
// 完整的写法
Before(execution(public void com.example.service.impl.DeptServiceImpl.add(java.lang.Integer)))
public void befor(){…
}可以使用通配符描述切入点
单个独立的任意符号可以通配任意返回值包括包名类名方法名任意一个参数也可以通配包类方法名的一部分。
After(execution( com..service..add()))多个连续的任意符号可以通配任意层级的包或任意类型任意个数的参数。
After(execution(* com.example..DeptService.(..)))根据业务的需要也可以使用 且或||非来组合切入点表达式。
After(execution( com.example..DeptService.(..)) || execution( com.example.service.DeptService.(..)))2.3.2 annotation用于匹配标识有特定注解的方法
语法annotation(注解的全类名)
先新建一个注解
Retention(RetentionPolicy.RUNTIME) // 用来描述有效时间RUNTIMW在运行时有效
Target(ElementType.METHOD) // 用来说明这个注解可以运行在哪里 METHOD方法上
public interface MyLog {
}在目标方法上添加注解
MyLog
Override
public void delete(Integer id) {deptMapper.delect(id); // 根据id删除部门
}
MyLog
Override
public void add(Dept dept) {dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.add(dept);
}在切入点表达式以注解的方式进行
After(annotation(com.example.aop.MyLog))
public void after(){…
}3.3. 连接点
在Spring中使用JoinPoint抽象了连接点用它可以获取方法执行时的相关信息如目标类目方法名方法参数等。
对于环绕通知around获取连接点信息只能使用ProceedingJoinPoint对于其他四种通知获取连接点信息只能使用JoinPoint他是ProceedingJoinPoint的父类型。
// 我们只在环绕通知中演示因为API都是相同的
Component
Aspect
Slf4j
public class MyAspect5 {Pointcut(annotation(com.example.aop.MyLog))public void pt(){}Before(pt())public void before(JoinPoint joinPoint){log.info(before …);}Around(pt())public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info(around … before);// 1. 获取目标对象的类名log.info(目标对象的类名:joinPoint.getTarget().getClass().getName());// 2. 获取目标方法的方法名log.info(目标方法的方法名joinPoint.getSignature().getName());// 3. 目标方法运行时传入的参数log.info(目标方法运行时传入的参数 Arrays.toString(joinPoint.getArgs())); // 数组不能直接输出// 4. 放行目标方法执行Object object joinPoint.proceed();// 5. 获取目标方法的返回值log.info(目标方法的返回值 object);log.info(around … after);return object;}
}
// 查看结果
com.example.aop.MyAspect5 : around … before
com.example.aop.MyAspect5 : 目标对象的类名:com.example.service.impl.DeptServiceImpl
com.example.aop.MyAspect5 : 目标方法的方法名select
com.example.aop.MyAspect5 : 目标方法运行时传入的参数[1]
com.example.aop.MyAspect5 : before …
com.example.aop.MyAspect5 : 目标方法的返回值[Dept(id1, name学工部, createTime2023-11-30T13:55:55, updateTime2023-11-30T13:55:55)]
com.example.aop.MyAspect5 : around … after三、AOP案例
3.1. 分析
需求将项目中的增、删、改、相关接口的操作日志记录到数据库表中
操作日志包含操作人操作时间执行方法的全类名执行方法名方法运行时的参数返回值方法运行时长。 思路分析需要对方法添加统一的功能使用AOP最方便并且需要计算运行时长所以使用 环绕通知因为增删改的方法名没有规则所以使用注解的方式写切入表达式 步骤 准备 案例中引入AOP的起步依赖设计数据表结构并且引入对应的实体类 编码 自定义注解Log定义切面类完成记录操作日志的逻辑代码
3.2. 开始干活
3.2.1. 创建数据库
create table operate_log
(id int unsigned primary key auto_increment comment ID,operate_user int unsigned comment 操作人ID,operate_time datetime comment 操作时间,class_name varchar(100) comment 操作的类名,method_name varchar(100) comment 操作的方法名,method_params varchar(1000) comment 方法参数,return_value varchar(2000) comment 返回值,cost_time bigint comment 方法执行耗时, 单位:ms
) comment 操作日志表;3.2.2. 引入依赖
!– AOP–
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId
/dependency!– fastJSON 阿里巴巴提供的转JSON的工具–
!– 因为返回值是一个json的但数据库表需要的是字符串所以使用此工具将json转换成String –
dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion2.0.7/version
/dependency3.2.3. 新建实体类
Data
NoArgsConstructor
AllArgsConstructor
public class OperateLog {private Integer id; //IDprivate Integer operateUser; //操作人IDprivate LocalDateTime operateTime; //操作时间private String className; //操作类名private String methodName; //操作方法名private String methodParams; //操作方法参数private String returnValue; //操作方法返回值private Long costTime; //操作耗时
}3.2.4. 新建Mapper层
Mapper
public interface OperateLogMapper {//插入日志数据Insert(insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});)void insert(OperateLog log);
}3.2.5. 新建注解
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.METHOD)
public interface Log {
}3.2.6. 定义切面类完成记录操作日志的逻辑代码
Component
Aspect
Slf4j
public class LogAspect {Autowiredprivate HttpServletRequest request;Autowiredprivate OperateLogMapper operateLogMapper;Around(annotation(com.example.anno.Log))public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {//操作人ID 因为jwt令牌有登录人信息所以解析jwt令牌就可以
// String token request.getHeader(token);
// Claims claims JwtUtils.parseJWT(token);
// Integer user (Integer) claims.get(id);// 使用链式编程 ↓↓↓Integer user (Integer) JwtUtils.parseJWT(request.getHeader(token)).get(id);//操作时间LocalDateTime optionTime LocalDateTime.now();//操作类名String className joinPoint.getTarget().getClass().getName();//操作方法名String methodName joinPoint.getSignature().getName();//操作方法参数String args Arrays.toString(joinPoint.getArgs());long start System.currentTimeMillis(); // 记录方法开始运行时间// 调用原始方法Object result joinPoint.proceed();long end System.currentTimeMillis(); // 记录方法结束运行时间//操作方法返回值String returnValue JSONObject.toJSONString(result);//操作耗时long costTime end - start;// 记录操作日志OperateLog operateLog new OperateLog(null, user, optionTime, className, methodName, args, returnValue, costTime);operateLogMapper.insert(operateLog);log.info(AOP记录操作日志{}, operateLog);return result;}
}3.2.7. 给需要记录的方法上面添加自定义的注解
// 这里就不一一展示了
/** 根据id删除部门/LogDeleteMapping(/{id})public Result delete(PathVariable Integer id){log.info(根据id删除部门{},id);deptService.delete(id);return Result.success();}/** 添加部门*/LogPostMappingpublic Result add(RequestBody Dept dept){log.info(添加部门{},dept);deptService.add(dept);return Result.success();}3.3. 查看结果
刚刚进行了部门的增删改以及员工的增删改操作我们查看数据库看有没有被记录。
1,1,2024-10-27 20:20:23,com.example.controller.DeptController,delete,[15],{code:1,msg:success},40
2,1,2024-10-27 20:20:45,com.example.controller.DeptController,add,[Dept(idnull, name测试部, createTimenull, updateTimenull)],{code:1,msg:success},5
3,1,2024-10-27 20:21:00,com.example.controller.EmpController,sava,[Emp(idnull, username测试, passwordnull, name测试, gender1, image, job1, entrydate2024-10-20, deptId16, createTimenull, updateTimenull)],{code:1,msg:success},6
4,1,2024-10-27 20:23:01,com.example.controller.DeptController,add,[Dept(idnull, name1, createTimenull, updateTimenull)],{code:1,msg:success},8
5,1,2024-10-27 20:23:18,com.example.controller.DeptController,delete,[17],{code:1,msg:success},12完全符合要求
相关文章
-
石家庄哪里有做外贸网站的公司有没有专门做旅游攻略的网站
石家庄哪里有做外贸网站的公司有没有专门做旅游攻略的网站
- 技术栈
- 2026年03月21日
-
-
石家庄模板建站行业解决方案wordpress音乐插件h5
石家庄模板建站行业解决方案wordpress音乐插件h5
- 技术栈
- 2026年03月21日
-
石家庄企业自助建站系统做爰全过程免费视频网站
石家庄企业自助建站系统做爰全过程免费视频网站
- 技术栈
- 2026年03月21日
-
石家庄市城乡和建设局网站京东短链接生成器
石家庄市城乡和建设局网站京东短链接生成器
- 技术栈
- 2026年03月21日
-
石家庄市住房和城乡建设厅网站桥 网站建设
石家庄市住房和城乡建设厅网站桥 网站建设
- 技术栈
- 2026年03月21日
