Spring依赖注入思想分析

Spring 依赖注入思想分析

文章目录

  • `Spring` 依赖注入思想分析
    • 一、前言
    • 二、控制反转(`Inversion of Control`)
      • 1. 代码依赖初始化问题
      • 2. 匿名内部类解决方案
      • 3. 创建接口实现类方案
      • 4. 问题深入
      • 5. 定义父类解决问题1方案
      • 6. 控制反转解决问题2方案
    • 三、依赖注入(`Dependency Injection`)
      • 1、依赖关系树
      • 2、 依赖注入框架
      • 3、 Spring的依赖注入
      • 4、 ApplicationContext
    • 参考文章

背景

​ 大二期间一直很疑惑,为什么 Spring 要把创建对象弄的这么复杂?

​ 从各种对象的创建,装配,到销毁,什么事情都是 Spring 给干了,感觉给我带来的无非就是少了几个 new 对象的语句,以及使用 @Autowired 把对象注入了就能用,没感觉太过于方便在哪里。

​ 大三了之后,就慢慢开始对 依赖注入 这种思想有了一点点似是而非的认识,突然觉得,这么做好像真的还挺好!

一、前言

​ 在维基百科中,依赖注入(dependency injection)简称DI,定义如下:

define:在软件工程中,依赖注入dependency injection)的意思为,给予调用方它所需要的事物。“依赖”是指可被方法调用的事物。依赖注入形式下,调用方不再直接指使用“依赖”,取而代之是“注入” 。“注入”是指将“依赖”传递给调用方的过程。在“注入”之后,调用方才会调用该“依赖”。传递依赖给调用方,而不是让让调用方直接获得依赖,这个是该设计的根本需求。在编程语言角度下,“调用方”为对象和类,“依赖”为变量。在提供服务的角度下,“调用方”为客户端,“依赖”为服务。

​ 看起来是不是有点阅读困难?

​ 在我们理解它在编程之中的含义之前,我们来聊聊什么是依赖依赖是指依靠某种东西来获得支持,比如我会说我们对手机的依赖程度过高。那么如果我们将依赖,放入在编程的世界中,又是什么表现形式呢?

example:当 class A 使用 class B 的某些功能的时候,则表示 class A 具有 class B的依赖,在Java 中,使用其他class的方法前,我们需要创建对应的 class 的对象。(即 class A 需要创建一个 class B 的实例)

结论因此,将创建对象的任务转移给其他 class,并直接使用依赖项的过程,被称为“依赖项注入”。

​ 了解依赖注入之后,本文再来聊聊 Spring 框架中DISpring Inversion of Control(控制反转),ApplicationContext (应用上下文)的故事。

二、控制反转(Inversion of Control

Tips:控制反转(IOC)是一种编程思想,它改变了传统程序的执行流程。想象一下,传统程序像是你亲自开车,而IOC则像是坐公交。在IOC中,你不再亲自控制每个步骤,而是把控制权交给框架公交车,框架会帮你处理很多琐碎的事情,你只需专注于自己的目的地业务逻辑。比如Spring框架帮你管理对象、处理依赖关系,使代码更简洁、灵活。

作者:阿妮亚学SqlSugar
链接:https://www.zhihu.com/question/381216328/answer/3366856030

​ 让我们简单来了解学习一下控制反转Java 代码中是如何体现这一思想的,我会尽可能讲述的简单,请各位细细体会每一次我改变的意图,和我们的解决方案,这个对于理解什么是控制反转的思想很重要。

1. 代码依赖初始化问题

​ 通常来说,我们实例化一个对象的方式一般是使用 new 这个关键字来进行实例化一个对象。如下代码展示

Car car = new Car();

​ 这样,我们就实例化了一辆车,显然,这个车是有一定基本属性的,以及这辆车我们也可以将其分割成好几大类,车由轮胎,引擎,车身,电气设备等等组成。在这里为了类尽可能简单,我们定义 Engine 接口来模拟汽车引擎,然后将 engine 对象作为变量成员放入 Car 类中,这样,我们构建了一个依赖关系 CarEngine 组成。

定义接口 Engine ,因为引擎可能有多个不同种类的引擎,但是只要符合有 trunOn() 方法的引擎,我们就都可以拿来用。

public interface Engine{void turnOn();
}

定义类 class Car

package org.example.car;/*** @ClassName Car* @Description TODO* @Author 枫飘长安* @Date 2024/3/30 11:42* @Version 1.0**/
public class Car {private Engine engine;public Car() {}public void start() {engine.turnOn();}}

显然,这个时候当我们调用 car.start() 方法时,会抛出NullPointerException空指针异常,为啥呢?因为你连引擎都没有啊,你引擎怎么启动?

这个时候你可能会好奇,为什么啊,在 Car 中的 start() 方法中,不是有 engine.turnOn(); 吗?但是你可能忽略了一个实时,你没有在 Car 的构造函数中初始化 engine,也就是说,此时你的 enginenull

package org.example;import org.example.car.Car;public class Main {public static void main(String[] args) {Car car = new Car();car.start();}
}

2. 匿名内部类解决方案

当然,你可以使用匿名内部类的方式进行如下改进,在匿名内部类中重写turnOn() 方法,并成功初始化 engine

package org.example.car;/*** @ClassName Car* @Description TODO* @Author 枫飘长安* @Date 2024/3/30 11:42* @Version 1.0**/
public class Car {private Engine engine;public Car() {}public void start() {engine = new Engine() {@Overridepublic void turnOn() {System.out.println("Engine started");}};engine.turnOn();}}

但是在工程中,我们往往不会这样去实现,以下,是使用匿名内部类的优劣。在一个庞大的工程体系中,匿名内部类往往是不太明智的选择。

匿名内部类优劣性

优势:

  1. 简洁性:如果只是简单地需要一个具有特定行为的Engine实例,并且只需要在一个地方使用,匿名内部类能很好地满足需求,无需单独创建一个新的类文件
  2. 灵活性:这种方式允许你在使用时即时定义和覆盖父类或接口的方法,根据当前方法的需求提供定制化的行为。

劣势:

  1. 可读性和可维护性:如果这个匿名内部类实现的功能较为复杂,或者有多个方法需要重写,代码量就会变得很大,降低代码的可读性和可维护性。
  2. 复用性:如果同样的逻辑需要在多处使用,那么每次都需要重新定义匿名内部类,不如直接创建一个独立的、命名的类来得方便和高效。
  3. 测试难度:匿名内部类由于其匿名特性,不容易被单独提取出来进行单元测试。

3. 创建接口实现类方案

其次,我们可以创建新的类,来实现 Engine 接口

ElectricEngine

public class ElectricEngine implements Engine {@Overridepublic void turnOn(){System.out.println("电动引擎启动");}
}

CombustionEngine

public class CombustionEngine implements Engine {@Overridepublic void turnOn(){System.out.println("燃油引擎启动");}
}

好,此时我们修改 Car 的构造函数,比如说我用 ElectricEngine 实现,将我们的 engine 字段分配给一个实例化的 ElectricEngine对象

public class Car {private Engine engine;public Car(){this.engine = new ElectricEngine();}public void start(){engine.turnOn();}
}

完成上述操作后,启动我们的 Main 函数,这个时候控制台打印信息,我们也达成了我们的目的。

电动引擎启动

创建接口实现类优劣性

优势:

  1. 清晰的职责划分:接口定义了一组行为规范接口实现类负责具体的实现细节,符合面向对象设计原则中的单一职责原则接口隔离原则,有助于提高代码的可读性和可维护性。

  2. 高内聚低耦合:不同的类可以实现同一个接口,可以根据实际需求灵活替换不同的实现类,增强了系统的扩展性和解耦程度

  3. 易于测试:可以通过Mock框架轻松模拟接口实现类的行为,便于单元测试集成测试

  4. 代码复用:接口实现类可以在多个地方被引用和复用,避免代码重复编写

劣势:

  1. 额外的类结构:相比于匿名内部类,创建接口及其实现类需要更多的类结构和代码组织,可能会增加项目的复杂度。
  2. 过度设计风险:如果不恰当的设计过多接口,可能会导致系统过于复杂,尤其是在需求不明确或变化频繁的情况下,可能导致接口难以适应后续需求变更。
  3. 运行时类型转换:在使用多态特性时,有时需要进行类型判断或类型转换,增加了程序的复杂性。

4. 问题深入

反思:我们解决了空指针异常,但是,我们胜利了吗?这就是完美的银弹了吗?

问题延伸:在解决问题的同时,我们又引入了另一个问题。尽管我们通过抽象Engine接口,然后通过不同的Engine实现类来负责不同类型引擎的业务逻辑,的确是很好的设计策略。但是细心的伙伴可能已经发现了,我们Car类的构造函数中将engine声明为ElectricEngine,这将导致所有车都有一个电动引擎。假如我们现在要创建不同的汽车对象它有一个燃油引擎我们将不得不改变我们的设计。比较常见的方法是创建两个独立里的类,各司其职,在他们的构造函数中将engine分配给Engine接口的不同实现;

例如:

创建 CombustionCar 燃油车类

public class CombustionCar {private Engine engine;public CombustionCar() {this.engine = new CombustionEngine();}public void start() {engine.turnOn();}}

创建 ElectricCar 电动车类

public class ElectricCar {private Engine engine;public ElectricCar() {this.engine = new ElectricEngine();}public void start() {engine.turnOn();}}

通过上述一顿操作,我们大大增加了我们的代码量,以后更是幸福,因为每多一种引擎,都得重新创建一个对应该引擎的车类,我们程序猿实在是太幸福了!

如果只是日常需求,我们已经可以不用动以上的代码了,因为这已经足够使用了,但是这种解决方案显然不是我写这篇文章的目的。

Tips:从设计角度来说,当前代码是糟糕的,有两个问题始终没有得到解决。

  1. 在两个不同的类中,都有重复的 start() 方法,代码冗余。
  2. 我们需要为每一个新的 Engine 实现类创建一个新的类。

随着整个工程的庞大化,第二个问题会越来越严重,成为**“屎山”**的一部分。

5. 定义父类解决问题1方案

带上上述两个问题继续思考,先聚焦到问题一,代码冗余问题。

这个时候Java 三大特性之一的继承知识就可以用上了!我们可以创建一个父类 Car,把公共代码抽取到父类中,就解决了第一个问题。

由于 Engine 字段是私有的,我们在父类 Car 的构造函数中接受一下 Engine 对象,并进行赋值即可。

Car

public class Car {private Engine engine;public Car(Engine engine) {this.engine = engine;}public void start() {engine.turnOn();}
}

CombustionCar

public class CombustionCar extends Car{public CombustionCar() {super(new CombustionEngine());}}

ElectricCar

public class ElectricCar extends Car {public ElectricCar() {super(new ElectricEngine());}}

通过使用父类并继承的方式,我们成功解决了代码重复的问题,测试一下我们的 Main 函数

package org.example;import org.example.engine.CombustionCar;
import org.example.engine.ElectricCar;public class Main {public static void main(String[] args) {CombustionCar combustionCar = new CombustionCar();combustionCar.start();ElectricCar electricCar = new ElectricCar();electricCar.start();}
}

能够得到输出

电动引擎启动
燃油引擎启动

此时,我们应该有的项目结构如下

在这里插入图片描述

6. 控制反转解决问题2方案

​ 现在,我们换一个角度看待问题2,为什么我们需要去关注 CombustionCarElectricCar 呢?这些不都是 Car 吗?于是,我们把关注点重新回到我们的 Car 上面来。

​ 我们现在已经允许在实例化 Car 对象的时候,将 Engine 对象作为构造函数的参数传入,其实这样已经消除了为每个 Engine 对象创建新的 Car 子类的问题。父类 Car依赖于 Engine 接口,并不需要知道任何关于 Engine的实现。

​ 通过带有Engine参数的构造函数,我们已将要使用哪个Engine实现的决定从Car类本身(最初由CombustionEngine决定)更改为实例化Car类的客户端。决策过程的这种逆转称为IoC原则。现在,由客户端控制使用哪种实现,而不是由Car类本身控制使用哪种Engine实现。

Tips:这个思想理解有点绕,大家可以结合示例代码进行理解

    public static void main(String[] args) {/*** 老法子* 为每一类型发送机的车创建类,然后实现父类car,然后在构造函数传入自己的引擎,然后调用start()*/CombustionCar combustionCar = new CombustionCar();combustionCar.start();ElectricCar electricCar = new ElectricCar();electricCar.start();/*** 控制反转思想* 把自己看作实例化car的客户端,需要什么引擎,直接传入相关对象*/CombustionEngine combustionEngine = new CombustionEngine();Car combustionCar = new Car(combustionEngine);combustionCar.start();ElectricEngine electricEngine = new ElectricEngine();Car electricCar = new Car(electricEngine);electricCar.start();}

​ 从上面的例子我们可以看到,实例化 Car 类的客户端可以控制所使用的Engine实现,并且取决于将哪个Engine实现传递给Car构造函数,Car对象的行为发生巨大变化。接下来就是依赖注入的领域了,两者结合,妙不可言。

三、依赖注入(Dependency Injection

Tips:在上面控制反转的知识点,解决了由谁决定使用哪种Engine实现的问题,但是不可避免,我们也更改了实例化一个Car对象的步骤;

Step-1:最开始,我们实例化Car不需要参数,因为在它的构造函数里面已经为我们newEngine对象。

Step-2:使用IoC方法之后,我们要求在实例化一个Car之前,我们需要先创建一个Engine对象,并作为参数传递给Car构造对象。

区别:

  1. 最初,我们首先实例化Car对象,然后实例化Engine对象。但是,使用IoC之后,我们首先实例化Engine对象,然后实例化Car对象;
  2. 因此,我们在上面的过程中创建了一个依赖关系。
  3. 这种依赖关系不是指编译时候 Car类对Engine接口的依赖关系,相反,我们构建了一个运行时依赖关系
  4. 运行时,实例化Car对象之前,必须首先实例化Engine对象。

思考:我们构建的这种运行时依赖关系会给我们带来什么样的影响呢?

1、依赖关系树

某一个具体的依赖对象大家可以理解为Spring中的bean,对于两个有依赖关系的bean,其中被依赖的那个bean,我们把它称为依赖对象

​ 我们用图形化的方式来看看它们之间的依赖关系,其中图形的节点代表对象,箭头代表依赖关系(箭头指向依赖对象)。对于我们我的Car类,依赖关系树非常简单:

在这里插入图片描述

​ 如果依赖关系树的终端结点还有自己的附加依赖关系,那么这个依赖关系树将变得更加复杂。

​ 现在再看我们上面的例子,如果 CombustionEngine 还有其他依赖对象,我们首先需要创建CombustionEngine依赖对象,然后才能实例化一个CombustionEngine对象。这样在创建Car对象时候,才能将CombustionEngine传递给Car的构造函数;

//凸轮轴        
public class Camshaft {}
//机轴
public class Crankshaft {}public class CombustionEngine implements Engine {//凸轮轴private Camshaft camshaft;//机轴private Crankshaft crankshaft;public CombustionEngine(Camshaft camshaft, Crankshaft crankshaft) {this.camshaft = camshaft;this.crankshaft = crankshaft;}@Overridepublic void turnOn() {System.out.println("燃油引擎启动");}}

经过我们改造,我们现在的依赖关系树变为下面的样子

在这里插入图片描述

2、 依赖注入框架

​ 随着我们不断引入更多的依赖关系,这种复杂性将继续增长。为了解决这个复杂问题,我们需要基于依赖关系树抽取对象的创建过程。这就是依赖注入框架

一般来说,我们可以把这个过程分为三个部分:

  1. 声明需要创建的对象需要哪些依赖对象
  2. 注册创建这些依赖对象所需要的类
  3. 提供一种使用1和2两点思想创建对象的机制

​ 通过反射,我们可以查看 Car 类的构造函数,并且知道它需要一个 Engine 参数。因此为了创建Car对象,我们必须创建至少一个Engine接口的实现类用作依赖项来使用。在这里,我们创建一个CombustionEngine 对象(为了方便,暂时当做只有一个实现类,bean冲突问题待会再说)来声明它作为依赖项来使用,就满足Car对象创建时的需求。

​ 其实,这个过程是递归的,因为CombustionEngine 依赖于其他对象,我们需要不断重复第一个过程,直到把所有依赖对象声明完毕,然后注册创建这些依赖对象所需要的类。

​ 第三点其实就是将前面两点思想付诸实施,从而形成一种创建对象的机制

举个例子

  1. 比如我们需要一个Car对象,我们必须遍历依赖关系树并检查是否存在至少一个符合条件的类来满足所有依赖关系。

  2. 例如,声明CombustionEngine类可满足Engine节点要求。如果存在这种依赖关系,我们将实例化该依赖关系,然后移至下一个节点。

  3. 如果有一个以上的类满足所需的依赖关系,那么我们必须显式声明应该选择哪一种依赖关系。

  4. 一旦我们确定所有的依赖关系都准备好了,我们就可以从终端节点开始创建依赖对象。

  5. 对于 Car 对象,我们首先实例化 CamshaftCrankshaftーー因为这些对象没有依赖关系ーー然后将这些对象传递给 CombustionEngine 构造函数,以实例化 CombunstionEngine 对象。最后,我们将 CombunstionEngine 对象传递给 Car 构造函数,以实例化所需的 Car 对象。

了解了 DI 的基本原理之后,我们现在可以继续讨论 Spring 如何执行 DI

3、 Spring的依赖注入

TipsSpring的核心是一个DI框架,它可以将DI配置转换为Java应用程序。

​ 在这里我们要阐述一个问题:那就是库和框架的区别库只是类定义的集合

​ 背后的原因仅仅是代码重用,即获取其他开发人员已经编写的代码。这些类和方法通常在域特定区域中定义特定操作。例如,有一些数学库可让开发人员仅调用函数而无需重做算法工作原理的实现。

框架通常被认为是一个骨架,我们在其中插入代码以创建应用程序。许多框架保留了特定于应用程序的部分,并要求我们开发人员提供适合框架的代码。在实践中,这意味着编写接口的实现,然后在框架中注册实现。

在这里插入图片描述

4、 ApplicationContext

​ 在 Spring 中,框架围绕 ApplicationContext 接口实现上一节中概述的三个 DI 职责。通常这个接口代表了一个上下文。因此,我们通过基于 java 或基于 xml 的配置向 ApplicationContext 注册合适的类,并从 ApplicationContext 请求创建 bean 对象。然ApplicationContext 构建一个依赖关系树并遍历它以创建所需的 bean对象。

在这里插入图片描述

Applicationcontext 中包含的逻辑通常被称为 Spring 容器。通常,一个 Spring 应用程序可以有多个 ApplicationContext,每个 ApplicationContext 可以有单独的配置。例如,一个 ApplicationContext 可能被配置为使用 CombustionEngine 作为其引擎实现,而另一个容器可能被配置为使用 ElectricEngine 作为其实现。

参考文章

  1. 为什么我们需要依赖注入? - 知乎 (zhihu.com)
  2. 据说,80%的人没有真正理解了Spring的依赖注入 - 知乎 (zhihu.com)
  3. 依赖注入是什么?如何使用它? (freecodecamp.org)

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

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

相关文章

const在指针中的作用以及*p在各种写法中分别代表什么含义

const在指针中起固定的作用,在不同的写法中其效果也有所区别,具体如下: 1、int* const p固定的是指针p指向的地址。 2、int const *p固定的是指针p指向地址中储存的内容。 例: 以上操作在编译器中执行不了,会报错。…

武汉星起航:助力跨境电商新手,打造高质量亚马逊产品评价新策略

在今日全球化与数字化浪潮的推动下,跨境电商已成为推动国际贸易发展的新动力。然而,随着市场竞争的日益激烈,如何让自己的产品在亚马逊平台上脱颖而出,成为了众多跨境电商新手面临的重要问题。武汉星起航电子商务有限公司&#xf…

【AI系列】Python NLTK 库和停用词处理的应用

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Codeforces Round 934 (Div. 2) D. Non-Palindromic Substring

题目 思路&#xff1a; #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e6 5, inf 1e9, maxm 4e4 5; co…

【ERP原理与应用】用友U8实验

实验一、系统管理与基础设置 实验内容&#xff1a; 一、核算体系的建立 好友软件公司是一家软件制造和系统集成企业&#xff0c;其产品面向国内外市场&#xff0c;自 2019 年 3 月公司开始使用 ERP 软件管理业务。软件操作员有三位&#xff0c;黄红是账套 主管&#xff0c;张…

【C++】string类(常用接口)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;http://t.csdnimg.cn/eCa5z 目录 修改操作 push_back append operator assign insert erase replace c_str find string类非成…

量化交易入门(二十五)什么是RSI,原理和炒股实操

前面我们了解了KDJ&#xff0c;MACD&#xff0c;MTM三个技术指标&#xff0c;也进行了回测&#xff0c;结果有好有坏&#xff0c;今天我们来学习第四个指标RSI。RSI指标全称是相对强弱指标(Relative Strength Index),是通过比较一段时期内的平均收盘涨数和平均收盘跌数来分析市…

论文研读:Transformers Make Strong Encoders for Medical Image Segmentation

论文&#xff1a;TransUNet&#xff1a;Transformers Make Strong Encoders for Medical Image Segmentation 目录 Abstract Introduction Related Works 各种研究试图将自注意机制集成到CNN中。 Transformer Method Transformer as Encoder 图像序列化 Patch Embed…

Net8 ABP VNext完美集成FreeSql、SqlSugar,实现聚合根增删改查,完全去掉EFCore

没有基础的&#xff0c;请参考上一篇 彩蛋到最后一张图里找 参考链接 结果直接上图&#xff0c;没有任何业务代码 启动后&#xff0c;已经有了基本的CRUD功能&#xff0c;还扩展了批量删除&#xff0c;与动态查询 动态查询截图&#xff0c;支持分页&#xff0c;排序 实现原理…

消息队列经典应用场景

笔者心中,消息队列,缓存,分库分表是高并发解决方案三剑客。 在职业生涯中,笔者曾经使用过 ActiveMQ 、RabbitMQ 、Kafka 、RocketMQ 这些知名的消息队列 。 这篇文章,笔者结合自己的真实经历,和大家分享消息队列的七种经典应用场景。 1 异步&解耦 笔者曾经负责某电…

Charles抓包配置代理手机连接

Charles下载地址&#xff1a; Charles_100519.zip官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘123云盘为您提供Charles_100519.zip最新版正式版官方版绿色版下载,Charles_100519.zip安卓版手机版apk免费下载安装到手机,支持电脑端一键快捷安装https://www.123pan.com…

Scala介绍与环境搭建

Scala环境搭建与介绍 一、Scala环境搭建 1、环境准备与下载 2、验证Scala 3、IDEA新建项目&#xff0c;配置Scala&#xff0c;运行Hello world 二、Scala介绍 1、Scala 简介 2、Scala 概述 一、Scala环境搭建 1、环境准备与下载 JDK1.8 Java Downloads | Oracle 下载需求版本…

酒店能源监测管理系统:实现节能减排与提升管理效率的利器

随着全球能源问题的日益突出和可持续发展理念的深入人心&#xff0c;酒店业也在积极探索节能减排的途径。在这一背景下&#xff0c;酒店能源监测管理系统应运而生&#xff0c;成为了酒店行业提升管理效率、降低能源消耗的重要工具。本文将从多个角度介绍酒店能源监测管理系统的…

Elastic 8.13:Elastic AI 助手中 Amazon Bedrock 的正式发布 (GA) 用于可观测性

作者&#xff1a;来自 Elastic Brian Bergholm 今天&#xff0c;我们很高兴地宣布 Elastic 8.13 的正式发布。 有什么新特性&#xff1f; 8.13 版本的三个最重要的组件包括 Elastic AI 助手中 Amazon Bedrock 支持的正式发布 (general availability - GA)&#xff0c;新的向量…

360奇酷刷机 360刷机助手 QGDP360手机QGDP刷机

360奇酷刷机 360刷机助手 QGDP破解版360手机QGDP刷机 360手机刷机资源下载链接&#xff1a;360rom.github.io 参考&#xff1a;360手机-360刷机360刷机包twrp、root 360奇酷刷机&#xff1a;360高通驱动安装 360手机刷机驱动&#xff1b;手机内置&#xff0c;可通过USB文件传输…

前端学习<二>CSS基础——11-CSS3属性详解(一)

前言 我们在上一篇文章中学习了CSS3的选择器&#xff0c;本文来学一下CSS3的一些属性。 本文主要内容&#xff1a; 文本 盒模型中的 box-sizing 属性 处理兼容性问题&#xff1a;私有前缀 边框 背景属性 渐变 文本 text-shadow&#xff1a;设置文本的阴影 格式举例&…

关于MD5加密

1、什么是MD5 计算机安全领域广泛使用的一种散列函数&#xff0c;是用以提供消息的完整性保护 2、MD5的优势 &#xff08;1&#xff09;压缩性&#xff1a;任意长度的密码进过MD5加密后的长度是固定的 &#xff08;2&#xff09;容易计算&#xff1a;从原数字计算到MD5很简单 &…

分享全栈开发医疗小程序 -带源码课件(课件无解压密码),自行速度保存

课程介绍 分享全栈开发医疗小程序 -带源码课件&#xff08;课件无解压密码&#xff09;&#xff0c;自行速度保存&#xff01;看到好多坛友都在求SpringBoot2.X Vue UniAPP&#xff0c;全栈开发医疗小程序 - 带源码课件&#xff0c;我看了一下&#xff0c;要么链接过期&…

我于窗中窥月光,恰如仰头见“链表”(Java篇)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

编程实现黄金分割法、平分法和不精确一维搜索等最优化算法

解&#xff1a; 1、黄金分割法 思想&#xff1a; 黄金分割法是通过不断缩短搜索区间的长度来寻求一维函数的极小点&#xff0c;这种方法的基本原理是&#xff1a;在搜索区间[a,b]内按如下规则对称地取两点a1和a2 a1a0.382(b-a); a2a0.618(b-a); 黄金分割法的搜索过程是&#x…