Spring - 13 ( 11000 字 Spring 入门级教程 )

news2025/4/16 3:13:57

一: Spring AOP

备注:之前学习 Spring 学到 AOP 就去梳理之前学习的知识点了,后面因为各种原因导致 Spring AOP 的博客一直搁置。。。。。。下面开始正式的讲解。

学习完 Spring 的统一功能后,我们就进入了 Spring AOP 的学习。Spring 的第一大核心是 IoC,而 AOP 则是 Spring 框架的第二大核心,意思是面向切面编程。AOP 的核心思想是针对特定问题进行集中处理,例如我们之前的 “登录校验” 就是一个典型的特定问题,我们当时是通过校验拦截器实现这一问题的统一处理。因此,拦截器可以被视为 AOP 思想的一种具体应用。 AOP 作为一种思想,为开发提供了结构化的方式,而拦截器则是实现这一思想的重要工具。

1.1 AOP 概述

什么是 Spring AOP?AOP 是一种思想,其实现方式有很多,包括 Spring AOP、AspectJ、CGLIB 等。其中 Spring AOP 是众多实现方式之一。学习了 Spring 的一些统一功能后,很多人可能会误认为这就完全掌握了 Spring AOP,实际上并非如此。AOP 的作用维度更为细致,能够根据包、类、方法名和参数等进行拦截,以实现更加复杂的业务逻辑。

例如,在一个项目中,我们开发了多个业务功能,但发现某些业务处理的执行效率较低,需要进行优化。第一步是定位执行耗时较长的业务方法,然后针对这些方法进行优化。为了实现这一点,我们需要统计每个业务方法的执行时间。我们可以在业务方法运行前后记录开始时间和结束时间,计算得出方法的耗时。

在这里插入图片描述

虽然这个方法在理论上可行,但在实际开发中,每个业务模块可能包含多个接口,每个接口又有许多方法。如果要在每个方法中都添加耗时统计代码,将增加开发人员的工作量。而 AOP 可以在不修改原始代码的情况下,对特定方法进行功能增强,实现无侵入性设计,降低代码耦合度。接下来,我们将深入了解 Spring AOP 如何实现这一点。

在这里插入图片描述

1.2 Spring AOP 快速入门

首先需要在 pom.xml 中引入 AOP 依赖才能使用 AOP 的相关功能:

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

导入依赖后开始编写 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;

@Slf4j
// 声明当前类为切面类。
@Aspect
@Component
public class TimeAspect {

    /**
     * 记录方法耗时
     */
     // 定义环绕通知,拦截目标方法并在其前后执行自定义逻辑。
     // execution:匹配方法执行的连接点。*:通配符,匹配任意返回类型、方法名、参数。
     // com.example.demo.controller.*.*(..):拦截 controller 包下所有类的所有方法,参数不限。
     // ProceedingJoinPoint pjp 封装了被拦截的目标方法信息(如方法名、参数、目标对象等)。
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        // 记录方法执行开始时间
        long begin = System.currentTimeMillis();
        
        // 执行原始方法,pjp.proceed() 调用被拦截的目标方法,这是环绕通知中必须执行的步骤,否则目标方法不会运行。
        Object result = pjp.proceed();
        
        // 记录方法执行结束时间
        long end = System.currentTimeMillis();
        
        // 记录方法执行耗时
        log.info(pjp.getSignature() + " 执行耗时: {}ms", end - begin);
        
        return result;
    }
}

在这里插入图片描述

1.3 Spring AOP 详解

1.3.1 切点

切点也称为 “切入点”,切点用于定义一组规则,告知程序需要对哪些方法进行功能增强,指定作用域和作用方式

在这里插入图片描述

1.3.2 连接点

连接点是指满足切点表达式规则的方法,它代表了可以被 AOP 控制的具体方法。连接点与切点之间的关系是:连接点是符合切点表达式的特定元素,而切点则可以看作是包含多个连接点的集合。例如,全体教师可以看作是一个切点,而张三老师或者李四老师就可以看作是一个连接点。

1.3.3 通知

通知是指具体要执行的工作,通常指那些重复的逻辑或共性功能,最终体现为一个方法。在 AOP 中,我们将这部分重复的代码逻辑进行抽取并单独定义,这些抽取出的代码便构成了通知的内容。

在这里插入图片描述

1.3.4 切面

切面是切点与通知的结合,通过切面可以描述 AOP 程序在针对哪些方法时,何时执行什么样的操作。切面不仅包含通知逻辑的定义,还包括连接点的定义。通常,将包含切面的类称为切面类,这些类通过 @Aspect 注解进行标识。

在这里插入图片描述

1.4 通知类型

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

注解描述
@Around环绕通知,标注的方法在目标方法前后都会被执行。
@Before前置通知,标注的方法在目标方法之前执行。
@AfterReturning返回后通知,标注的方法在目标方法正常返回后执行,异常时不会执行。
@After后置通知,标注的方法在目标方法之后执行,无论是否有异常都会执行。
@AfterThrowing异常后通知,标注的方法在目标方法抛出异常后执行。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class AspectDemo {

    // 前置通知
    @Before("execution(* com.example.demo.controller.*.*(..))")
    public void doBefore() {
        log.info("执行 Before 方法");
    }

    // 后置通知
    @After("execution(* com.example.demo.controller.*.*(..))")
    public void doAfter() {
        log.info("执行 After 方法");
    }

    // 返回后通知
    @AfterReturning("execution(* com.example.demo.controller.*.*(..))")
    public void doAfterReturning() {
        log.info("执行 AfterReturning 方法");
    }

    // 抛出异常后通知
    @AfterThrowing("execution(* com.example.demo.controller.*.*(..))")
    public void doAfterThrowing() {
        log.info("执行 doAfterThrowing 方法");
    }

    // 环绕通知
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Around 方法开始执行");
        Object result = joinPoint.proceed();
        log.info("Around 方法结束执行");
        return result;
    }
}
package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/test")
@RestController
public class TestController {

    @RequestMapping("/t1")
    public String t1() {
        return "t1";
    }

    @RequestMapping("/t2")
    public boolean t2() {
        int a = 10 / 0;
        return true;
    }
}
  1. 访问 http://127.0.0.1:8080/test/t1,此时是正常的运行情况:

在这里插入图片描述
在这里插入图片描述

  1. 访问 http://127.0.0.1:8080/test/t2,此时是异常的运行情况:

在这里插入图片描述
在这里插入图片描述

此处需要注意 @AfterReturning 方法不会被执行,而 @AfterThrowing 方法则会被执行。此外,在 @Around 环绕通知中只有头部部分会被执行。下面是别的注意事项:

  • 一个切面类可以定义多个切点。
  • @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 方法以便执行原始方法,而其他通知不需要考虑目标方法是否执行。
  • @Around 环绕通知方法的返回值必须指定为 Objec 以便接收原始方法的返回值;否则在原始方法执行完毕后无法获取返回值。

1.5 @PointCut

上述代码存在一个问题,即大量重复使用切点表达式 execution(* com.example.demo.controller..(…))。为了解决这个问题,Spring 提供了 @Pointcut 注解,可以将公共的切点表达式提取出来。这样,在需要使用时只需引用该切入点表达式即可。因此,可以对代码进行如下修改:

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

@Slf4j
@Aspect
@Component
public class AspectDemo {

    // 定义切点(公共的切点表达式)
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt() {}

    // 前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 Before 方法");
    }

    // 后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 After 方法");
    }

    // 返回后通知
    @AfterReturning("pt()")
    public void doAfterReturning() {
        log.info("执行 AfterReturning 方法");
    }

    // 抛出异常后通知
    @AfterThrowing("pt()")
    public void doAfterThrowing() {
        log.info("执行 doAfterThrowing 方法");
    }

    // 添加环绕通知
    @Around("pt()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Around 方法开始执行");
        Object result = joinPoint.proceed();
        log.info("Around 方法结束执行");
        return result;
    }
}

当切点定义使用 private 修饰时,该切点只能在当前切面类中使用。如果其他切面类也需要使用这个切点定义,则需要将其访问修饰符改为 public。引用时的格式为:全限定类名.方法名()。

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

@Slf4j
@Aspect
@Component
public class AspectDemo2 {

    // 前置通知
    @Before("com.example.demo.aspect.AspectDemo.pt()")
    public void doBefore() {
        log.info("执行 AspectDemo2 -> Before 方法");
    }
}

1.6 切面优先级 @Order

在一个项目中如果定义了多个切面类,并且它们的多个切入点都匹配到同一个目标方法,当该方法执行的时候,这些切面类中的通知方法将依次执行,那么此时这几个通知方法的执行顺序是怎么样的呢?我们看看下面的这段代码:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class AspectDemo2 {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt() {}

    // 前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 AspectDemo2 -> Before 方法");
    }

    // 后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 AspectDemo2 -> After 方法");
    }
}

@Slf4j
@Aspect
@Component
public class AspectDemo3 {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt() {}

    // 前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 AspectDemo3 -> Before 方法");
    }

    // 后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 AspectDemo3 -> After 方法");
    }
}

@Slf4j
@Aspect
@Component
public class AspectDemo4 {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt() {}

    // 前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 AspectDemo4 -> Before 方法");
    }

    // 后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 AspectDemo4 -> After 方法");
    }
}

访问 http://127.0.0.1:8080/test/t1:

在这里插入图片描述
在这里插入图片描述

通过上述程序的运行结果可以看出:在存在多个切面类的情况下,切面通知的执行顺序默认是按照类名的字母顺序排列的:对于 @Before 通知,字母排名靠前的切面会优先执行;对于 @After 通知,字母排名靠前的切面则会后执行。然而,这种方式不便于管理,特别是当类名具有实际意义时。为了解决这个问题,Spring 提供了 @Order 注解,用于显式控制切面通知的执行顺序。使用该注解可以根据需要定义切面类的优先级。

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
@Order(2)
public class AspectDemo2 {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt() {}

    // 前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 AspectDemo2 -> Before 方法");
    }

    // 后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 AspectDemo2 -> After 方法");
    }
}

@Slf4j
@Aspect
@Component
@Order(1)
public class AspectDemo3 {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt() {}

    // 前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 AspectDemo3 -> Before 方法");
    }

    // 后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 AspectDemo3 -> After 方法");
    }
}

@Slf4j
@Aspect
@Component
@Order(3)
public class AspectDemo4 {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt() {}

    // 前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行 AspectDemo4 -> Before 方法");
    }

    // 后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 AspectDemo4 -> After 方法");
    }
}

访问 http://127.0.0.1:8080/test/t1:
在这里插入图片描述

在这里插入图片描述

通过上述程序的运行结果可以得出结论:使用 @Order 注解标识的切面类,其执行顺序如下:对于 @Before 通知,数字越小的切面优先执行;而对于 @After 通知,数字越大的切面则优先执行。换句话说,@Order 注解控制了切面的优先级,优先执行优先级较高的切面,随后执行优先级较低的切面,最终执行目标方法。

在这里插入图片描述

1.7 切点表达式

在上述代码中我们一直使用切点表达式来描述切点。接下来,我们将介绍切点表达式的语法。常见的切点表达式主要有两种形式:

  1. execution(??):根据方法的签名进行匹配。
  2. @annotation(??):根据方法上的注解进行匹配。

1.7.1 execution 表达式

execution() 是最常用的切点表达式,用于匹配方法,其语法为如下图所示,其中访问修饰符和异常可以省略。切点表达式支持通配符,主要包括两种:
好的,感谢你的提醒。以下是重新整理后的内容,去掉了反引号和加粗的格式:

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

    • 包名使用 * 表示任意包。仅匹配一层包。
    • 类名使用 * 表示任意类。
    • 返回值使用 * 表示任意返回值类型。
    • 方法名使用 * 表示任意方法。
    • 参数使用 * 表示一个任意类型的参数。
  2. 使用 .. :匹配多个连续的任意符号,可以通配任意层级的包,或任意类型、任意数量的参数。

    • 使用 .. 配置包名,标识此包及其所有子包。
    • 可以使用 .. 配置参数,表示任意个、任意类型的参数。

在这里插入图片描述

// 匹配 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..*(..))

1.7.2 @annotation

execution 表达式更适用有规则的,如果我们要匹配多个无规则的方法呢,这个时候我们使用 execution 这种切点表达式来描述就不是很方便了,我们可以借助自定义注解的方式以及另⼀种切点表达式 @annotation 来描述这⼀类的切点,@annotation 描述切点的方步骤如下:

  1. 编写自定义注解。
  2. 使用 @annotation 表达式来定义切点。
  3. 在连接点的方法上添加自定义注解。
1.7.2.1 自定义注解 @MyAspect

创建一个注解类的流程与创建普通类类似,只需选择 Annotation 类型即可。

在这里插入图片描述

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

// 定义自定义注解 MyAspect
@Target(ElementType.METHOD)  // 注解适用于方法
@Retention(RetentionPolicy.RUNTIME)  // 注解在运行时可用
public @interface MyAspect {
    // 注解属性可以根据需要进行定义
    String value() default "";  // 默认属性
}

这段代码比较简单,我们这里简单讲解了解即可,@Target 标识了 Annotation 所修饰的对象范围,即该注解可以用在什么地方,@Retention 指 Annotation 被保留的时间长短,标明注解的生命周期。

ElementType描述
ElementType.TYPE用于描述类、接口(包括注解类型)或 enum 声明
ElementType.METHOD`描述方法
ElementType.PARAMETER描述参数
ElementType.TYPE_USE可以标注任意类型
RetentionPolicy描述
RetentionPolicy.SOURCE表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使用。
RetentionPolicy.CLASS编译时注解,表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时无法获取。
RetentionPolicy.RUNTIME运行时注解,表示注解存在于源代码、字节码和运行时中。这意味着在编译时、字节码中和实际运行时都可以通过反射获取到该注解的信息。
1.7.2.2 编写切面类并添加自定义注解

使用 @annotation 切点表达式定义切点,只对 @MyAspect 生效,切面类代码如下:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspectDemo {

    // 前置通知
    @Before("@annotation(com.example.demo.aspect.MyAspect)")
    public void before() {
        log.info("MyAspect -> before ...");
    }

    // 后置通知
    @After("@annotation(com.example.demo.aspect.MyAspect)")
    public void after() {
        log.info("MyAspect -> after ...");
    }
}

添加自定义注解 @MyAspect:

@MyAspect
@RequestMapping("/t1")
public String t1() {
    return "t1";
}

访问 http://127.0.0.1:8080/test/t1:

在这里插入图片描述

在这里插入图片描述

1.8 Spring AOP 的实现方式

方法描述
基于注解 @Aspect利用 Spring AOP 提供的 @Aspect 注解功能来实现切面编程。
基于自定义注解使用自定义注解结合 @annotation 来定义切点,灵活地控制切面的执行。
基于 Spring API通过 XML 配置的方式实现 AOP,这种方法在 Spring Boot 广泛使用之后几乎被淘汰,但可以作为补充了解。
基于代理实现一种较早的实现方式,通过代理方式实现 AOP,写法相对繁琐,不推荐使用。

1.9 Spring AOP 原理

偷个懒,下次有空了再补。。。。。。

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

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

相关文章

Spring Cloud Alibaba微服务治理实战:Nacos+Sentinel深度解析

一、引言 在微服务架构中&#xff0c;服务发现、配置管理、流量控制是保障系统稳定性的核心问题。Spring Cloud Netflix 生态曾主导微服务解决方案&#xff0c;但其部分组件&#xff08;如 Eureka、Hystrix&#xff09;已进入维护模式。 Spring Cloud Alibaba 凭借 高性能、轻…

红宝书第三十六讲:持续集成(CI)配置入门指南

红宝书第三十六讲&#xff1a;持续集成&#xff08;CI&#xff09;配置入门指南 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、什么是持续集成&#xff1f; 持续集成&#xff08;CI&#xff09;就像咖啡厅的…

Java—HTML:3D形变

今天我要介绍的是在Java HTML中CSS的相关知识点内容之一&#xff1a;3D形变&#xff08;3D变换&#xff09;。该内容包含透视&#xff08;属性&#xff1a;perspective&#xff09;&#xff0c;3D变换&#xff0c;3D变换函数以及案例演示&#xff0c; 接下来我将逐一介绍&…

什么是音频预加重与去加重,预加重与去加重的原理是什么,在什么条件下会使用预加重与去加重?

音频预加重与去加重是音频处理中的两个重要概念&#xff0c;以下是对其原理及应用条件的详细介绍&#xff1a; 1、音频预加重与去加重的定义 预加重&#xff1a;在音频信号的发送端&#xff0c;对音频信号的高频部分进行提升&#xff0c;增加高频信号的幅度&#xff0c;使其在…

免费下载 | 2025清华五道口:“十五五”金融规划研究白皮书

《2025清华五道口&#xff1a;“十五五”金融规划研究白皮书》的核心内容主要包括以下几个方面&#xff1a; 一、五年金融规划的重要功能与作用 凝聚共识&#xff1a;五年金融规划是国家金融发展的前瞻性谋划和战略性安排&#xff0c;通过广泛听取社会各界意见&#xff0c;凝…

微信小程序实战案例 - 餐馆点餐系统 阶段 4 - 订单列表 状态

✅ 阶段 4 – 订单列表 & 状态 目标 展示用户「我的订单」列表支持状态筛选&#xff08;全部 / 待处理 / 已完成&#xff09;支持分页加载和实时刷新使用原生组件编写 ✅ 1. 页面结构&#xff1a;文件结构 pages/orders/├─ index.json├─ index.wxml├─ index.js└─…

如何为C++实习做准备?

博主介绍&#xff1a;程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章&#xff0c;首发gzh&#xff0c;见文末&#x1f447;&#x1…

Cesium.js(6):Cesium相机系统

Camera表示观察场景的视角。通过操作摄像机&#xff0c;可以控制视图的位置、方向和角度。 帮助文档&#xff1a;Camera - Cesium Documentation 1 setView setView 方法允许你指定相机的目标位置和姿态。你可以通过 Cartesian3 对象来指定目标位置&#xff0c;并通过 orien…

AI 代码生成工具如何突破 Java 单元测试效能天花板?

一、传统单元测试的四大痛点 时间黑洞&#xff1a;根据 JetBrains 调研&#xff0c;Java 开发者平均花费 35% 时间编写测试代码覆盖盲区&#xff1a;手工测试覆盖率普遍低于 60%&#xff08;Jacoco 全球统计数据&#xff09;维护困境&#xff1a;业务代码变更导致 38% 的测试用…

客户端负载均衡与服务器端负载均衡详解

客户端负载均衡与服务器端负载均衡详解 1. 客户端负载均衡&#xff08;Client-Side Load Balancing&#xff09; 核心概念 定义&#xff1a;负载均衡逻辑在客户端实现&#xff0c;客户端主动选择目标服务实例。典型场景&#xff1a;微服务内部调用&#xff08;如Spring Cloud…

基于springboot的“流浪动物管理系统”的设计与实现(源码+数据库+文档+PPT)

基于springboot的“流浪动物管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;springboot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 局部E-R图 系统首页界面 系统…

爬虫解决debbugger之替换文件

鼠鼠上次做一个网站的时候&#xff0c;遇到的debbugger问题&#xff0c;是通过打断点然后编辑断点解决的&#xff0c;现在鼠鼠又学会了一个新的技能 首先需要大家下载一个reres的插件&#xff0c;这里最好用谷歌浏览器 先请大家看看案例国家水质自动综合监管平台 这里我们只…

奇怪的电梯——DFS算法

题目 题解 每到一层楼都面临了两种选择&#xff1a;上还是下&#xff1f;因此我们可以定义一个布尔数组用来记录选择。 终止条件其实也明显&#xff0c;要么到了B层&#xff0c;要么没有找到楼层。 如果找到了&#xff0c;选择一个步骤少的方式。又怎么表示没有找到楼层&…

Open GL ES-> 工厂设计模式包装 SurfaceView + 自定义EGL的OpenGL ES 渲染框架

XML文件 <?xml version"1.0" encoding"utf-8"?> <com.example.myapplication.EGLSurfaceView xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"…

深入解析多表联查(MySQL)

前言 在面试中以及实际开发中&#xff0c;多表联查是每个程序员必备技能&#xff0c;下文通过最简单的学生表和课程表的实例帮大家最快入门多表联查技能。 建立数据表 1. 学生表&#xff08;students&#xff09; 创建学生表 CREATE TABLE students (student_id INT AUTO_…

宇视设备视频平台EasyCVR打造智慧酒店安防体系,筑牢安全防线

一、需求背景 酒店作为人员流动频繁的场所&#xff0c;对安全保障与隐私保护有着极高的要求。为切实维护酒店内部公共区域的安全秩序&#xff0c;24小时不间断视频监控成为必要举措。通常情况下&#xff0c;酒店需在本地部署视频监控系统以供查看&#xff0c;部分连锁酒店还希…

Linux中的文件传输(附加详细实验案例)

一、实验环境的设置 ①该实验需要两台主机&#xff0c;虚拟机名称为 L2 和 L3 &#xff0c;在终端分别更改主机名为 node1 和 node2&#xff0c;在实验过程能够更好分辨。 然后再重新打开终端&#xff0c;主机名便都更改了相应的名称。 ②用 ip a 的命令分别查看两个主机的 …

基于 OpenHarmony 5.0 的星闪轻量型设备应用开发——Ch2 OpenHarmony LiteOS-M 内核应用开发

写在前面&#xff1a; 此篇是系列文章《基于 OpenHarmony5.0 的星闪轻量型设备应用开发》的第 2 章。本篇介绍了如何在 OpenHarmony 5.0 框架下&#xff0c;针对 WS63 进行 LiteOS-M 内核应用工程的开发。 为了方便读者学习&#xff0c;需要OpenHarmony 5.0 WS63 SDK 的小伙伴可…

Linux--线程概念与控制

目录 1. Linux线程概念 1-1 什么是线程 1-2 分⻚式存储管理 1-2-1 虚拟地址和⻚表的由来 1-2-2 物理内存管理 1-2-3 ⻚表 1-2-4 ⻚⽬录结构 1-2-5 两级⻚表的地址转换 1-2-6 缺⻚异常 1-3 线程的优点 1-4 线程的缺点 1-5 线程异常 1-6 线程⽤途 2. Linux进程VS线…

Python | kelvin波的水平空间结构

写在前面 简单记录一下之前想画的一个图&#xff1a; 思路 整体比较简单&#xff0c;两个子图&#xff0c;本质上就是一个带有投影&#xff0c;一个不带投影&#xff0c;通常用在EOF的空间模态和时间序列的绘制中&#xff0c;可以看看之前的几个详细的画法。 Python | El Ni…