[Java]SpringBoot业务代码增强

2024-09-04 11:52

本文主要是介绍[Java]SpringBoot业务代码增强,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

异常处理

在程序开发过程中, 不可避免的会遇到异常现象, 如果不处理异常, 那么程序的异常会层层传递, 直到spring抛出标准错误, 标准错误不符合我们的结果规范

手动处理: 在所有Controller的方法中添加 try/catch 处理错误, 代码臃肿, 所以并不推荐

全局异常处理器: 统一捕获程序中的所有异常, 简单优雅


@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)  //指定捕获所有异常public Result ex(Exception ex) {// 输出堆栈信息ex.printStackTrace();return Result.error("对不起,出现错误,请联系管理员");}}
  1. 新建exception包, 新建GlobalExceptionHandler类
  2. 使用 @RestControllerAdvice 注解 注册全局异常处理器
  3. @RestControllerAdvice = @ControllerAdvice + @ResponseBody
  4. 使用 @ExceptionHandler注解 指定需要捕获的异常类型

事务管理

事务是 一组操作的集合, 保证操作同时成功或失败, 避免出现数据操作不一致

在SpringBoot中提供了 @Transactional 注解, 用于事务的管理, 可以自动开启事务/关闭事务/事务回滚

@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Autowiredprivate EmpMapper empMapper;//进行事务管理,保证数据操作的同步@Transactional public void delete(Integer id) {deptMapper.deleteById(id);  //根据id删除部门数据int i = 1 / 0;  //模拟异常empMapper.deleteByDeptId(id);  //根据部门id删除该部门下的员工数据}
}

作用:

  1. 将当前方法交给spring进行事务管理, 方法执行前自动开启事务, 方法结束后自动关闭事务,
  2. 出现异常时自动回滚事务

使用:

  1. 可以在业务层(service)的方法上, 类上或者接口上使用注解
  2. 在方法上使用该注解, 意味着把这个方法交给spring进行事务管理
  3. 在类上使用该注解, 意味着把这个类的所有方法都交给spring进行事务管理
  4. 在接口上使用该注解, 意味着把这个接口的所有实现类的所有方法都交给spring进行事务管理
  5. 一般在业务层的方法中控制事务, 当一个方法需要多次操作数据时, 就要进行事务管理, 保证数据操作的一致性

开启spring事务管理日志

#开启事务管理日志
logging:level:org.springframework.jdbc.support.JdbcTransactionManager: debug

默认只有RuntimeException(运行时异常)才会回滚事务, 可以通过rollbackFor属性控制回滚的的异常类型

@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Autowiredprivate EmpMapper empMapper;// 默认只有发生运行时异常才会回滚// 指定为所有异常都会回滚@Transactional(rollbackFor = Exception.class)public void delete(Integer id) {deptMapper.deleteById(id);  //根据id删除部门数据int i = 1 / 0;  //模拟异常empMapper.deleteByDeptId(id);  //根据部门id删除该部门下的员工数据}
}

事务传播行为: 当一个事务方法被另一个事务方法调用时, 这个事务方法应该如何进行事务控制

可以通过propagation属性控制事务的传播行为

  1. 事务传播: 可以理解为, 嵌套调用的两个事物方法, 里面的事物方法与外面的事物方法的关系
  2. 加入事务: 可以理解为父子关系, 内层事务方法受外层事务方法的影响, 外层事务回滚会导致内层事务的回滚
  3. 新建事务: 可以理解为兄弟关系, 内存事务是独立于外层事务的, 不受其影响,
  4. 比如下单日志, 无论下单是否成功, 都要保证日志能够记录成功, 就要指定新建事务模式

示例: 解散部门时, 无论成功还是失败, 都要记录操作操作日志

@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Autowiredprivate EmpMapper empMapper;@Autowiredprivate DeptLogService deptLogService;//进行事务管理,保证数据同步@Transactional(rollbackFor = Exception.class)  public void delete(Integer id) {try {//根据id删除部门数据deptMapper.deleteById(id);  //模拟异常int i = 1 / 0;  //根据部门id删除该部门下的员工数据empMapper.deleteByDeptId(id);  } finally {DeptLog deptLog = new DeptLog();deptLog.setCreateTime(LocalDateTime.now());deptLog.setDescription("解散部门的操作,解散的是" + id + "号部门");// 记录解散部门的操作日志// 该方法也是一个事务方法deptLogService.insert(deptLog);}}
}
@Service
public class DeptLogServiceImpl implements DeptLogService {@Autowiredprivate DeptLogMapper deptLogMapper;// 指定事务传播模式为 新建事务// 保证这个事务方法是独立的, 不会因为其他事务的回滚收到影响@Transactional(propagation = Propagation.REQUIRES_NEW)public void insert(DeptLog deptLog) {deptLogMapper.insert(deptLog);}
}

控制台日志高亮插件: 可以选择日志类型, 高亮显示该类型的控制台日志

AOP

介绍

Aspect Oriented Programming翻译过来就是面向切面编程, 其实就是面向特定方法编程, 在不修改方法的同时, 增强或修改方法的代码逻辑

  1. 如果我们要统计所有业务方法的执行耗时, 比较容易想到的方案, 就是在程序执行前记录时间, 在程序执行后记录时间, 然后计算时间差, 得到程序执行耗时, 虽然可以实现, 但是相当繁琐
  2. 如果采用AOP技术, 我们只需要定义一个模版方法, 然后在模版方法中记录程序开始和结束时间, 就可以在不改变原始方法的同时, 得到程序耗时, 程序就变得非常优雅
  3. 面向切面编程是一种思想, 动态代理是实现面向切面编程的主流技术
  4. SpringAOP是Spring框架的高级技术, Spring实现面向切面编程的技术方案
  5. 旨在管理bean对象的过程中, 主要通过底层动态代理机制, 对特定方法进行增强和修改

AOP面向切面编程的优势

常见的使用AOP技术的场景

  1. SpringBoot中的事务管理就是基于AOP技术实现的
  2. 方法执行前, 自动开启事务
  3. 方法执行后, 自动关闭事务

开发步骤

使用SpringAOP完成面向切面编程, 首先要引入AOP依赖, 然后编写AOP程序, 完成对特定方法的编程

// 引入SpringAOP依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
// 编写AOP程序
@Component
@Slf4j
@Aspect //定义AOP类
public class TimeAspect {// 切入点表达式: 决定切面的生效范围@Around("execution(* com.itheima.service.*.*(..))")  public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {//1,记录开始时间long begin = System.currentTimeMillis();//2,调用原始方法运行Object result = joinPoint.proceed();//3,记录结束时间,计算方法耗时long end = System.currentTimeMillis();log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",end-begin);return result;}}

执行流程

  1. 切入点表达式指定需要被监听的方法
  2. 条件触发后, 程序进入AOP模版类, 执行AOP方法
  3. 在AOP方法内, 可以实现特定操作

核心概念

AOP中的核心概念

  1. 连接点: JoinPoint, 可以被AOP控制的方法(暗含方法执行时的相关信息)
  2. 通知: Advice, 那些重复的逻辑, 也就是共性功能(最终体现为一个方法)
  3. 切入点: PointCut, 匹配连接点的条件, 通知仅会在切入点方法执行时被应用
  4. 切面: Aspect, 描述通知与切入点的对应关系(通知 + 切入点)
  5. 目标对象: Target, 通知所应用的对象

AOP程序的执行流程

  1. SpringAOP是基于动态代理技术实现
  2. 通过 @Aspect 注解定义切面类, 该类就会被SpringAOP管理
  3. 通过 切入点表达式 指定目标对象, 在程序运行时就会自动生成目标对象的代理对象
  4. 在代理对象中, 就会对原始对象中的方法进行增强, 增强的逻辑就是切面类中定义的通知
  5. 在本案例中, 就是先记录执行前时间, 在执行目标方法,, 再记录执行后时间, 最后统计方法执行耗时, 并且返回目标方法执行的结果
  6. 最终, 在程序中注入目标对象时, 注入的其实是增强后的代理对象, 而不是原始的目标对象

AOP详解

通知类型

通知类型控制通知的执行时机

  1. @Around: 环绕通知,通知方法执行前后都被执行
  • 环绕通知需要自己调用 ProceedingJoinPoint.proceed()方法 让原始方法执行, 其他通知不需要
  • 环绕通知方法的返回值, 必须指定为Object, 来接收原始方法的返回值
  1. @Before: 前置通知,通知方法执行前被执行
  2. @After: 后置通知,通知方法执行后被执行,无论是否异常
  3. @AfterReturning: 通知方法正常执行后被执行, 有异常不执行
  4. @AfterThrowing: 通知方法有异常后执行

通知顺序

通知顺序: 当有多个切面的切入点都匹配到了方法, 目标方法执行时, 多个通知方法都会被执行

复用表达式

抽取切入点表达式: 通过 @PoinCut 注解将公共的切入点表达式出来, 需要的时候引用该表达式即可

  1. 如果切入点表达式的修饰符是 private, 则只能在当前切面类中引用
  2. 如果切入点表达式的修饰符是 public, 在其他外部的切面类中也可以引用该表达式

切入点表达式

切入点表达式: 描述切入点方法的一种表达式, 用来决定项目中的哪些方法需要加入通知

excution(): 根据方法的签名来匹配

主要根据方法的返回值, 包名, 类名, 方法名, 方法参数等信息来匹配

  1. 其中 ?表示可以省略的部分
  2. 访问修饰符: 建议省略(比如public, protected )
  3. 包名.类名: 建议不要省略, 省略后匹配的范围太大, 影响匹配效率
  4. throws 异常: 建议省略不写

通配符

可以使用通配符描述切入点

  1. *匹配单个的任意符号
  2. ..匹配多个连续的任意符号,一般用于描述任意包或任意参数
@Slf4j
@Aspect
@Compoment
public class MyAspect6 {// DeptServiceImpl这个类下的delete方法生效, 并且这个方法返回值要是void@Pointcut("execution(public void com.itheima.server.impl.DeptServiceImpl.delete(java.lang.Interger))")// 匹配com包下的所有方法@Pointcut("execution(* com..*.*(..))")// 匹配程序中的所有方法(慎用)@Pointcut("execution(* *(..))")// 匹配符合条件的list方法或者delete方法@Pointcut("execution(* com.itheima.service.DeptService.list()) ||" + "execution(* com.itheima.service.DeptService.delete(java.lang.Integer))")private void pt(){}@Before("pt()")public void before() {log.info("...执行before...");}
}

建议

  1. 业务方法名在命名时保持规范, 方便匹配, 查询方法用find开头,更新方法用updata开头
  2. 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强扩展性
  3. 尽量缩小切入点的匹配范围, 匹配范围越大, 性能越差
  4. 根据业务需要, 可以使用 && || ! 来组合比较复杂的切入点表达式
@annotation(...): 根据注解匹配

适用于切入点表达式过于复杂时使用

// 自定义注解
@Retention(RetentionPolicy.RUNTIME)  //指定运行时注解生效
@Target(ElementType.METHOD)  //指定注解生效的范围,此为方法
// 注意是注解
public @interface MyLog {}
@Slf4j
@Server
public class DeptServiceImpl implements DeptService {// 加上@MyLog注解@MyLogpublic List<Dept> list() {... ...}
}
@Slf4j
@Aspect
@Compoment
public class MyAspect6 {// 匹配有MyLog注解的方法@Pointcut("@annotation(com.itheima.aop.MyLog)")private void pt(){}@Before("pt()")public void before() {log.info("...执行before...");}
}

连接点

连接点就是指所有被SpringAOP管理的方法

在spring中用JoinPoint抽象了连接点, 用它可以获取方法执行时的相关信息, 如目标类型, 方法名, 方法参数等

@Around通知类型: 必须使用 ProceedingJoinPoint 获取连接点信息

其他4种通知类型, 使用 JoinPoint 获取连接点信息

  1. 注意使用 org.aspectj.lang 包下的 Joinpoint连接点对象

综合案例

将增删改相关接口的操作日志记录到数据库表中

引入AOP依赖

 <!-- AOP -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

新建日志操作表(资料中提供)

准备实体类(资料中提供)

@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; //操作耗时
}

准备mapper接口(资料中提供)

@Mapper
public interface DeptLogMapper {@Insert("insert into dept_log(create_time,description) values(#{createTime},#{description})")void insert(DeptLog log);}

新增自定义注解

@Retention(RetentionPolicy.RUNTIME)  //指定自定义注解的生效时机
@Target(ElementType.METHOD)  //指定自定义注解生效的范围
public @interface Log { }

创建切面类, 编写通知逻辑

@Slf4j
@Component
@Aspect  //标明是切面类
public class LogAspect {@Autowiredprivate OperateLogMapper operateLogMapper;@Autowired// 注入请求对象, 通过请求对象解析JWTprivate HttpServletRequest request;@Around("@annotation(com.itheima.anno.Log)")  //切入点表达式public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {//操作人id----当前登录员工id//获取请求头中的jwt令牌,解析令牌String jwt = request.getHeader("token");  //获取令牌Claims claims = JwtUtils.parseJWT(jwt);  //解析令牌Integer operateUser = (Integer) claims.get("id");  //拿到员工id//操作时间LocalDateTime operateTime = LocalDateTime.now();//操作类名String className = joinPoint.getTarget().getClass().getName();//操作的方法名String methodName = joinPoint.getSignature().getName();//操作的方法参数Object[] args = joinPoint.getArgs();String methodParams = Arrays.toString(args);long begin = System.currentTimeMillis();//执行原始方法,并获取返回值Object result = joinPoint.proceed();long end = System.currentTimeMillis();//操作方法的返回值,转String类型String returnValue = JSONObject.toJSONString(result);//操作耗时long costTime = end - begin;// 记录操作日志OperateLog operateLog = new OperateLog(null, operateUser, operateTime, className, methodName, methodParams, returnValue, costTime);operateLogMapper.insert(operateLog);log.info("AOP记录操作日志:{}", operateLog);return result;}
}

应用通知: 给所有需要记录操作日志的方法, 添加自定义注解

/*** 部门管理Controller*/
@Slf4j
@RestController
@RequestMapping("/depts")
public class DeptController {@Autowiredprivate DeptService deptService;/*** 根据id删除部门信息*/@Log@DeleteMapping("/{id}")public Result delete(@PathVariable Integer id) {log.info("删除部门的id:{}", id);deptService.delete(id);return Result.success();}... ...
}

前后端联调, 操作日志被记录到数据库表中

这篇关于[Java]SpringBoot业务代码增强的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1135948

相关文章

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Java中的工具类命名方法

《Java中的工具类命名方法》:本文主要介绍Java中的工具类究竟如何命名,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java中的工具类究竟如何命名?先来几个例子几种命名方式的比较到底如何命名 ?总结Java中的工具类究竟如何命名?先来几个例子JD

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多

Spring Security自定义身份认证的实现方法

《SpringSecurity自定义身份认证的实现方法》:本文主要介绍SpringSecurity自定义身份认证的实现方法,下面对SpringSecurity的这三种自定义身份认证进行详细讲解,... 目录1.内存身份认证(1)创建配置类(2)验证内存身份认证2.JDBC身份认证(1)数据准备 (2)配置依