【Spring】深入探索 Spring AOP:概念、使用与实现原理解析

news2024/12/23 12:05:04

文章目录

  • 前言
  • 一、初识 Spring AOP
    • 1.1 什么是 AOP
    • 1.2 什么是 Spring AOP
  • 二、AOP 的核心概念
    • 2.1 切面(Aspect)
    • 2.2 切点(Pointcut)
    • 2.3 通知(Advice)
    • 2.4 连接点(Join Point)
  • 三、Spring AOP 的使用
    • 3.1 添加 Spring AOP框架支持
    • 3.2 定义切面和切点
    • 3.3 定义相关的通知方法
  • 四、Spring AOP 的实现原理
    • 4.1 动态代理
    • 4.2 JDK 动态代理
    • 4.3 CGLIB 动态代理
    • 4.4 JDK Proxy 和 CGLIB 动态代理的区别


前言

在现在的软件开发中,面向对象编程(Object-Oriented Programming,即 OOP)已经成为主流的编程范式,它以对象作为程序的基本构件单元,通过封装、继承和多态等特性来组织代码,实现可维护性和可扩展性的应用程序。但是,随着软件规模的不断扩大和复杂度的增加,OOP 在某些情况下可能就面临一些挑战了。

一个常见的问题是,业务逻辑和横切关注点(cross-cutting concerns)之间的耦合问题。所谓的 横切关注点就是那些在应用程序中分布广泛,与核心业务逻辑代码相互交织在一起的功能,例如登录判断、日志记录、事务管理、权限控制等 。将这些关注点与核心业务逻辑紧密耦合在一起会导致代码的重复和难以维护。因此,在这种情况下,一种面向切面编程(Aspect-Oriented Programming,简称 AOP)的编程范式应运而生。

一、初识 Spring AOP

在传统的面向对象编程中,我们通过封装、继承和多态等方式来组织代码,将相关的功能逻辑封装到对象的方法中。然而,在实际开发中,除了核心的业务逻辑,往往还有一些与之关系不大但却必要的功能,如日志记录、性能监测、安全控制等。这些功能通常会散布在代码中的多个地方,导致代码的重复性和难以维护性。

1.1 什么是 AOP

面向切面编程(Aspect-Oriented Programming,简称 AOP)正是为了解决这种代码重复性和难以维护的问题而产生的一种编程范式。AOP 的核心思想是将横切关注点(cross-cutting concerns)从主要的业务逻辑中分离出来,以模块化的方式进行管理。横切关注点是那些在应用程序中分布广泛、与核心业务逻辑交织在一起的功能,例如日志、事务、权限等。

AOP 将系统划分为两个主要部分:核心关注点和横切关注点核心关注点即应用程序的主要业务逻辑,而横切关注点则是与核心关注点无关但却必要的功能

AOP 通过将横切关注点抽象成称为切面(Aspect)的模块,然后将其独立于核心业务逻辑进行管理和维护。这样一来,我们可以在不修改主要业务逻辑的情况下,灵活地添加、修改或删除各种功能,从而提高了代码的可维护性和可扩展性。

1.2 什么是 Spring AOP

就如同 IoC 与 DI 之间的关系一样,AOP是一种思想,而 Spring AOP 则是 AOP 思想的一种实现。同时,Spring AOP也是 Spring 框架中的一个重要模块,它允许开发者能够轻松地实现面向切面编程。

简单来说,Spring AOP 建立在传统的 AOP 概念之上,提供了一种更简单和便捷方式来管理横切关注点。它通过代理技术在核心关注点的前、后或环绕执行切面的逻辑,从而实现了横切关注点的模块化。与传统的面向对象编程相比,使用 Spring AOP 可以更好地分离关注点,使得代码更加清晰、可维护性更高。

Spring AOP 支持两种类型的代理:基于接口的 JDK 动态代理基于类的 CGLIB 动态代理。它还提供了一系列的注解和配置选项,让开发者能够灵活地定义切面和通知(Advice),并将其应用到不同的目标对象上。

二、AOP 的核心概念

在面向切面编程(AOP)中,有几个核心概念是关键的,它们帮助我们理解如何实现横切关注点的管理和应用。这些概念包括切面、切点、通知和连接点,它们一起构成了 AOP 的基础。

2.1 切面(Aspect)

切面(Aspect)由切点(Pointcut)和通知(Advice)组成,它是对横切关注点的抽象化。它定义了在哪些切点上应该执行什么样的横切逻辑。切面是一个模块化的单元,它将通用的横切关注点从主要业务中分离出来,以便更好的管理和维护。

更加通俗地说,切面就相当于 Java 中的一个类,它代表了某一方面的具体内容,类似于一个代码模块。

  • 举例来说,我们可以把用户登录判断看作一个“切面”,其中包含了在用户登录过程中需要执行的逻辑。同样地,日志的统计记录也是一个“切面”,其中包括了在代码执行时需要记录日志的相关操作。
  • 切面就像是一个装有特定功能的工具箱,每个工具箱里都放着一组相关的操作。在代码中,切面定义了在哪些地方(切点)以及如何执行这些操作。当我们需要在不同的地方应用相似的功能时,我们可以创建一个切面,将相关的操作封装在里面。

2.2 切点(Pointcut)

切点(Pointcut)可以被理解为匹配连接点(Join Point)的谓词,或者说它是一组规则的集合,用于选择在哪些连接点上应用横切逻辑。切点使用 AspectJ pointcut expression language 来描述这些规则,该语言允许您灵活地定义匹配条件,以便精确地选择特定的连接点。

AspectJ pointcut expression language 是一种用于定义切点(Pointcut)的表达式语言,它是 AspectJ 框架中的一个关键部分。这个表达式语言允许我们灵活地描述在哪些连接点上应该应用通知(Advice)。

切点的作用就是为我们提供了一种方式来定义规则,以便筛选出满足条件的连接点。当连接点满足切点定义的规则时,我们就可以将通知(Advice)应用到这些连接点上,从而在这些特定的位置插入横切逻辑。这使得我们可以有选择地在代码中插入特定的行为,而不需要将横切关注点与主要业务逻辑耦合在一起。

2.3 通知(Advice)

在 AOP 中,切面不仅仅是一个抽象概念,它是有着明确目标的。切面需要完成特定的任务,而在 AOP 术语中,这些任务被称为通知(Advice)通知定义了切面的具体行为:它解决了切面是什么、何时使用以及在何时执行这些行为的问题

在 Spring 的切面类中,可以使用不同的注解来标记方法作为通知方法,这些通知方法在满足条件时会被调用:

  • 前置通知使用 @Before:通知方法会在目标方法调用之前执行,可以执行一些预处理操作。

  • 后置通知使用 @After:通知方法会在目标方法返回或抛出异常后调用,可以执行一些善后操作。

  • 返回之后通知使用 @AfterReturning:通知方法会在目标方法成功返回后调用,可以处理返回值。

  • 抛异常后通知使用 @AfterThrowing:通知方法会在目标方法抛出异常后调用,可以处理异常情况。

  • 环绕通知使用 @Around:通知包裹了被通知的方法,在目标方法调用之前和之后执行自定义的行为,也可以控制是否调用目标方法以及如何处理返回值。

2.4 连接点(Join Point)

连接点代表了程序执行过程中的实际点,它包括了所有能够触发切点的点。换句话说,连接点是切点在代码中的实际发生的实例。通知(Advice)会在连接点上执行,从而实现横切关注点的添加或修改。连接点是 AOP 中被拦截的点。

AOP 整个组成部分的概念如下图所示,以多个页面都要访问用户登录权限为例:

除了切面、切点、通知和连接点之外,AOP 还涉及一些其他重要的概念和术语。以下是一些相关的概念:

  1. 引入(Introduction): 引入是一种特殊类型的通知,它允许向现有的类添加新方法或字段。因此实现了可以将新功能引入到现有的类中,而无需修改其代码。

  2. 目标对象(Target Object): 目标对象是应用程序中的对象,被切面影响和拦截的对象。切面会在目标对象的连接点上应用通知。

  3. 代理(Proxy): 代理是切面对目标对象的包装。它允许切面将通知插入到目标对象的连接点上,实现横切逻辑。代理可以通过静态代理、JDK 动态代理或 CGLIB 动态代理来实现。

  4. 织入(Weaving): 织入是将切面与目标对象连接的过程。在编译时、加载时或运行时,切面的通知被插入到目标对象的连接点上,从而实现横切关注点的逻辑。

  5. 通知顺序(Advice Order): 如果在一个切点上有多个通知,通知的执行顺序可能很重要。通知顺序定义了多个通知在切点上执行的先后顺序。

  6. 切面优先级(Aspect Priority): 如果在应用中存在多个切面,切面的优先级可以影响通知的执行顺序。切面优先级决定了多个切面之间的先后执行顺序。

  7. 动态切面(Dynamic Aspects): 动态切面是在运行时根据某些条件确定是否要应用切面的一种机制。允许根据需要动态地选择是否将切面应用于目标对象。

三、Spring AOP 的使用

以上的概念可以说都是晦涩难懂的,下面通过使用 Spring AOP 来模拟实现一下 AOP 的功能,可以更好的帮助我们理解 AOP。此时我们完成的目标是拦截 UserController 中的所有方法,每次调用其中一个方法的时候,都会执行相应的通知事件。

使用 Spring AOP 的步骤如下:

  1. 添加 Spring AOP框架支持;
  2. 定义切面和切点;
  3. 定义相关的通知方法。

3.1 添加 Spring AOP框架支持

pom.xml 中添加如下配置:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.7.14</version>
</dependency>

3.2 定义切面和切点

切面代表的是某一方面的具体内容,比如用户登录权限的判断,而此处的切面就是对UserController类的处理。其中的就是就是定义具体的拦截规则。例如下面的代码:

@Aspect // 表示定义一个切面(类)
@Component
public class UserAspect {
    // 创建切点(方法)定义拦截规则
    @Pointcut("execution(public * com.example.demo.controller.UserController.*(..))")
    public void pointcut() {
    }
}

在这段代码中,创建了一个切面类 UserAspect,并使用 @Aspect 注解标记它为一个切面。同时,使用 @Component 注解将这个切面声明为一个 Spring 管理的组件,以便让 Spring 容器能够管理和识别它。

UserAspect 类中,使用 @Pointcut 注解定义了一个切点,该切点的拦截规则是匹配 UserController 类中的所有公共方法(public * com.example.demo.controller.UserController.*(..))。这意味着我们的切点会在 UserController 类的所有公共方法上触发。

需要注意的是,pointcut 方法的方法体可以为空,因为 @Pointcut 注解仅用于定义切点表达式,实际的逻辑代码会在通知方法中实现。

切点表达式说明:
切点表达式是用来定义切点的匹配规则,它决定了在哪些连接点上应用通知。AspectJ 支持三种通配符来构建切点表达式:

  • *:匹配任意字符,只匹配一个元素(包、类、或方法、方法参数)。
  • ..:匹配任意字符,可以匹配多个元素。在表示类时,必须和 * 联合使用,例如:com.cad..* 表示 com.cad 包下的所有子孙包中的所有类。
  • +:表示按照类型匹配指定类的所有类。必须跟在类名后面,如 com.cad.Car+ 表示继承该类的所有子类包括本身。

切点表达式通常使用 execution() 切点函数,它是最常用的用来匹配方法的切点函数。

切点表达式示例:
以下是一些切点表达式的示例,以便更好地理解如何构建切点规则:

  • execution(* com.cad.demo.User.*(..)):匹配 User 类中的所有方法。
  • execution(* com.cad.demo.User+.*(..)):匹配 User 类的子类中的所有方法,包括本身。
  • execution(* com.cad.*.*(..)):匹配 com.cad 包下所有类的所有方法。
  • execution(* com.cad..*.*(..)):匹配 com.cad 包下、以及其所有子孙包中的所有类的所有方法。
  • execution(* addUser(String, int)):匹配 addUser 方法,且第一个参数类型是 String,第二个参数类型是 int

3.3 定义相关的通知方法

通知定义的是被拦截的方法具体要执行的业务,比如用户登录权限验证方法就是具体要执行的业务,而此次只是进行简单的打印输操作。在 Spring AOP 中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本方法进行调用:

  • 前置通知使用 @Before:通知方法会在目标方法调用之前执行,可以执行一些预处理操作。

  • 后置通知使用 @After:通知方法会在目标方法返回或抛出异常后调用,可以执行一些善后操作。

  • 返回之后通知使用 @AfterReturning:通知方法会在目标方法成功返回后调用,可以处理返回值。

  • 抛异常后通知使用 @AfterThrowing:通知方法会在目标方法抛出异常后调用,可以处理异常情况。

  • 环绕通知使用 @Around:通知包裹了被通知的方法,在目标方法调用之前和之后执行自定义的行为,也可以控制是否调用目标方法以及如何处理返回值。

此处具体实现的通知代码如下:

// 创建一个切面(类)
@Aspect
@Component
public class UserAscept {
    // 创建切点(方法)定义拦截规则
    @Pointcut("execution(public * com.example.demo.controller.UserController.*(..))")
    public void pointcut() {
    }

    // 前置通知
    @Before("pointcut()")
    public void doBefore() {
        System.out.println("执行了前置通知:" + LocalDateTime.now());
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter() {
        System.out.println("执行了后置通知:" + LocalDateTime.now());
    }

    // 返回后通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
        System.out.println("执行了返回后通知:" + LocalDateTime.now());
    }

    // 抛异常后通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
        System.out.println("抛异常后通知:" + LocalDateTime.now());
    }

    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {

        Object proceed = null;
        System.out.println("Around 方法开始执行:" + LocalDateTime.now());
        try {
            // 执行拦截的方法
            proceed = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("Around 方法结束执行: " + LocalDateTime.now());
        return proceed;
    }
}

在这段代码中,定义了不同类型的通知方法,每个方法都使用了相应的注解标记,如 @Before@After@AfterReturning@AfterThrowing@Around。这些注解将通知方法与切点(使用 pointcut() 方法定义的切点)关联起来。当调用 UserController 中的方法时,就会被切点拦截,然后执行这些相应的通知方法。

关于上述环绕方法的说明:

其中,环绕通知在 Spring AOP 中是最为特殊和灵活的通知类型。它允许开发人员完全控制拦截的方法调用,包括在目标方法执行前后以及捕获异常时都能够插入自定义的逻辑。这种特性使得环绕通知非常强大,适用于需要全面控制横切逻辑的场景。

在上述代码中展示了如何实现环绕通知。环绕通知使用 @Around 注解标记,通知方法的参数是一个 ProceedingJoinPoint 对象,它允许我们在适当的时候调用 proceed() 方法来执行拦截的方法。

在这个环绕通知方法中:

  1. 方法的参数 joinPoint 是一个 ProceedingJoinPoint 对象,它代表了目标方法的调用点,可以在需要的时候调用 proceed() 方法来执行拦截的方法。

  2. 可以在 proceed() 方法调用前后插入自定义的逻辑,实现特定的横切行为。

  3. 可以捕获可能抛出的异常,对异常情况进行处理。

  4. 通过返回值,您可以控制目标方法的返回值或进行适当的返回值处理。

四、Spring AOP 的实现原理

4.1 动态代理

Spring AOP 是建立在动态代理的基础之上的,因此它的支持范围局限于方法级别的拦截。这意味着 Spring AOP 主要用于拦截和管理方法调用,而不能直接拦截类级别的操作。

在 Spring AOP 中,有两种方式来实现动态代理:JDK Proxy 和 CGLIB。默认情况下,Spring AOP 会根据目标类是否实现接口来选择使用哪种代理方式:

  • JDK Proxy: 如果目标类实现了接口,Spring AOP 将使用 JDK Proxy 来生成代理类。JDK Proxy 基于 Java 标准库中的 java.lang.reflect.Proxy 类实现,要求目标类必须实现至少一个接口。

  • CGLIB: 如果目标类没有实现接口,Spring AOP 将使用 CGLIB 来生成代理类。CGLIB 是一个功能强大的第三方库,它可以在运行时动态地创建类的子类,以实现代理。

这意味着,当使用 Spring AOP 时,如果目标类实现了接口,Spring 将会使用 JDK Proxy 来创建代理。如果目标类没有实现接口,Spring 将使用 CGLIB 来创建代理。在某些情况下,您可能需要注意 CGLIB 代理可能会带来的一些细微影响,例如 final 方法无法被拦截等。

4.2 JDK 动态代理

JDK Proxy 是 Java 标准库中的一种动态代理机制。 它基于目标类实现的接口来创建代理对象,从而在方法调用前后插入横切逻辑。JDK Proxy 主要用于拦截实现了接口的类的方法调用。

JDK Proxy 的实现方式:

  1. 首先,通过实现 InvocationHandler 接口创建一个方法调用处理器。这个处理器定义了在拦截方法调用时要执行的逻辑。
  2. 然后,使用 Proxy 类来创建代理对象。Proxy 类的 newProxyInstance() 方法接受一个 ClassLoader、一个接口数组和一个 InvocationHandler 对象作为参数,然后动态生成代理类的字节码,创建代理实例。

JDK Proxy 的限制:

  • JDK Proxy 要求目标类必须实现至少一个接口。它无法直接拦截没有实现接口的类的方法调用。
  • JDK Proxy 创建的代理对象实现了目标类实现的接口,因此代理对象只能调用接口中定义的方法。

4.3 CGLIB 动态代理

CGLIB 是一个功能强大的第三方库,用于在运行时创建类的子类,从而实现代理。 它的主要特点在于可以在运行时动态地生成类的子类,而不需要目标类实现接口,从而实现拦截和增强目标类的方法调用。

CGLIB 的特点和优势:

  • CGLIB 可以代理未实现接口的类。这使得它适用于更广泛的场景,包括那些没有接口的类。
  • CGLIB 使用继承机制来实现代理,生成目标类的子类,并在子类中插入横切逻辑。因此,它可以拦截目标类的所有方法,无论是实例方法还是静态方法。
  • 由于使用继承来生成代理类,CGLIB 无法代理被声明为 final 的方法。这是一个需要注意的限制。
  • CGLIB 的代理速度通常比 JDK Proxy 稍慢,因为它涉及到创建和加载代理类的字节码。

4.4 JDK Proxy 和 CGLIB 动态代理的区别

当使用 Spring AOP 创建代理时,可以选择使用 JDK Proxy 或 CGLIB 来实现动态代理。这两种代理方式有一些区别,以下是它们的总结:

JDK Proxy:

  • 基于 Java 标准库中的 java.lang.reflect.Proxy 类实现。
  • 要求目标类必须实现至少一个接口,因为它基于接口来创建代理。
  • 通过实现代理接口的方法,在方法调用前后插入横切逻辑。
  • 由于使用接口作为代理的基础,生成的代理对象只能调用接口中定义的方法。
  • 通常情况下,JDK Proxy 的性能相对较高。

CGLIB:

  • 是一个第三方库,用于在运行时生成类的子类实现代理。
  • 不需要目标类实现接口,可以代理未实现接口的类。
  • 通过继承机制,在目标类的子类中插入横切逻辑。
  • 能够拦截目标类的所有方法,包括实例方法和静态方法。
  • 由于使用继承和生成代理类的字节码,CGLIB 无法代理被声明为 final 的方法。
  • 代理速度相对较慢,因为涉及生成和加载代理类的字节码。

如何选择:

  • 如果目标类实现了接口,首选使用 JDK Proxy。它提供了更高的性能,适用于基于接口的代理。
  • 如果目标类没有实现接口,或者需要拦截未实现接口的类,可以选择使用 CGLIB。它更加灵活,适用于更广泛的代理场景。

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

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

相关文章

Android复习(Android基础-四大组件)——Broadcast

1. 广播分类 广播的发送方式&#xff1a;标准广播、有序广播、粘性广播广播的类型&#xff1a;系统广播、本地广播 1.1 标准广播 完全异步&#xff0c;无序的广播发出后&#xff0c;所有的广播接收器几乎都会在同一时间收到消息。&#xff08;异步&#xff09;但是消息无法截…

科技云报道:算力之战,英伟达再度释放AI“炸弹”

科技云报道原创。 近日&#xff0c;在计算机图形学顶会SIGGRAPH 2023现场&#xff0c;英伟达再度释放深夜“炸弹”&#xff0c;大模型专用芯片迎来升级版本。 英伟达在会上发布了新一代GH200 Grace Hopper平台&#xff0c;该平台依托于搭载全球首款搭载HBM3e处理器的新型Grac…

Java并发之ReentrantLock

AQS AQS&#xff08;AbstractQueuedSynchronizer&#xff09;&#xff1a;抽象队列同步器&#xff0c;是一种用来构建锁和同步器的框架。在是JUC下一个重要的并发类&#xff0c;例如&#xff1a;ReentrantLock、Semaphore、CountDownLatch、LimitLatch等并发都是由AQS衍生出来…

ElementUI的MessageBox的按钮置灰且不可点击

// this.$confirmthis.$alert(这是一段内容, 标题名称, {confirmButtonText: 确定,confirmButtonCLass: confirmButton,beforeClose: (action,instance,done) > {if (action confirm) {return false} else {done()}});}.confirmButton {background: #ccc !important;cursor…

淘宝API接口的实时数据和缓存数据区别

电商API接口实时数据是指通过API接口获取到的与电商相关的实时数据。这些数据可以包括商品库存、订单状态、销售额、用户活跃度等信息。 通过电商API接口&#xff0c;可以实时获取到电商平台上的各种数据&#xff0c;这些数据可以帮助企业或开发者做出及时的决策和分析。例如&…

16.3.1 【Linux】程序的观察

既然程序这么重要&#xff0c;那么我们如何查阅系统上面正在运行当中的程序呢&#xff1f;利用静态的 ps 或者是动态的 top&#xff0c;还能以 pstree 来查阅程序树之间的关系。 ps &#xff1a;将某个时间点的程序运行情况撷取下来 仅观察自己的 bash 相关程序&#xff1a; p…

安达发APS|APS排产软件之计划甘特图

在当今全球化和竞争激烈的市场环境下&#xff0c;制造业企业面临着巨大的压力&#xff0c;如何在保证产品质量、降低成本以及满足客户需求的同时&#xff0c;提高生产效率和竞争力成为企业需要迫切解决的问题。在这个背景下&#xff0c;生产计划的制定和执行显得尤为重要。然而…

Spring Boot 集成 XXL-JOB 任务调度平台

一、下载xxl-job并使用。 二、将xxl-job集成到springboot里 一、 下载xxl-job并使用。 这一步没完成的请参考这个博客&#xff1a;http://t.csdn.cn/lsp4r 二、将xxl-job集成到springboot里 1、引入依赖 <dependency><groupId>org.springframework.boot</group…

单模光纤模场强度分布以及高斯近似的MATLAB仿真

已知纤芯半径5um&#xff0c;数值孔径NA 0.1&#xff0c;波长 用波长和数值孔径计算归一化常数V 之前我们在单模光纤特征方程及其MATLAB数值求解中&#xff0c;用线性关系拟合过V和W&#xff0c;这里直接用拟合结果 U用V和W计算 clc clear close alla 5e-6;%纤芯半径 NA …

ssm+vue小型企业办公自动化系统源码和论文PPT

ssmvue小型企业办公自动化系统源码和论文PPT013 开发工具&#xff1a;idea 数据库mysql5.7(mysql5.7最佳) 数据库链接工具&#xff1a;navcat,小海豚等 开发技术&#xff1a;java ssm tomcat8.5 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xf…

Web服务器项目一

文章目录 是什么HTTP协议——应用层协议服务器基本框架两种高效的处理模式线程池 是什么 Web服务器是一个服务器软件程序&#xff0c;主要功能是通过HTTP协议与客户端&#xff08;通常是浏览器Browser)进行通信&#xff0c;来接收&#xff0c;存储&#xff0c;处理来自客户端的…

npm run xxx 的时候发生了什么?(以npm run dev举例说明)

文章目录 一、去package.json寻找scripts对应的命令二、去node_modules寻找vue-cli-service三、从package-lock.json获取.bin的软链接1. bin目录下的那些软连接存在于项目最外层的package-lock.json文件中。2.vue-cli-service文件的作用3.npm install 的作用 总结 一、去packag…

java springboot驾校管理系统

开发技术&#xff1a; SpringBootspringmvcMybitis-Puls mysql5.x html ajax数据交互 开发工具&#xff1a; idea或eclipse jdk1.8 maven (1) 管理员登陆 (2) 所有学员信息列表查询 &#xff08;3&#xff09;所有学生考试信息 &#xff08;4&#xff09;学员…

Datawhale Django入门组队学习Task01

Task01 一.创建虚拟环境 python -m venv django_learn &#xff08;django_learn那里是自己定的环境名字&#xff09; 之前一直用conda管理虚拟环境&#xff0c;没咋用过virtualenv&#xff0c;然后我的powershell之前也设置了默认启动了base环境&#xff0c;然后输入activat…

目标检测(Object Detection)

文章目录 1. 目标检测1.1 目标检测简要概述及名词解释1.2 IOU1.3 TP TN FP FN1.4 precision&#xff08;精确度&#xff09;和recall&#xff08;召回率&#xff09; 2. 边框回归Bounding-Box regression3. Faster R-CNN3.1 Faster-RCNN&#xff1a;conv layer3.2 Faster-RCNN&…

如何搭建个人邮件服务hmailserver并实现远程发送邮件

文章目录 1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpolar内网映射工…

Destination Host Unreachable

背景&#xff1a;物理机的IP地址是192.168.31.189&#xff0c;虚拟机的IP地址是192.168.194.130 物理机ping得通虚拟机 虚拟机ping得通外网 可是虚拟机ping不通物理机 1、报错信息 Destination Host Unreachable 2、原因 用route -n命令查看路由表发现192.168.194.0没有走网…

设计HTML5图像和多媒体

在网页中的文本信息直观、明了&#xff0c;而多媒体信息更富内涵和视觉冲击力。恰当使用不同类型的多媒体可以展示个性&#xff0c;突出重点&#xff0c;吸引用户。在HTML5之前&#xff0c;需要借助插件为网页添加多媒体&#xff0c;如Adobe Flash Player、苹果的QuickTime等。…

R语言画图的-- ggplot2(实现图例的精细修改)

文章目录 1. 图的精确修改theme函数实现图的精细修改实现一页多图具体作图中的参数修改(某些特殊的参数)柱状图的参数修改 ggplot2是R中用来作图的很强的包&#xff0c;但是其用法比较大且各种参数比较复杂&#xff0c;我自己使用的时候还经常需要查阅一些关键参数等&#xff0…