【JavaEE进阶】Spring AOP使用篇

news2024/11/23 21:06:35

目录

1.AOP概述

2.SpringAOP快速入门

2.1 引入AOP依赖

2.2 编写AOP程序

3. Spring AOP详解

3.1 Spring AOP 核心概念

3.1.1切点(Pointcut)

3.1.2 连接点 (Join Point)

3.1.3 通知(Advice)

3.1.4 切面(Aspect)

3.2 通知类型

3.3@PointCut

3.4 切面优先级

3.5 切点表达式

3.5.1 execution 表达式

3.5.2 @annotation

3.5.2.1 自定义注解 @MyAspect

3.5.2.2 切面类

3.5.2.3 添加自定义注解

4. Spring AOP的实现方式


1.AOP概述

AOP是Spring框架的一大核心(第一大核心是loC)

什么是AOP?

Aspect Oriented Programming(面向切面编程)

什么是面向切面编程呢?切面就是指某一类特定问题, 所以 AOP 也可以理解为面向特定方法编程.

什么是面向特定方法编程呢? 比如以前学习的"登录校验", 就是一类特定问题.  登录校验拦截器, 就
是对 "登录校验" 这类问题的统一处理. 所以,拦截器也是 AOP 的一种应用. AOP 是一种思想,拦截器是 AOP 思想的一种实现. Spring 框架实现了这种思想,  提供了拦截器技术的相关接口.

同样的, 统一数据返回格式和统一异常处理,也是AOP思想的一种实现.

简单来说:  AOP是一种思想,是对某一类事情的集中处理.
 

什么是 Spring AOP ?

AOP是一种思想,它的实现方法有很多,有Spring AOP, 也有AspectJ, CGLIB等.

Spring AOP是其中的一种实现方式.

学会了统一功能之后,是不是就学会了Spring AOP呢,当然不是.

截器作用的维度是URL(一次请求和响应), @ControllerAdvice 应用场景主要是全局异常处理
(配合自定义异常效果更佳), 数据绑定 , 数据预处理.  AOP 作用的维度更加细致(可以根据包、类、方法名、参数等进行拦截),  能够实现更加复杂的业务逻辑.

举个例子:

我们现在有一个项目, 项目中开发了很多的业务功能

现在有一些业务的执行效率比较低, 耗时较长, 我们需要对接口进行优化.

第一步就需要定位出执行耗时比较长的业务方法, 在针对该业务方法来进行优化

如何定位呢? 我们就需要统计当前项目中每一个业务方法的执行耗时.

如何统计呢? 可以在业务方法运行前和运行后,  记录下方法的开始时间和结束时间, 两者之差就是这个方法的耗时. 

这种方法是可以解决问题的,但一个项目中会包含很多业务模块,每个业务模块又有很多接口,一个接口又包含很多方法, 如果我们要在每个业务方法中都记录方法的耗时,对于程序员而言, 会增加很多的工作量.

AOP 就可以做到在不改动这些原始方法的基础上, 针对特定的方法进行功能的增强.

AOP 的作用: 在程序执行期间不修改源代码的基础上对已有方法的增强(无侵入性: 解耦)

接下来我们来看看 SpringAOP 如何来实现.

2.SpringAOP快速入门

学习完什么是 AOP 后, 我们先通过下面的程序体验下 AOP 的开发, 并掌握 Spring 中 AOP 的开发步骤.

需求: 统计图书系统中各个接口方法的执行时间.

2.1 引入AOP依赖

在 pom.xml 文件中添加配置

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

2.2 编写AOP程序

记录 Controller 中每个方法的执行时间

先看看传统的方法:

/**
     * 根据ID查询图书信息
     *
     * @param bookId
     * @return
     */
    @RequestMapping("/queryBookById")
    public BookInfo queryBookById(Integer bookId) {
        log.info("根据ID查询图书信息, id:{}", bookId);
        long start = System.currentTimeMillis();
        BookInfo bookInfo = bookService.queryBookById(bookId);
        long end = System.currentTimeMillis();
        log.info("[BookController] queryBookById 耗时: " + (end-start) + " ms");
        return bookInfo;
    }

使用这种方式, 当要测试多个接口的时候, 就需要对每个接口的代码进行修改 , 工作量比较复杂, 很影响我们的时间和效率.

下面来看看使用AOP的代码吧:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class TimeRecordAspect {
    /**
     * 记录耗时
     */
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object TimeRecord(ProceedingJoinPoint joinPoint) throws Throwable {
        //记录开始事件
        long start = System.currentTimeMillis();
        //执行目标方法
        Object proceed = joinPoint.proceed();
        //记录结束时间
        long end = System.currentTimeMillis();
        //日志打印耗时
        log.info(joinPoint.getSignature() + " 耗时时间: " + (end- start) + " ms");
        return proceed;
    }
}

运行程序, 观察日志

对程序进行简单的讲解:

1. @Aspect: 标识这是一个切面类

2. @Around: 环绕通知, 在目标方法的前后都会被执行. 后⾯的表达式表示对哪些方法进行增强.

3. ProceedingJoinPoint.proceed(): 让原始方法执行

整个代码划分为三部分

我们通过AOP入门程序完成了业务接口执行耗时的统计.

通过上面的程序,我们也可以感受到AOP面向切面编程的一些优势:

  • 代码无侵入: 不修改原始的业务方法, 就可以对原始的业务方法进行了功能的增强或者是功能的改变
  • 减少了重复代码
  • 提高开发效率
  • 维护方便
     

3. Spring AOP详解

下面我门再来详细学习AOP, 主要是一下几个部分

Spring AOP 中涉及的核心概念

Spring AOP 的通知类型

多个 AOP 的执行顺序

3.1 Spring AOP 核心概念

3.1.1切点(Pointcut)

切点(Pointcut), 也称之为 "切入点"

Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述), 告诉程序对哪些方法来进行功能增强.

上面的表达式  execution(* com.example.demo.controller.*.*(..)) 就是切点表达式.

3.1.2 连接点 (Join Point)

满足切点表达式规则的方法, 就是连接点, 也就是可以被 AOP 控制的方法

以如门程序举例, 所有 com.example.demo.controller 路径下的方法, 都是连接点

package com.example.demo.controller;

@RequestMapping("/book")
@RestController
public class BookController {
    @RequestMapping("/addBook")
    public Result addBook(BookInfo bookInfo) {
        //...代码省略 
    }

    @RequestMapping("/queryBookById")
    public BookInfo queryBookById(Integer bookId) {
        //...代码省略 
    }

    @RequestMapping("/updateBook")
    public Result updateBook(BookInfo bookInfo) {
        //...代码省略 
    }
}

上述 BookController 中的方法都是连接点

切点和连接点的关系

连接点是满足切点表达式的元素, 切点可以看作是保存了众多连接点的一个集合.

比如:

切点表达式: 全体教师

连接点就是: 张三, 李四等各个教室

3.1.3 通知(Advice)

通知就是具体要做的事情, 指哪些重复的逻辑, 也就是共性功能(最终体现为一个方法)

比如上述程序中记录业务方法的耗时时间, 就是通知

在AOP面向切面编程当中, 我门把这部分重复的代码逻辑抽取出来单独定义, 这部分代码就是通知类容.

3.1.4 切面(Aspect)

切面(Aspect) = 切点(Pointcut) + 通知(Advice)

通过切面就能描述当前AOP程序需要针对于哪些方法, 在什么时候执行什么样的操作.

切面既包含了通知逻辑的定义, 也包括了连接点的定义.

切面所在的类, 我们一般称为切面类(被@Aspect注解标识的类)

3.2 通知类型

上面我们讲了什么是通知, 接下来学习通知的类型. @Around 就是其中的一种通知类型, 表示环绕通知.

Spring 中 AOP 的通知类型有一下几种:

@Around: 环绕通知, 次注解标注的通知方法在目标方法前, 后都被执行

@Before: 前置通知, 次注解表述的通知方法在目标方法前被执行

@After: 后置通知, 次注解标注的通知方法在目标方法后被执行, 无论是否有异常都会执行

@AfterReturning: 返回后通知, 次注解标注的通知方法在目标方法返回后被执行, 有异常不会执行

@AfterThrowing: 次注解标注的通知方法发生异常后执行

接下来我门通过代码来加深对这几个通知的理解:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


@Component
@Slf4j
@Aspect
public class AspectDemo {
    //前置通知
    @Before("execution(* com.example.aop.controller.*.*(..))")
    public void doBefore() {
        log.info("AspectDemo do before...");
    }
    //后置通知
    @After("execution(* com.example.aop.controller.*.*(..))")
    public void doAfter() {
        log.info("AspectDemo do after...");
    }
    //环绕通知
    @Around("execution(* com.example.aop.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("AspectDemo do around before...");
        Object result = null;
        try {
            result = joinPoint.proceed();
        }catch (Exception e) {
            log.error("do around 执行目标函数, 内部发生异常");
        }
        log.info("AspectDemo do around after...");
        return result;
    }
    //返回后通知
    @AfterReturning("execution(* com.example.aop.controller.*.*(..))")
    public void doAfterReturning() {
        log.info("AspectDemo do AfterReturning...");
    }
    //抛出异常后通知
    @AfterThrowing("execution(* com.example.aop.controller.*.*(..))")
    public void doAfterThrowing() {
        log.info("AspectDemo do AfterThrowing...");
    }
}

写一些测试程序:

import com.example.aop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {
    @MyAspect
    @RequestMapping("/t1")
    public String t1() {
        log.info("执行t1方法....");
        return "t1";
    }

    @RequestMapping("/t2")
    public String t2() {
        log.info("执行t2方法....");
        int a = 10/0;
        return "t2";
    }
}

运行程序, 观察日志:

1.正常运行的情况

观察日志

程序正常运行的情况下, @AfterThrowing 标识的通知方法不会执行

从上图也可以看出来, @Around 标识的通知方法包含两个部分, 一个 "前置逻辑" , 一个 "后置逻辑" .其中 "前置逻辑" 会先于 @Before 标识的通知方法执行. "后置逻辑" 会晚于 @After 标识的通知方法执行

2. 异常时的情况

观察日志

程序发生异常的情况下:

@AfterReturning 标识的通知方法不会执行, @AfterThrowing 标识的通知方法执行了

@Around 环绕通知中原始方法调用时有异常, 通知中的环绕后的代码也不会在执行了(因为原始方法调用出异常了)

注意事项:

@Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行, 其他通知不需要考虑目标方法执行.

@Around环绕通知方法的返回值, 必须指定为Object , 来接收原始方法的返回值, 否则原始方法执行完毕, 是获取不到返回值的.

一个切面类可以有多个切点

3.3@PointCut

上面代码存在一个问题, 就是存在大量重复的切点表达式 execution(* com.example.demo.controller.*.*(..)) , Spring 提供了 @Pointcut 注解, 把公共的切点表达式提取出来, 需要用到时引入该切点表达式即可.

上述代码就可以修改为:

@Component
@Slf4j
@Aspect
public class AspectDemo {
    @Pointcut("execution(* com.example.aop.controller.*.*(..))")
    public void pt(){};

    //前置通知
    @Before("pt()")
    public void doBefore() {
        //...代码省略
    }
    //后置通知
    @After("pt()")
    public void doAfter() {
        //...代码省略
    }
    //添加环绕通知
    @Around("pt()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //...代码省略
    }
    //返回后通知
    @AfterReturning("pt()")
    public void doAfterReturning() {
        //...代码省略
    }
    //抛出异常后通知
    @AfterThrowing("pt()")
    public void doAfterThrowing() {
        //...代码省略
    }
}

当请切点使用 private 修饰时, 仅能在当前切面类中使用, 当其他切面类也要使用当前切点定义时, 就需要把 private 改为 public, 引用方式为: 全限定类名.方法名()

@Component
@Slf4j
@Aspect
public class AspectDemo2 {

    @Before("com.example.aop.aspect.AspectDemo.pt()")
    public void doBefore() {
        log.info("AspectDemo2 do before...");
    }

    @After("com.example.aop.aspect.AspectDemo.pt()")
    public void doAfter() {
        log.info("AspectDemo2 do after...");
    }
}

3.4 切面优先级

当我们在一个项目中,定义了多个切面类时,并且这些切面类的多个切入点都匹配到了同一个目标方法. 当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?

我们还是通过程序来验证:

定义多个切⾯类:

为了防止干扰, 我们把AspectDemo这个切面先去掉(把@Component注解去掉就可以)

为了简单化, 只写了@Before 和 @After 两个通知

@Component
@Slf4j
@Aspect
public class AspectDemo2 {

    @Before("com.example.aop.aspect.AspectDemo.pt()")
    public void doBefore() {
        log.info("AspectDemo2 do before...");
    }

    @After("com.example.aop.aspect.AspectDemo.pt()")
    public void doAfter() {
        log.info("AspectDemo2 do after...");
    }
}
@Component
@Slf4j
@Aspect
public class AspectDemo3 {
    @Before("com.example.aop.aspect.AspectDemo.pt()")
    public void doBefore() {
        log.info("AspectDemo3 do before...");
    }

    @After("com.example.aop.aspect.AspectDemo.pt()")
    public void doAfter() {
        log.info("AspectDemo3 do after...");
    }
}
@Component
@Slf4j
@Aspect
public class AspectDemo4 {

    @Before("com.example.aop.aspect.AspectDemo.pt()")
    public void doBefore() {
        log.info("AspectDemo4 do before...");
    }

    @After("com.example.aop.aspect.AspectDemo.pt()")
    public void doAfter() {
        log.info("AspectDemo4 do after...");
    }
}

运行程序, 访问接口:

 观察日志:

通过上述程序的运行结果, 可以看出:

存在多个切面类时, 默认按照切面类的名字母排序:

  • @Before 通知: 字母名字靠前的先执行
  • @After 通知: 字母排名靠前的后执行

但这种方式不方便管理,我们的类名更多还是具备一定含义的.

Spring给我们提供了一个新的注解,来控制这些切面通知的执行顺序: @Order

使用方式如下:

重新运行程序, 观察日志:

通过上述程序的运行结果, 得出结论:

@Order 注解标识的切面类, 执行顺序如下:

@Before 通知: 数字越小先执行

@After 通知: 数字越大先执行

@Order 控制切面的优先级, 先执行优先级高的切面, 在执行优先级较低的切面, 最终执行目标方法.

3.5 切点表达式

上面的代码中,我们一直在使用切点表达式来描述切点. 下面我们来介绍一下切点表达式的语法.

切点表达式常见有两种表达方式

1. execution(......): 根据方法的签名来匹配

2. @annotation(......): 根据注解匹配

3.5.1 execution 表达式

execution() 是最常用的切点表达式, 用来匹配方法, 语法为:

其中: 访问修饰符和异常可以省略

切点表达式支持通配符表达:

1.: 匹配任意字符, 只匹配一个元素(返回类型, 包, 类名, 方法或者方法参数)

a. 包名使用 表示任意包(一层包使用一个 * )

b. 类名使用 表示任意类

c. 返回值使用 表示任意返回值类型

d. 方法名使用 表示任意方法

e. 参数使用 表示一个任意类型的参数

2. .. : 匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数

a. 使用 .. 配置包名, 标识次包以及此包下的所有子包

b. 可以使用 .. 配置参数, 任意个任意类型的参数

切点表达式示例

TestController 下的 public 修饰, 返回类型为 String 方法名为 t1, 无参方法

execution(public String com.example.demo.controller.TestController.t1())

省略访问修饰符

execution(String com.example.demo.controller.TestController.t1())

匹配所有返回类型

execution(* com.example.demo.controller.TestController.t1())

匹配 TestController 下的所有无参方法

execution(* com.example.demo.controller.TestController.*())

匹配 TestController 下的所有方法

execution(* com.example.demo.controller.TestController.*(..))

匹配 controller 包下所有的类的所有方法

execution(* com.example.demo.controller.*.*(..))

匹配所有包下面的 TestController

execution(* com..TestController.*(..))

匹配 com.example.demo 包下, 子孙包下的所有类的所有方法

execution(* com.example.demo..*(..))

匹配特定方法名且有特定参数的方法:

execution(* myMethod(String, int))

 匹配特定方法名且有特定参数, 并且抛出特定异常的方法

execution(* myMethod(String, int) throws IOExeception)

3.5.2 @annotation

execution 表达式更适合有规则的, 如果我门要匹配多个无规则的方法呢, 比如: TestController中的 t1() 和 UserController 中的 u1() 这两个方法.

这个时候我们使用 execution 这种切点表达式来描述就不是很方便了.

我们可以借助自定义注解的方法以及另一种切点表达式 @annotation 来描述这一类的切点

实现步骤:

1. 编写自定义注解

2. 使用 @annotation 表达式来描述切点

3. 在连接点的方法上添加自定义注解

准备测试代码

@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {
    @RequestMapping("/t1")
    public String t1() {
        log.info("执行t1方法....");
        return "t1";
    }

    @RequestMapping("/t2")
    public String t2() {
        log.info("执行t2方法....");
        int a = 10/0;
        return "t2";
    }
}
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController{
    @RequestMapping("/u1")
    public String u1() {
        log.info("执行u1方法...");
        return "u1";
    }

    @RequestMapping("/u2")
    public String u2() {
        log.info("执行u2方法...");
        return "u2";
    }
}
3.5.2.1 自定义注解 @MyAspect

创建一个注解类(和创建Class文件一样的流程, 选择Annotation就可以了)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {

}

代码简单说明, 了解即可, 不做过多的解释

1. @Target 标识了 Annotation 所修饰的对象范围, 及该注解可以用在什么地方.

常用取值: 

ElementType.TYPE: 用于描述类, 接口(包括注解类型)或 enum 声明

ElementType.METHOD: 描述方法

ElementType.PARAMETER: 描述参数

ElementType.TYPE_USE: 可以标注任意类型

2. @Retention 指 Annotation 被保留的时间长短, 标明注解的声明周期

@Retention的取值有三种

1. RetentionPolicy.SOURCE: 表示注解仅存在于源代码中,编译成字节码后会被丢弃. 这意味着在运行时无法获取到该注解的信息,  只能在编译时使用.  比如@SuppressWarnings,  以及lombok  提供的注解 @Data, @Slf4j

2. RetentionPolicy.CLASS: 编译时注解. 表示注解存在于源代码和字节码中,但在运行时会被丢弃. 这意味着在编译时和字节码中可以通过反射获取到该注解的信息,  但在实际运行时无法获取. 通常用于一些框架和工具的注解.

3. RetentionPolicy.RUNTIME: 运行时注解. 表示注解存在于源代码,字节码和运行时中. 这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息. 通常用于一些需要在运行时处理的注解,如Spring的 @Controller @ResponseBody

3.5.2.2 切面类

使用 @annotation 切点表达式定义切点, 只对 @MyAspect 生效

切面类代码如下:

@Component
@Slf4j
@Aspect
public class MyAspectDemo {
    //前置通知
    @Before("@annotation(com.example.aop.config.MyAspect)")
    public void before() {
        log.info("MyAspect -> before...");
    }
    //后置通知
    @After("@annotation(com.example.aop.config.MyAspect)")
    public void after() {
        log.info("MyAspect -> after...");
    }
}
3.5.2.3 添加自定义注解

在 TestController 中 t1() 和 UserController 中的 u1() 这两个方法上添加自定义注解 @MyAspect, 其他方法不加

运行程序, 测试接口:

观察日志:

继续测试 t2, 观察日志:

切面通知未执行

4. Spring AOP的实现方式

1.基于注解 @Aspect

2. 基于自定义注解(参考上面自定义注解 @annotation 部分的内容)

3. 基于Spring API (通过xml配置的方法, 自从SpringBoot广泛使用之后, 这种方法几乎看不到了, 稍作了解即可)

4. 基于代理来实现(更加久远的一种方式, 写法笨重, 不建议使用)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1870507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Unity设计模式】使用对象池

前言 最近在学习Unity游戏设计模式&#xff0c;看到两本比较适合入门的书&#xff0c;一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》 这两本书介绍了大部分会使用到的设计模式&#xff0c;因此很值得学习 本…

NSIS 打包发布 exe 安装包之 配置文件参数说明

一、打包exe教程 详见上期博客&#xff1a;visual studio打包QT工程发布exe安装包 二、参数说明 1、程序图标显示无效问题 在nsi配置文件中找到以下行&#xff0c;分别在尾部追加 “” “$INSTDIR\logo-ico.ico” &#xff0c; logo-ico.ico为程序图标名称&#xff0c;Setup…

Flutter学习目录

学习Dart语言 官网&#xff1a;https://dart.cn/ 快速入门&#xff1a;Dart 语言开发文档&#xff08;dart.cn/guides&#xff09; 学习Flutter Flutter生命周期 点击跳转Flutter更换主题 点击跳转StatelessWidget和StatefulWidget的区别 点击跳转学习Flutter中新的Navigato…

基于Java的汽车租赁系统【附源码】

论文题目 设计&#xff08;论文&#xff09;综述&#xff08;1000字&#xff09; 当今社会&#xff0c;汽车租赁已成为一种受欢迎的出行方式。本文旨在探讨汽车租赁行业的发展趋势、市场规模及其对环境的影响。目前&#xff0c;汽车租赁行业正在经历着快速的发展。随着经济的发…

昇思25天学习打卡营第9天|使用静态图加速

一、简介&#xff1a; AI编译框架分为两种运行模式&#xff0c;分别是动态图模式以及静态图模式。MindSpore默认情况下是以动态图模式运行&#xff0c;但也支持手工切换为静态图模式。两种运行模式的详细介绍如下&#xff1a; &#xff08;1&#xff09;动态图&#xff1a; …

维基百科:12种维基百科推广技术让你成为行业专家

维基百科&#xff08;Wikipedia&#xff09;作为全球最大的免费网络百科全书&#xff0c;已经成为人们获取知识的重要源泉之一。对于想要在特定领域成为行业专家的人来说&#xff0c;利用维基百科进行推广是一种非常有效的方式。本文将介绍12种维基百科推广技术&#xff0c;帮助…

奔驰汽车的通信如此固若金汤的原因

随着摄像系统、距离控制、航线保持等功能以及制动辅助系统、制动力分配系统、车身侧倾干预与缓解系统等功能的飞速发展,汽车的系统功能之间已经不再独立,而是呈现互相合作的关系,各功能之间的无缝集成更是各大整车厂追求的目标。俗话说,外练筋骨皮,内练一口气,有了各式安…

alibaba easyexcel 导出excel使用

需求 传统导出&#xff0c;一般都是通过Workbook > Sheet > Row > Cell 获取详细Cell 设置值&#xff0c;比较麻烦&#xff0c;偶然遇到alibaba easyexcel 直接通过注解设置哪些需要导出 哪些忽略&#xff0c;发现特别好用。 pom依赖 <dependency><groupId…

yolov10打包为exe

一、前言 本节实验将官方yolov10推理程序打包为exe运行 二、代码 首先下载官方代码至本机&#xff0c;并使用conda创建虚拟环境&#xff0c;并安装好yolov10所需库 conda create --prefix E:/pyenv/myYolo10 python3.8 pip install -r requirements.txt 下载官方模型权重 …

HarmonyOS Next开发学习手册——内存管理(GC)

GC&#xff08;全称 Garbage Collection&#xff09;&#xff0c;即垃圾回收。在计算机领域&#xff0c;GC就是找到内存中的垃圾&#xff0c;释放和回收内存空间。当前主流编程语言实现的GC算法主要分为两大类&#xff1a;引用计数和对象追踪&#xff08;即Tracing GC&#xff…

springcloud-config 客户端启用服务发现client的情况下使用metadata中的username和password

为了让spring admin 能正确获取到 spring config的actuator的信息&#xff0c;在eureka的metadata中添加了metadata.user.user metadata.user.password eureka.instance.metadata-map.user.name${spring.security.user.name} eureka.instance.metadata-map.user.password${spr…

Java中的程序异常处理介绍

一、异常处理机制 Java提供了更加优秀的解决办法&#xff1a;异常处理机制。 异常处理机制能让程序在异常发生时&#xff0c;按照代码的预先设定的异常处理逻辑&#xff0c;针对性地处理异常&#xff0c;让程序尽最大可能恢复正常并继续执行&#xff0c;且保持代码的清晰。 Ja…

Spring事务的源码底层实现

文章目录 事务理论执行过程EnableTransactionManagement底层实现 事务 在线流程图 理论执行过程 通过事务管理器创建一个连接对象connection1设置事务隔离级别、是否只读等conn1.autocommit(false)将conn1存入ThreadLocal中Map<DataSource,Connection>执行目标方法、多…

c++习题01-ljc的暑期兼职

目录 一&#xff0c;题目描述 二&#xff0c;思路 三&#xff0c;伪代码 四&#xff0c;流程图 五&#xff0c;代码 一&#xff0c;题目描述 二&#xff0c;思路 1&#xff0c;根据题目要求需要声明4个变量&#xff1a;a,b,c,d ;牛奶价格a&#xff0c;活动要求b&…

浅析Resource Quota中limits计算机制

前言 在生产环境中&#xff0c;通常需要通过配置资源配额&#xff08;Resource Quota&#xff09;来限制一个命名空间&#xff08;namespace&#xff09;能使用的资源量。在资源紧张的情况下&#xff0c;常常需要调整工作负载&#xff08;workload&#xff09;的请求值&#xf…

java基于ssm+jsp 毕业生就业信息管理系统

1管理员功能模块 管理员输入个人的用户名、密码、角色登录系统&#xff0c;这时候系统的数据库就会在进行查找相关的信息&#xff0c;如果我们输入的用户名、密码不正确&#xff0c;数据库就会提示出错误的信息提示&#xff0c;同时会提示管理员重新输入自己的用户名、密码&am…

MYSQL存储过程的创建

关于存储过程的题目 1、创建存储过程,查看user表中的所有数据 2、创建存储过程avg_order_quantity,返回所有订单的平均工资 3、创建存储过程show_max_bprice,用来查看bookS的单价最贵的价格 4、创建存储过程show_min_bprice,用来查看bookS的单价最低的价格&#xff0c;并将…

JS在线加密简述

JS在线加密&#xff0c;是指&#xff1a;在线进行JS代码混淆加密。通过混淆、压缩、加密等手段&#xff0c;使得JS源代码难以阅读和理解。从而可以有效防止代码被盗用或抄袭&#xff0c;保护开发者的知识产权和劳动成果。常用的JS在线加密网站有&#xff1a;JShaman、JS-Obfusc…

美业管理系统的优势和功能分析,美业系统你选对了吗?Java源码/演示视频分享

在当今竞争激烈的美业市场中&#xff0c;有效的管理对于提高效率、增强客户体验和推动业务增长至关重要。美业管理系统通过其各种功能和优势&#xff0c;成为现代美业企业不可或缺的利器。 本文将探讨美业管理系统的优势和功能&#xff0c;以及它们对美业企业的重要性。 1.预…

来聊聊Redis客户端的概念

写在文章开头 对于每一个建立的连接redis都会通过redisClient来管理建立的socket连接的信息&#xff0c;本文将从源码的分析的角度来剖析的Redis客户端的基本设计和实现。 Hi&#xff0c;我是 sharkChili &#xff0c;是个不断在硬核技术上作死的 java coder &#xff0c;是 C…