Spring AOP概念及其实现

一、什么是AOP

全称Aspect Oriented Programming,即面向切面编程,AOP是Spring框架的第二大核心,第一大为IOC。什么是面向切面编程?切面就是指某一类特定的问题,所以AOP也可以称为面向特定方法编程。例如对异常的统一处理,简单来说,AOP是一种思想,是对某一类问题的集中处理。AOP的优势在于程序运行期间在不修改源码的基础上对已有的方法进行增强(无侵入性)

二、什么是Spring AOP

AOP是一种思想,它的实现方法有很多,有Spring AOP,也有AspectJ,CGLIB等,Spring AOP只是其中的一种实现方式。

三、Spring AOP入门

首先在pom文件中引入依赖:

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

编写AOP程序

@Slf4j
@Aspect
@Component
public class TimeAspect {@Around("execution(* com.example.bookManagement.Controller.*.*(..))")public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {//记录开始时间long startTime = System.currentTimeMillis();//执行原始方法Object result = joinPoint.proceed();//记录方法执行时间long endTime = System.currentTimeMillis();long elapsedTime = endTime - startTime;log.info(joinPoint.getSignature()+"耗时: {} ms", elapsedTime);return result;}
}

运行程序,观察Controller方法运行时间:

对程序进行简单的讲解:

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

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

ProceedingJoinPoint.proceed()让原方法执行。

四、Spring AOP详解

1.Spring AOP核心

        1>切点(Pointcut)

        切点的作用是提供一组规则,告诉程序对哪些方法进行功能增强

        

@Around("execution(* com.example.bookManagement.Controller.*.*(..))")

什么表达式中的"execution(* com.example.bookManagement.Controller.*.*(..))"就是切点表达式

        2>.连接点(Join Point)

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

        例如:"execution(* com.example.bookManagement.Controller路径下的方法都是连接点。

        3>.通知(Advice)

        通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能。

        比如例子中的记录业务方法的耗时时间,就是通知。

        long startTime = System.currentTimeMillis();//执行原始方法Object result = joinPoint.proceed();//记录方法执行时间long endTime = System.currentTimeMillis();long elapsedTime = endTime - startTime;log.info(joinPoint.getSignature()+"耗时: {} ms", elapsedTime);

        4>.切面(Aspect)

        切面=切点+通知

        通过切面能够描述当前AOP程序需要针对哪些方法,在什么时候执行什么样的操作,切面即包含了通知逻辑的定义,也包含了连接点的定义。即例子中的如下代码:

 @Around("execution(* com.example.bookManagement.Controller.*.*(..))")public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {//记录开始时间long startTime = System.currentTimeMillis();//执行原始方法Object result = joinPoint.proceed();//记录方法执行时间long endTime = System.currentTimeMillis();long elapsedTime = endTime - startTime;log.info(joinPoint.getSignature()+"耗时: {} ms", elapsedTime);return result;}

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

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;@Slf4j
@Aspect
@Component
public class AspectDemo {//前置通知@Before("execution(* com.example.aop.Controller.*.*(..))")public void before() {log.info("执行Before方法");}//后置通知@After("execution(* com.example.aop.Controller.*.*(..))")public void after() {log.info("执行After方法");}//返回后通知@AfterReturning("execution(* com.example.aop.Controller.*.*(..))")public void afterReturning() {log.info("执行AfterReturn 方法");}//抛出异常后通知@AfterThrowing("execution(* com.example.aop.Controller.*.*(..))")public void afterThrowing() {log.info("执行AfterThrowing方法");}//环绕通知@Around("execution(* com.example.aop.Controller.*.*(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around方法开始执行");Object result = joinPoint.proceed();log.info("Around方法执行");return result;}
}

添加以下测试程序:

@RequestMapping("/test")
@RestController
public class TestController {@RequestMapping("/t1")public String test1() {return "test1";}@RequestMapping("/t2")public String test2() {int a = 10/0;return "test2";}
}

运行t1:

可以看出执行顺序如下:

运行t2,观察异常情况:

程序发生异常情况下:

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

@Around第一个方法执行了,第二个没有执行。

3.@PointCut

什么代码存在一个问题,就是存在大量的切点表达式execution(* com.example.aop.Controller.*.*(..)),Spring提供了@PointCut注解,把公共的切点表达式提取出来,需要时引入该切点表达式即可。

上述代码可以改为:

@Slf4j
@Aspect
@Component
public class AspectDemo {@Pointcut("execution(* com.example.aop.Controller.*.*(..))")public void pointcut() {}//前置通知@Before("pointcut()")public void before() {log.info("执行Before方法");}//后置通知@After("pointcut()")public void after() {log.info("执行After方法");}//返回后通知@AfterReturning("pointcut()")public void afterReturning() {log.info("执行AfterReturn 方法");}//抛出异常后通知@AfterThrowing("pointcut()")public void afterThrowing() {log.info("执行AfterThrowing方法");}//环绕通知@Around("pointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around方法开始执行");Object result = joinPoint.proceed();log.info("Around方法执行");return result;}
}

但是这种方式只适用于当前切面,如果当其他切面要使用时,就需要将private改为public,而且引用方式改为:全限定类名.方法名()

例如:

@Slf4j
@Aspect
@Component
public class AspectDemo2 {@Before("com.example.aop.AspectDemo.AspectDemo.pointcut()")public void before() {log.info("执行before方法:Aspect Demo2");}
}

4.切面优先级@Order

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

这里直接给出结果:

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

@Before:字母排名靠前的先执行。

@After:字母排名靠前的后执行。

这种方式并不方便管理,因为类名往往是不考虑首字母的。

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

@Order(数字):

@Before:数字小的先执行;

@After:数字大的先执行。

5.切点表达式

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

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

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

1.execution表达式

最常见的切点表达式,用来匹配方法,语法为:

execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)

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

其中:*表示任意一层/一个包/参数,..表示任意多个包或者参数。

2.@annotation注解

相较于execution表达式,@annotation注解适用于多个无规则的方法,比如TestController中的t1和User Controller中的t2.。

实现步骤:

1.编写自定义注解。

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

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

例子:

先定义Controller:

@RequestMapping("/user")
public class UserController {@RequestMapping("/u1")public String u1() {return "u1";}@RequestMapping("/u2")public String u2() {return "u2";}
}
@RequestMapping("/test")
@RestController
public class TestController {@RequestMapping("/t1")public String test1() {return "test1";}@RequestMapping("/t2")public String test2() {int a = 10/0;return "test2";}
}

创建自定义注解类@MyAspect

package com.example.aop.MyAspect;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 {
}

解释:@Target标识了Annotation所修饰的对象范围,即注解可以用在什么地方

@Retention标明注解的生命周期。

定义切面类

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

@Slf4j
@Component
@Aspect
public class MyAspectDemo {@Before("@annotation(com.example.aop.MyAspect.MyAspect)")public void before() {log.info("MyAspect->before method");}@After("@annotation(com.example.aop.MyAspect.MyAspect)")public void after() {log.info("MyAspect->after method");}
}

添加自定义注解

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

@RequestMapping("/user")
public class UserController {@MyAspect@RequestMapping("/u1")public String u1() {return "u1";}@MyAspect@RequestMapping("/t1")public String test1() {return "test1";}

运行程序进行测试:

可以看出切面通知被执行了

运行其他的方法:

可以看出并没有执行切面通知

经典面试题:

Spring AOP的实现方式:

1.基于注解@Aspect

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

3.基于Spring API(提供xml配置的方式,但是自从SpringBoot广泛使用后,这种方式几乎就看不见了)

4.基于代理来实现(更加久远的方式,写法繁琐)

五、Spring AOP原理

Spring AOP是基于动态代理来实现AOP的,首先先了解代理模式:

1.代理模式

也叫委托模式,为其他对象提供一种代理以控制对这个对象的访问,它的作用就是提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,就是提供代理类简介访问。

根据代理的创建时期,代理模式可以分为静态代理动态代理

静态代理:由程序员创建代理类或特定工具自动生成源代码再对其进行编译,在程序运行前代理类的.class文件就已经存在了。

动态代理:在程序运行时,运用反射机制动态创建而成。

静态代理

静态代理由于代码冗余度高,灵活性低,无法在没有接口的情况下使用,所以使用的频率较低。

下面是一个简单的例子:

就用户注册的例子:

// 公共接口:用户服务
public interface UserService {void register(String username, String password); // 注册方法
}
// 真实目标对象:具体用户服务实现
public class RealUserService implements UserService {@Overridepublic void register(String username, String password) {System.out.println("真实注册逻辑:用户 " + username + " 注册成功(密码已加密)");// 这里可以是复杂的数据库操作、校验等真实逻辑}
}
/ 静态代理类:增强用户服务
public class UserServiceProxy implements UserService {private final UserService realUserService; // 持有真实对象的引用// 构造方法传入真实对象public UserServiceProxy(UserService realUserService) {this.realUserService = realUserService;}// 重写接口方法,织入增强逻辑@Overridepublic void register(String username, String password) {// 前置增强:注册前的日志记录System.out.println("【代理前置】开始处理注册请求:用户 " + username);// 调用真实对象的方法(核心业务)realUserService.register(username, password);// 后置增强:注册后的日志记录System.out.println("【代理后置】注册流程结束,已记录操作日志");}
}

运行一个例子:

public class Client {public static void main(String[] args) {// 1. 创建真实对象UserService realService = new RealUserService();// 2. 创建代理对象,传入真实对象UserService proxyService = new UserServiceProxy(realService);// 3. 通过代理对象调用方法(触发代理逻辑)proxyService.register("张三", "123456");}
}

结果:

【代理前置】开始处理注册请求:用户 张三
真实注册逻辑:用户 张三 注册成功(密码已加密)
【代理后置】注册流程结束,已记录操作日志

动态代理

相比于静态代理,动态代理就更加灵活,我们不需要针对每一个目标对象都单独创建一个代理

对象,而是把这个创建对象的工作推迟到程序运行时由JVM来实现,也就是说动态代理在程序运行时,根据需要动态创建生成。

常见的实现方式有两种:

1.JDK动态代理

2.CGLIB动态代理

JDK动态代理

实现步骤:

1.定义一个接口及其实现类

2.自定义InvocationHandler并重写invoke方法,在invoke方法中调用目标方法并自定义一些处理逻辑。

3.通过Proxy.newProxyInstance(ClassLoder loder,Class<?>[] interfaces, InvaocationHandler h)方法创建代理对象。

定义JDK动态代理类

实现InvocationHandler接口

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class JDKInvocationHandler implements InvocationHandler {//目标对象private Object target;public JDKInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//增强代理内容System.out.println("我是代理者");Object reVal = method.invoke(target, args);//代理增强内容System.out.println("代理结束");return reVal;}
}

创建一个代理对象并使用

import com.example.aop.HouseSubject.HouseSubject;
import com.example.aop.HouseSubject.RealHouseSubject;
import com.example.aop.JDK.JDKInvocationHandler;
import org.springframework.cglib.proxy.Proxy;public class Main {public static void main(String[] args) {HouseSubject target = new RealHouseSubject();//创建一个代理类HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{HouseSubject.class},new JDKInvocationHandler(target));proxy.rentHouse();}
}

HouseSubject和RealHouseSubject的代码:

public interface HouseSubject {void rentHouse();void saleHouse();}
public class RealHouseSubject implements HouseSubject {@Overridepublic void rentHouse() {System.out.println("房屋拥有者出租房子");}@Overridepublic void saleHouse() {System.out.println("房屋拥有者出售房子");}
}

运行结果:

代码讲解:

1.InvocationHandler

InvocationHandler接口是java动态代理的关键接口之一,它定义了一个单一方法invoke(),用于处理被代理对象的方法调用。

public interface InvocationHandler
extends Callback
{/*** @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}

参数说明:proxy:代理对象;method:代理对象需要实现的方法,即其中需要重写的方法;

args:mothed所对应方法的参数。

2.Proxy

Proxy类中使用频率最高的方法是:newProxyInstance(),这个方法主要用来生成一个代理对象。

    public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) {try {Class clazz = getProxyClass(loader, interfaces);return clazz.getConstructor(new Class[]{ InvocationHandler.class }).newInstance(new Object[]{ h });} catch (RuntimeException e) {throw e;} catch (Exception e) {throw new CodeGenerationException(e);}}

参数含义:

loder:类加载器,用于加载代理对象。

interfaces:被代理类实现的一些接口。

h:实现了InvocationHandler接口的对象。

CGLIB动态代理

JDK有一个很致命的缺点就是只能代理实现了接口的类,在没有实现接口的情况下,我们就可以使用CGLIB来解决问题。

实现步骤:

1.定义一个被代理类;

2.自定义MethodInterceptor并重写intercept方法,intercept用于增强目标方法,和JDK中的invoke方法类似。

3.通过Enhancer类中的create()创建代理类。

添加依赖:

CGLIB是一个开源项目,使用的话需要引入依赖

 <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>

自定义MethodInterceptor(方法拦截器)

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class CGLIBInterceptor implements MethodInterceptor {private Object target;public CGLIBInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("代理者开始代理");Object result = methodProxy.invoke(target, objects);System.out.println("代理者结束代理");return result;}
}

注意导入的包为import org.springframework.cglib.proxy.MethodProxy;

创建代理类并使用:

public class Main {public static void main(String[] args) {HouseSubject target = new RealHouseSubject();HouseSubject proxy = (HouseSubject) Enhancer.create(target.getClass(), new CGLIBInterceptor(target));proxy.rentHouse();}
}

运行结果:

代码讲解:

1.MethodInterceptor

和JDK方式中的InvocationHandler类似,它只是定义了一个方式Intercept,用于增强目标方法。

2.Enhancer.create()

用来生成一个代理对象。

面试题:

Spring使用的哪种方式:

默认proxyTargetClass (源码中的一个重要参数) false,如果实现了接口,使用JDK;普通类,使用CGLIB

Spring Boot使用哪种方式:

在SpringBoot2.0以后,proxyTargetClass默认为true,默认使用CGLIB。

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

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

相关文章

强化学习_Paper_2017_Curiosity-driven Exploration by Self-supervised Prediction

paper Link: ICM: Curiosity-driven Exploration by Self-supervised Prediction GITHUB Link: 官方: noreward-rl 1- 主要贡献 对好奇心进行定义与建模 好奇心定义&#xff1a;next state的prediction error作为该state novelty 如果智能体真的“懂”一个state&#xff0c;那…

spring中的@Configuration注解详解

一、概述与核心作用 Configuration是Spring框架中用于定义配置类的核心注解&#xff0c;旨在替代传统的XML配置方式&#xff0c;通过Java代码实现Bean的声明、依赖管理及环境配置。其核心作用包括&#xff1a; 标识配置类&#xff1a;标记一个类为Spring的配置类&#xff0c;…

7.计算机网络相关术语

7. 计算机网络相关术语 ACK (Acknowledgement) 确认 ADSL (Asymmetric Digital Subscriber Line) 非对称数字用户线 AP (Access Point) 接入点 AP (Application) 应用程序 API (Application Programming Interface) 应用编程接口 APNIC (Asia Pacific Network Informatio…

Hadoop 集群基础指令指南

目录 &#x1f9e9; 一、Hadoop 基础服务管理指令 ▶️ 启动 Hadoop ⏹️ 关闭 Hadoop &#x1f9fe; 查看进程是否正常运行 &#x1f4c1; 二、HDFS 常用文件系统指令 &#x1f6e0;️ 三、MapReduce 作业运行指令 &#x1f4cb; 四、集群状态监控指令 &#x1f4a1; …

【MySQL数据库】事务

目录 1&#xff0c;事务的详细介绍 2&#xff0c;事务的属性 3&#xff0c;事务常见的操作方式 1&#xff0c;事务的详细介绍 在MySQL数据库中&#xff0c;事务是指一组SQL语句作为一个指令去执行相应的操作&#xff0c;这些操作要么全部成功提交&#xff0c;对数据库产生影…

一、OrcaSlicer源码编译

一、下载 1、OrcaSlicer 2.3.0版本的源码 git clone https://github.com/SoftFever/OrcaSlicer.git -b v2.3.0 二、编译 1、在OrcaSlicer目录运行cmd窗口&#xff0c;输入build_release.bat 2、如果出错了&#xff0c;可以多运行几次build_release.bat 3、在OrcaSlicer\b…

港口危货储存单位主要安全管理人员考试精选题目

港口危货储存单位主要安全管理人员考试精选题目 1、危险货物储存场所的电气设备应符合&#xff08; &#xff09;要求。 A. 防火 B. 防爆 C. 防尘 D. 防潮 答案&#xff1a;B 解析&#xff1a;港口危货储存单位存在易燃易爆等危险货物&#xff0c;电气设备若不防爆&…

格雷希尔用于工业气体充装站的CZ系列气罐充装转换连接器,其日常维护有哪些

格雷希尔气瓶充装连接器&#xff0c;长期用于压缩气体的快速充装和压缩气瓶的气密性检测&#xff0c;需要进行定期的维护&#xff0c;为每一次的充装提供更好的连接。下列建议的几点维护准则适用于格雷希尔所有充注接头&#xff0c;请非专业人士不要随意拆卸连接器。 格雷希尔气…

Java 多线程进阶:什么是线程安全?

在多线程编程中&#xff0c;“线程安全”是一个非常重要但又常被误解的概念。尤其对于刚接触多线程的人来说&#xff0c;不理解线程安全的本质&#xff0c;容易写出“偶尔出错”的代码——这类 bug 往往隐蔽且难以复现。 本文将用尽可能通俗的语言&#xff0c;从三个角度解释线…

MSO-Player:基于vlc的Unity直播流播放器,支持主流RTSP、RTMP、HTTP等常见格式

MSO-Player 基于libVLC的Unity视频播放解决方案 支持2D视频和360度全景视频播放的Unity插件 &#x1f4d1; 目录 &#x1f3a5; MSO-Player &#x1f4cb; 功能概述&#x1f680; 快速入门&#x1f4da; 关键组件&#x1f4dd; 使用案例&#x1f50c; 依赖项&#x1f4cb; 注意…

navicat中导出数据表结构并在word更改为三线表(适用于navicat导不出doc)

SELECTCOLUMN_NAME 列名,COLUMN_TYPE 数据类型,DATA_TYPE 字段类型,IS_NULLABLE 是否为空,COLUMN_DEFAULT 默认值,COLUMN_COMMENT 备注 FROMINFORMATION_SCHEMA.COLUMNS WHEREtable_schema db_animal&#xff08;数据库名&#xff09; AND table_name activity&#xff08;…

docker学习笔记6-安装wordpress

一、创建自定义网络、查看网络 docker netword create blog docker network ls 二、 启动mysql容器 启动命令&#xff1a; docker run -d -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEwordpress \ -v mysql-data:/var/lib/mysql \ -v /app/myconf:/etc…

03_Mybatis-Plus LambadaQueryWrapper 表达式爆空指针异常

&#x1f31f; 03_MyBatis-Plus LambdaQueryWrapper 爆出空指针异常的坑点分析 ❓ 场景描述 来看一段常见的 MyBatis-Plus 查询写法&#xff0c;是否存在问题&#xff1f; Page<VideoInfoVo> videoInfoVosPage videoMapper.selectPage(page, new LambdaQueryWrapper&…

WEB安全--社会工程--SET钓鱼网站

1、选择要钓鱼的网站 2、打开kali中的set 3、启动后依次选择&#xff1a; 4、输入钓鱼主机的地址&#xff08;kali&#xff09;和要伪装的网站域名&#xff1a; 5、投放钓鱼网页&#xff08;服务器域名:80&#xff09; 6、获取账号密码

Ethan独立开发产品日报 | 2025-04-29

1. mrge 代码审查的光标 mrge 是一个由人工智能驱动的代码审查平台&#xff0c;能够自动审核拉取请求&#xff08;PR&#xff09;&#xff0c;为人工审查员提供超级能力。它是像 cal.com 和 n8n 这样快速发展的团队的首选工具。 关键词&#xff1a;mrge, 代码审查, AI驱动, …

ubuntu22.04 qemu arm64 环境搭建

目录 创建 安装 Qemu 启动 # 进入qemu虚拟机后执行 qemu编译器安装 创建 qemu-img create ubuntu22.04_arm64.img 40G 安装 qemu-system-aarch64 -m 4096 -cpu cortex-a57 -smp 4 -M virt -bios QEMU_EFI.fd -nographic -drive ifnone,fileubuntu-22.04.5-live-server-a…

安全生产知识竞赛宣传口号160句

1. 安全生产是责任&#xff0c;每个人都有责任 2. 安全生产是保障&#xff0c;让我们远离危险 3. 安全生产是团结&#xff0c;共同守护每一天 4. 注重安全&#xff0c;守护明天 5. 安全生产无小事&#xff0c;关乎千家万户 6. 安全第一&#xff0c;人人有责 7. 安全生产无差别&…

Python 虚拟环境管理:venv 与 conda 的选择与配置

文章目录 前言一、虚拟环境的核心价值1.1 依赖冲突的典型场景1.2 隔离机制实现原理 二、venv 与 conda 的架构对比2.1 工具定位差异2.2 性能基准测试&#xff08;以创建环境 安装 numpy 为例&#xff09; 三、venv 的配置与最佳实践3.1 基础工作流3.2 多版本 Python 管理 四、…

【自然语言处理与大模型】如何获取特定领域的微调数据集?

在特定领域中&#xff0c;数据集通常由提出需求的一方提供。然而&#xff0c;在某些情况下&#xff0c;如果他们未能提供所需的数据&#xff0c;或者你正在独立开展一个项目&#xff0c;并且需要相应的数据来推进工作&#xff0c;这时你应该怎么办呢&#xff1f;本文提供一种思…

Map系列之ConcurrentHashMap源码分析:高并发场景下的性能密码

引言&#xff1a;当线程安全成为刚需 1.1 并发时代的Map困境 经典案例&#xff1a;电商秒杀系统超卖事故分析&#xff08;附线程堆栈截图&#xff09;传统方案缺陷&#xff1a;synchronizedMap的吞吐量陷阱&#xff08;JMH测试数据对比&#xff09;ConcurrentHashMap的定位&a…