当前位置: 首页 > news >正文

Java阶段三02

第3章-第2节

一、知识点

面向接口编程、什么是spring、什么是IOC、IOC的使用、依赖注入

二、目标

  • 了解什么是spring

  • 理解IOC的思想和使用

  • 了解IOC的bean的生命周期

  • 理解什么是依赖注入

三、内容分析

  • 重点

    • 了解什么是spring

    • 理解IOC的思想

    • 掌握IOC的使用

  • 难点

    • 理解IOC的思想

    • 掌握IOC的使用

四、内容

1、Spring

1.1 简介

spring是分层的java SE/EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业级应用开源框架。

1.2 优势
  • 开源免费的轻量级框架

  • 低侵入式设计,代码污染极低

  • 支持事务的处理,对框架整合的支持

  • 以控制反转(IOC),面向切面编程(AOP)为内核

  • 方便解耦,简化开发:将对象的创建交给spring 无需new

  • 提供了展现层SpringMVC和持久层Spring JDBCTemplate以及业务层事务管理等众多企业级应用技术

  • 能整合开源世界众多第三方框架和类库

1.3 引入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.11.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version>
</dependency>

2、Ioc

2.1 概念

控制反转(IoC,Inversion of Control)是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。

以前我们在代码中,使用new 构造方法创建对象,现在不用了, 由容器代替开发人员管理对象。

2.2 传统方式和ioc方式的区别
  • 传统

    我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起。

  • ioc

    创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题(你是什么时候生的,怎么生出来的我可不关心,能帮我干活就行),A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。

下面用制造汽车需要引擎、轮胎等零件的场景解释含义

  • 假设现在要生产汽车、货车、客车、出租车,它们都是使用【1代引擎】

 

代码如下:

public class EngineV1 {
​private String name = "1代引擎";
​public String getName() {return name;}
}
/*** 汽车*/
public class Car {
​private String carName = "小汽车";
​private EngineV1 engineV1;
​public Car(EngineV1 engineV1) {this.engineV1 = engineV1;}
​public void info() {System.out.println(carName + "使用的是" + engineV1.getName());}
}
/*** 货车*/
public class Van {
​private String carName = "货车";
​private EngineV1 engineV1;
​public Van(EngineV1 engineV1) {this.engineV1 = engineV1;}
​public void info() {System.out.println(carName + "使用的是" + engineV1.getName());}
}

测试

Car car = new Car(new EngineV1());
car.info();
​
Van van = new Van(new EngineV1());
van.info();
​
// 结果
// 小汽车使用的是1代引擎
// 货车使用的是1代引擎

总结:现在可以看出不管是汽车还是货车,都是依赖于【1代引擎】的,耦合度很高,没有【1代引擎】就没有这些车。假如我们还有很多其他类型的车都是依赖于【1代引擎】,有一天我想把所有的【1代引擎】换成 【2代引擎】我该怎么做?全局搜索【1代引擎】修改为 【2代引擎】,想想都有点麻烦。

  • 我们需要换一种思路来实现,解决上述问题。

 

/*** 引擎接口*/
public interface IEngine {String getName();
}
public class EngineV1 implements IEngine{
​private String name = "1代引擎";
​public String getName() {return name;}
}
public class EngineV2 implements IEngine{
​private String name = "2代引擎";
​public String getName() {return name;}
}
/*** 汽车*/
public class Car {
​private String carName = "小汽车";
​// 现在不依赖于具体哪个引擎,直接对接引擎接口private IEngine engine;
​public Car(IEngine engine) {this.engine = engine;}
​public void info() {System.out.println(carName + "使用的是" + engine.getName());}
}
/*** 货车*/
public class Van {
​private String carName = "货车";
​private IEngine engine;
​public Van(IEngine engine) {this.engine = engine;}
​public void info() {System.out.println(carName + "使用的是" + engine.getName());}
}

测试

Car car = new Car(new EngineV1());
car.info();
​
Van van = new Van(new EngineV1());
van.info();
​
// 结果
// 小汽车使用的是1代引擎
// 货车使用的是1代引擎
Car car = new Car(new EngineV2());
car.info();
​
Van van = new Van(new EngineV2());
van.info();
​
// 结果
// 小汽车使用的是2代引擎
// 货车使用的是2代引擎

总结:代码中不再依赖于具体,而是依赖于抽象容器,即要针对接口编程,不针对实现编程。过去思维中想要什么依赖,需要自己去 “拉” 改为抽象容器主动 “推” 给你,你只管使用实体就可以了。这是依赖倒转 (DIP) 的一种表现形式。由原来的汽车依赖引擎转化成了引擎依赖引擎接口了,进行了反转。现在生产汽车的时候,给汽车什么引擎它就用什么引擎,汽车就脱离了只能用【1代引擎】绝对绑定形式了。

我们还可以接着优化,现在是使用引擎的时候我们手动去new的。假如项目中有很多的地方使用了【1代引擎】,要换成【2代引擎】,即把全部的new EngineV1()改成new EngineV2()

  • 针对上面的问题进行优化。我们可以先创建一个仓库出来,把引擎先创建好放在容器里面。车辆要用引擎的时候直接从仓库里面获取。

 

/*** 容器*/
public class Container {
​private Map<String, Object> map = new HashMap<String, Object>();
​public Container() {// 此处利用可以利用读取配置的方式,利用反射动态创建出对象map.put("engine", new EngineV1());}
​public Object getBean(String name) {return map.get(name);}
}

测试

// 创建容器
Container container = new Container();
​
Car car = new Car((IEngine) container.getBean("engine"));
car.info();
​
Van van = new Van((IEngine) container.getBean("engine"));
van.info();
​
// 结果
// 小汽车使用的是1代引擎
// 货车使用的是1代引擎

现在要把【1代引擎】化成【2代引擎】,我们只需要修改容器里面的引擎即可,只要修改一处地方。

map.put("engine", new EngineV2());

上面的测试结果就变成了

// 创建容器
Container container = new Container();
​
Car car = new Car((IEngine) container.getBean("engine"));
car.info();
​
Van van = new Van((IEngine) container.getBean("engine"));
van.info();
​
// 结果
// 小汽车使用的是2代引擎
// 货车使用的是2代引擎

总结:这就是IOC要做的事情。把创建对象的过程交给spring管理,我们需要用的时候直接拿就行。

3、基本使用

3.1 方式一:配置文件
  • 创建xml文件 resources/文件名.xml

  • 创建类
  • xml配置

    <!--  id:spring生成对象的时候,放到容器里面,会给每一个对象生成一个id,用于区分对象  -->
    <!--  class:告诉它使用哪个类  -->
    <bean id="student" class="com.company.eneity.Student">

  • 获取对象
    • 通过id获取
      public static void main(String[] args) {// 生成spring容器// 读取spring的xml文件,根据内容生成对应的对象,放到容器里面ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");// 获取对象,传入id,通过id获取对象Student student = (Student) ctx.getBean("student");System.out.println(student);
      }

    • 通过类获取
      public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");Student student1 = ctx.getBean(Student.class);System.out.println(student1);
      }

3.2 方式二:注解
  • 创建类
    // spring扫描到这个注解的时候,会自动生成对象
    @Component
    public class Teacher {}

  • 配置xml的component-scan
    <!-- 扫描包,会去扫描注解,有对应注解的会自动生成对象 -->
    <context:component-scan base-package="com.company"></context:component-scan>

  • 获取对象
    @Component
    public class TeacherDao {private Teacher teacher;public void test(){System.out.println(teacher);}
    }
    ​
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
    TeacherDao teacherDao = ctx.getBean(TeacherDao.class);
    teacherDao.test();
    3.3 bean的生命周期

spring默认的是单例对象可以通过scope修改

  • 单例对象

    scope="singleton"

    创建bean的时候使用默认的无参构造函数

    一个应用只有一个对象的实例,它的作用范围就是整个应用

    生命周期

    对象出生:当应用加载,创建容器时,对象就被创建

    对象活着:只要容器在,对象一直活着

    对象死亡:当应用卸载,销毁容器时,对象就被销毁了

    测试
    // 创建spring容器
    // 创建容器的时候,对象已经生成
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
    ​
    // 获取对象只是从容器里面拿出来
    Student student1 = (Student) ctx.getBean("student");
    Student student2 = (Student) ctx.getBean("student");
    ​
    System.out.println(student1 == student2);
    // 结果 true

  • 多例对象

    scope="prototype"

    每次访问对象时,都会被重新创建对象实例

    生命周期

    对象出生:当使用对象时,创建新的对象实例

    对象活着:对象在使用中,对象就一直活着

    对象死亡:当对象长时间不用,被java垃圾回收机制回收

    测试
    // 创建spring容器
    // 创建容器的时候对象还没有生成
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
    ​
    // 获取对象的时候才创建对象
    Student student1 = (Student) ctx.getBean("student");
    Student student2 = (Student) ctx.getBean("student");
    ​
    System.out.println(student1 == student2);
    ​
    // 结果 false

4、依赖注入

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。

4.1 使用构造函数

前提:类中需要提供一个对应参数列表的构造函数

标签:constructor-arg

标签属性:

index:指定参数在构造函数参数列表中的索引位置

type:指定参数在构造函数中的数据类型

name:指定参数在构造函数中的名称,用这个找给谁赋值

value:它能赋的值是基本数据类型和String类型

ref:它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean

  • 基本数据类型(数值类和字符串类)

    基本数据类型直接用value赋值

    <bean id="student" class="com.company.eneity.Student"><!-- constructor-arg调用有参否则函数,必须有写构造函数 --><constructor-arg name="name" value="张三"></constructor-arg><constructor-arg name="age" value="18"></constructor-arg>
    </bean>

  • 引用数据类型

    引用数据类型使用ref赋值

    <bean id="student" class="com.company.eneity.Student"><!-- constructor-arg调用有参否则函数,必须有写构造函数 --><constructor-arg name="name" value="张三"></constructor-arg><constructor-arg name="age" value="18"></constructor-arg><constructor-arg name="studentMarjor" ref="studentMarjor"></constructor-arg>
    </bean>

4.2 使用set赋值

标签:property

标签属性:

name:找的是类中set方法后面的部分

value:给属性赋值的是基本数据类型和String类型

ref:给属性赋值是其他bean类型的

  • 基本数据类型
    <bean id="student2" class="com.company.eneity.Student2"><!-- property调用set的方法,必须set方法 --><property name="name" value="张三"></property><property name="age" value="18"></property>
    </bean>

  • 引用数据类型
    <bean id="student" class="com.company.eneity.Student"><property name="name" value="张三"></property><property name="age" value="18"></property><property name="studentMarjor" ref="studentMarjor"></property>
    </bean>
    <bean id="studentMarjor" class="com.company.eneity.StudentMarjor"><property name="name" value="软件工程"></property><property name="studentNum" value="80"></property>
    </bean>

5、小结

本章节中我们学习了什么是Spring,spring的优势是什么,为什么要用spring,同时学习了spring的一个核心功能IOC(控制反转),理解了控制反转的作用以及为什么要用控制反转,同时理解了什么是依赖注入,学会了通过XML文件配置IOC和不同方式的依赖注入。

下一节中,我们将会学习一些Spring的常用注解,学会使用注解进行开发,去理解注解开发和配置文件开发的区别和优势。

相关文章:

Java阶段三02

第3章-第2节 一、知识点 面向接口编程、什么是spring、什么是IOC、IOC的使用、依赖注入 二、目标 了解什么是spring 理解IOC的思想和使用 了解IOC的bean的生命周期 理解什么是依赖注入 三、内容分析 重点 了解什么是spring 理解IOC的思想 掌握IOC的使用 难点 理解IO…...

【Linux】掌握库的艺术:我的动静态库封装之旅

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 &#x1f308;C专栏&#xff1a;C 文章目录 1.什么是库1.2 认识动静态库1.2.1 动态库1.2.2…...

UE5动画控制 基础

素材 mixamo先去选择一个character 点击下载 就这个下载下来 然后选几个animation&#xff0c; 记得勾选 把动作下载了 without skin就是只要动作 然后把他们放在一个文件夹里先 UE里导入 找一个文件夹&#xff0c;直接拖拽进来那个character的fbx&#xff0c;默认配置就…...

流畅!HTMLCSS打造网格方块加载动画

效果演示 这个动画的效果是五个方块在网格中上下移动&#xff0c;模拟了一个连续的加载过程。每个方块的动画都是独立的&#xff0c;但是它们的时间间隔和路径被设计为相互协调&#xff0c;以创建出流畅的动画效果。 HTML <div class"loadingspinner"><…...

linux命令之top(Linux Command Top)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…...

数据结构-希尔排序(ShellSort)笔记

看动画理解 【数据结构】八大排序(超详解附动图源码)_数据结构排序-CSDN博客 一 基本思想 先选定一个整数gap&#xff0c;把待排序文件中所有记录分成gap个组&#xff0c;所有距离为gap的记录分在同一组内&#xff0c;并对每一组内的元素进行排序。 然后将gap逐渐减小重复上…...

Junit + Mockito保姆级集成测试实践

一、做好单测&#xff0c;慢即是快 对于单元测试的看法&#xff0c;业界同仁理解多有不同&#xff0c;尤其是在业务变化快速的互联网行业&#xff0c;通常的问题主要有&#xff0c;必须要做吗&#xff1f;做到多少合适&#xff1f;现在没做不也挺好的吗&#xff1f;甚至一些大…...

软件项目管理要点

一.项目管理 1.盈亏平衡分析 销售额固定成本可变成本税费利润 当利润为0的时候就是盈亏平衡点。 2.范围管理 范围定义的输入包括&#xff1a;项目章程、项目范围管理计划、组织过程资产、批准的变更申请。 3.时间管理 项目时间管理中的过程包括活动定义、活动排序、活动的资…...

ESP8266 连接 MQTT 服务器EMQX 连接MQTTX

目录 1.先用有一台自己的云服务器 2. 使用FinalShell连接阿里云云服务器ECS 3.安装宝塔 4.在云服务器打开8888端口 5.使用外网面板地址打开宝塔面板 6.安装Docker 7.下载emqx 8.打开emqxWeb 界面 9.下载MQTTX 10.EMQX加一个客户端 11.开始通信 12.加入单片机ESP8266 …...

Python中如何处理异常情况?

1、Python中如何处理异常情况&#xff1f; 在Python中&#xff0c;处理异常情况通常使用try/except语句。try语句块包含可能会引发异常的代码&#xff0c;而except语句块包含处理异常的代码。如果try块中的代码引发了异常&#xff0c;控制流将立即转到相应的except块。 以下是…...

openpnp - 在openpnp中单独测试相机

文章目录 openpnp - 在openpnp中单独测试相机概述笔记END openpnp - 在openpnp中单独测试相机 概述 底部相机的位置不合适, 重新做了零件&#xff0c;准备先确定一下相机和吸嘴的距离是多少才合适。 如果在设备上直接实验&#xff0c;那么拆装调整相机挺麻烦的。 准备直接在电…...

Spark窗口函数

1、 Spark中的窗口函数 窗口就是单纯在行后面加一个列 可以套多个窗口函数&#xff0c;但彼此之间不能相互引用&#xff0c;是独立的 窗口函数会产生shuffle over就是用来划分窗口的 (1) 分组聚合里面的函数&#xff0c;基…...

Idea、VS Code 如何安装Fitten Code插件使用

博主主页:【南鸢1.0】 本文专栏&#xff1a;JAVA 目录 ​编辑 简介 所用工具 1、Idea如何安装插件 1.idea下载插件 2.需要从外部下载然后在安装&#xff0c; 2、VS Code如何安装插件 总结 简介 Fitten Code是由非十大模型驱动的AI编程助手&#xff0c;它可以自动生成代…...

elasticsearch7.x在k8s中的部署

一、说明 二、思路 三、部署 1、建nfs服务器 2、建持久卷 3、部署elasticsearch 四、附件 ?pv.yaml内容 elasticsearch.yaml内容 一、说明 本文章内容主要的参考来源是https://www.cnblogs.com/javashop-docs/p/12410845.html&#xff0c;但参考文献中的elasticsearc…...

校园社团信息管理平台:Spring Boot技术实战指南

3系统分析 3.1可行性分析 通过对本校园社团信息管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本校园社团信息管理系统采用SSM框架&#xff0c;JAVA作…...

【Linux】从内核角度理解 TCP 的 全连接队列(以及什么是 TCP 抓包)

文章目录 概念引入理解全连接队列内核方面理解Tcp抓包方法注意事项 概念引入 我们知道&#xff0c;TCP的三次握手是由TCP协议 自动处理的&#xff0c;建立连接的过程与用户是否进行accept无关&#xff0c;accept()的作用主要是为当前连接创建一个套接字&#xff0c;用于进行后…...

太速科技-712-6U VPX飞腾处理器刀片计算机

6U VPX飞腾处理器刀片计算机 一、产品概述 该产品是一款基于国产飞腾FT-2000四核处理器或D2000八核处理器的高性能6U VPX刀片式计算机。产品提供了可支持全网状交换的高速数据通道&#xff0c;其中P1、P2均支持1个PCIe x16 Gen3或2个PCIe x8 Gen3或4个PCIe x4 Gen3总…...

深度学习(八) TensorFlow、PyTorch、Keras框架大比拼(8/10)

一、深度学习框架概述 深度学习框架在当今人工智能和机器学习领域中占据着至关重要的地位。其中&#xff0c;TensorFlow 由 Google 开发&#xff0c;自 2015 年发布以来&#xff0c;凭借其灵活的计算图、自动微分功能以及跨平台支持等特点&#xff0c;迅速成为主流深度学习框架…...

thinkphp中命令行工具think使用,可用于快速生成控制器,模型,中间件等

在使用tp框架的时候tp内置了一个think的命令行工具&#xff0c; 这个工具可以帮助我们快速的生成控制器&#xff0c;模型&#xff0c;中间件的代码。 这个工具就和laravel中的 artisan 的作用都差不多&#xff0c;只是名称不同而已。 tp中常用的命令行工具&#xff1a; 1. 快…...

Discourse 是否支持手机注册

Discourse 能不能使用手机进行注册&#xff1f; 功能总结 简答来说&#xff0c;Discourse 不能使用手机注册&#xff0c;Discourse 也没有提供这个功能。 这个功能多是需要 SSO 来实现。 比如说华为的那个社区&#xff0c;他们自己做了一个自己的用户管理 SSO&#xff0c;然…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...