[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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来