我们通常所说的 Spring 指的是 Spring Framework(Spring 框架),它是⼀个开源框架,有着活跃⽽庞 ⼤的社区,这就是它之所以能⻓久不衰的原因。Spring ⽀持⼴泛的应⽤场景,它可以让 Java 企业级的 应⽤程序开发起来更简单。
⽤⼀句话概括 Spring:Spring 是包含了众多⼯具⽅法的 IoC 容器
由此引发的问题是:什么是容器? 什么又是Ioc容器???
容纳某种物品的装置
比如前面学数据结构中的List / Map (容纳数据)等等
包括 Tomcat (Web容器)
Ioc实际上是控制反转的意思 —> Inversion of control
如何理解控制反转?
传统程序开发
假设我们去构建一辆车,利用传统的程序思想进行开发…
构建⼀辆⻋(Car Class),然⽽⻋需要依赖⻋身(FrameWork Class),⽽⻋身需要依赖底盘(Bottom Class),⽽底盘需要依赖轮胎(Tire Class),最终程序的实现代码如下
```java
在这里插入代码片
```public class NewCarExample {
public static void main(String[] args) {
Car car = new Car();
car.init();
}
/**
* 汽车对象
*/
static class Car {
public void init() {
// 依赖车身
Framework framework = new Framework();
framework.init();
}
}
/**
* 车身类
*/
static class Framework {
public void init() {
// 依赖底盘
Bottom bottom = new Bottom();
bottom.init();
}
}
/**
* 底盘类
*/
static class Bottom {
public void init() {
// 依赖轮胎
Tire tire = new Tire();
tire.init();
}
}
/**
* 轮胎类
*/
static class Tire {
// 尺寸
private int size = 30;
public void init() {
System.out.println("轮胎尺寸:" + size);
}
}
}
通过代码我们发现了一些规律, Car类依赖于Framework,也就是new Car的时候,Framework也被创建了,并且调用init方法,接下来调用的init方法又自动去new Bottom,因为通过这些类的含义知道,他们之间是相互依赖的,如果你想去建造一辆车,就需要去保证这种依赖关系
但是这种设计思想也有缺陷,当你满足不同客户的需求的时候,轮胎的尺寸不同,你就要去修改参数,又或者你想加别的参数,可能解决方法是 在每个类中加上参数,通过参数传递就行了呀.实际上这种方式问题很大,因为这几个类的依赖性很强,我们不断的因为客户的需求而修改类的代码是很麻烦而且很容易出Bug的行为.
传统程序开发的缺陷:
以上程序中,轮胎的尺⼨的固定的,然⽽随着对的⻋的需求量越来越⼤,个性化需求也会越来越多,这 时候我们就需要加⼯多种尺⼨的轮胎,那这个时候就要对上⾯的程序进⾏修改了
public class NewCarExample2 {
public static void main(String[] args) {
Car car = new Car();
car.init(50, "猛男粉");
}
/**
* 汽车对象
*/
static class Car {
public void init(int size, String color) {
// 依赖车身
Framework framework = new Framework();
framework.init(size, color);
}
}
/**
* 车身类
*/
static class Framework {
public void init(int size, String color) {
// 依赖底盘
Bottom bottom = new Bottom();
bottom.init(size, color);
}
}
/**
* 底盘类
*/
static class Bottom {
public void init(int size, String color) {
// 依赖轮胎
Tire tire = new Tire();
tire.init(size, color);
}
}
/**
* 轮胎类
*/
static class Tire {
// 尺寸
// private int size = 30;
public void init(int size, String color) {
System.out.println("轮胎尺寸:" + size + " | 颜色:" + color);
}
}
}
这种就是不断的修改类中的代码,实际上并不能完全解决问题反而会出很大的问题.
从以上代码可以看出,以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要修 改。
我们可以尝试不在每个类中⾃⼰创建下级类,如果⾃⼰创建下级类就会出现当下级类发⽣改变操作,⾃ ⼰也要跟着修改。 此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不需 要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本身也⽆需修改任 何代码,这样就完成了程序的解耦。 解决传统开发中的缺陷
PS:解耦指的是解决了代码的耦合性,耦合性也可以换⼀种叫法叫程序相关性。好的程序代码的耦合 性(代码之间的相关性)是很低的,也就是代码之间要实现解耦
实际上IoC就是解耦操作
这就好⽐我们打造⼀辆完整的汽⻋,如果所有的配件都是⾃⼰造,那么当客户需求发⽣改变的时候, ⽐如轮胎的尺⼨不再是原来的尺⼨了,那我们要⾃⼰动⼿来改了,但如果我们是把轮胎外包出去,那 么即使是轮胎的尺⼨发⽣变变了,我们只需要向代理⼯⼚下订单就⾏了,我们⾃身是不需要出⼒的
基于上述思路,之前我们是通过在构造方法中创建依赖类的方法,现在换一种思路就是改为 依赖注入的方法
public class IocCarExample {
public static void main(String[] args) {
Tire tire = new Tire(50, "红色");
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
static class Car {
private Framework framework;
public Car(Framework framework) {
this.framework = framework;
}
public void run() {
framework.init();
}
}
static class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
}
public void init() {
bottom.init();
}
}
static class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
}
public void init() {
tire.init();
}
}
static class Tire {
private int size;
private String color;
public Tire(int size, String color) {
this.size = size;
this.color = color;
}
public void init() {
System.out.println("轮胎:" + size + " | 颜色:" + color);
}
}
}
仔细观察这种代码跟上面的有什么区别???
这是很重要的一点
当我们new Car的时候,已经创建好了framework并将framework注入到car的构造方法中,并且 car.run中去执行framework的init方法
每个类的init方法实际上都是去执行下一个类的init方法
反正这种代码是很精妙的,需要自己好好看看,博主在这也说不太懂,毕竟刚学…
但是最重要的一点是,你跟着代码的思路走发现他确确实实解决了上树的问题,无论我们怎么去改或者加多少个参数,都是很简单的事情,只需要在new Tire那里和 Tire类本身进行修改即可
这就是IoC思想,接下里的两张图可以进行对比一下

我们发现了⼀个规:,通⽤程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了 Framework,Framework 创建并创建了 Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再是 上级对象创建并控制下级对象了,⽽是下级对象把注⼊将当前对象中,下级的控制权不再由上级类控制 了,这样即使下级类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实 现思想。
在传统设计里面,我们new 的是Car,但是改的是Tire,由于改了Tire就要改Bottom等等
在IoC中,我们通过注入依赖的方式,改的还是Tire,但是我们已经将Tire注入到Bottom中去了,所以Bottom也随之改变了,之后也是如此,所以他主要是对之前的控制顺序进行了反转,所以IoC叫做控制反转
IoC的设计思想说完了,那么如何去理解Spring是一个Ioc容器呢???
既然 Spring 是⼀个 IoC(控制反转)容器,重点还在“容器”⼆字上,那么它就具备两个最基础的功能: 将对象存⼊到容器; 从容器中取出对象。 也就是说学 Spring 最核⼼的功能,就是学如何将对象存⼊到 Spring 中,再从 Spring 中获取对象的过 程。
将对象存放到容器中的好处:将对象存储在 IoC 容器相当于将以后可能⽤的所有⼯具制作好都放到仓 库中,需要的时候直接取就⾏了,⽤完再把它放回到仓库。⽽ new 对象的⽅式相当于,每次需要⼯具 了,才现做,⽤完就扔掉了也不会保存,下次再⽤的时候还得重新做,这就是 IoC 容器和普通程序开 发的区别。
Spring 是⼀个 IoC 容器,说的是对象的创建和销毁的权利都交给 Spring 来管理了,它本身⼜具备了存 储对象和获取对象的能⼒。
说到 IoC 不得不提的⼀个词就是“DI”,DI 是 Dependency Injection 的缩写,翻译成中⽂是“依赖注 ⼊”的意思。 所谓依赖注⼊,就是由 IoC 容器在运⾏期间,动态地将某种依赖关系注⼊到对象之中。所以,依 赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容 器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。 IoC 是“⽬标”也是⼀种思想,⽽⽬标和思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就 属于具体的实现。
DI就是我们讲的依赖注入的实现,通过将下级类注入进去的具体实现
所以建议大家多去看看这种通过注入方式来实现的代码,这对我们如何去设计代码是很重要的
Spring作为容器,最重要的两个功能就是:
接下来使⽤ Maven ⽅式来创建⼀个 Spring 项⽬,创建 Spring 项⽬和 Servlet 类似,总共分为以下 3 步:


在项⽬的 pom.xml 中添加 Spring 框架的⽀持,xml 配置如下:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
</dependencies>
从上面的依赖可以看到,添加了两个框架:
这跟Servlet很像的,都是需要去添加依赖来下载jar包

这里需要去设置依赖源,博主这里设置的国内的阿里源,最重要的是需要勾选住,否则后期下载别的依赖就会很慢(下载国外的源 网络不太行)


到这里Spring的配置和创建就完成了
存储 Bean 分为以下 2 步:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
接下来,再将 User 对象注册到 Spring 中就可以,具体操作是在 中添加如下配置:
<beans>
<bean id="user" class="com.bit.User"></bean>
</beans>
在beans里面每一个bean都代表一个注册类,id就不是类名,而是beanName
class代表的是注册类存放的位置

写到这里之后,意味着bean注册成功

然后就可以利用spring-context上下文来将bean对象取出来用


这里通过Application来进行创建的,当然还有别的方法

ApplicationContext 和 BeanFactory 效果是⼀样的,ApplicationContext 属于 BeanFactory 的⼦类, 它们的区别如下。
ApplicationContext VS BeanFactory(常⻅⾯试题):
相同点:都从容器中获取bean,并且都有getBean方法
不同点1、ApplicationContext属于BeanFactory的子类。BeanFactory 只提供了基础访问Bean法,
而ApplicationContext除了拥有BeanFactory的所有功能之外,还提供了更的方法实现,比如对国际化的支持、资源访问的支持、以及事件和传播等方面的支持。
2、从性能方面来说二者是不同,BeanFactory 是按需加载Bean,ApplicationContext是饿汉方式,
在创建时会将所有的Bean都加载起来,以备以后使用。
PS:⽽ ClassPathXmlApplicationContext 属于 ApplicationContext 的⼦类,拥有 ApplicationContext 的所有功能,是通过 xml 的配置来获取所有的 Bean 容器的

第一种里面的"userinfo"要跟当时的bean id对应,并且返回的是一个object类型还需要强转才能拿到
第二种里面直接通过类型进行获取,但是假设同一个类型注册了多次就会出现问题,他要保证注册次数是唯一
这就是注册两次的情况,所以不建议使用


第三种就是双重保险

经过前⾯的学习,我们已经可以实现基本的 Spring 读取和存储对象的操作了,但在操作的过程中我们 发现读取和存储对象并没有想象中的那么“简单”,所以接下来我们要学习更加简单的操作 Bean 对象的 ⽅法。
在 Spring 中想要更简单的存储和读取对象的核⼼是使⽤注解,也就是我们接下来要学习 Spring 中的相 关注解,来存储和读取 Bean 对象。
之前我们在注册类的时候,每注册一个类都要写一行配置,现在可以通过注解的方式来更方便的完成
在开始存对象之前,需要准备工作
注意:想要将对象成功的存储到 Spring 中,我们需要配置⼀下存储对象的扫描包路径,只有被配置的 包下的所有类,添加了注解才能被正确的识别并保存到 Spring 中
在spring-config.xml下添加一下信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package=" ">
</content:component-scan>
</beans>

base-package就是设置的扫描路径,你可以把想要注册的类都放到这个路径下,他就会自动去扫描注册
也就是说,即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的
想要将对象存储在 Spring 中,有两种注解类型可以实现:
接下来我们分别来看。 使⽤ @Controller 存储 bean 的代码如下所示:
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
更简单的注册结束了,还是利用原来的方式进行上下文的读取
public class Application {
public static void main(String[] args) {
// 1.得到 spring 上下⽂
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到 bean
UserController userController = (UserController)
context.getBean("userController");
// 3.调⽤ bean ⽅法
userController.sayHi("Bit");
}
}
类注解一共是五类,分别是
既然功能是⼀样的,为什么需要这么多的类注解呢?
这和为什么每个省/市都有⾃⼰的⻋牌号是⼀样的?⽐如陕⻄的⻋牌号就是:陕X:XXXXXX,北京的⻋ 牌号:京X:XXXXXX,⼀样。甚⾄⼀个省不同的县区也是不同的,⽐如⻄安就是,陕A:XXXXX,咸 阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,⼀样。这样做的好处除了可以节约号码之外,更重要的作 ⽤是可以直观的标识⼀辆⻋的归属地。 那么为什么需要怎么多的类注解也是相同的原因,就是让程序员看到类注解之后,就能直接了解当前类 的⽤途,⽐如: @Controller:表示的是业务逻辑层; @Servie:服务层; @Repository:持久层; @Configuration:配置层。
一个完成的程序必然是有很逻辑化的步骤顺序来执行的,直到Resposity这一层才能和数据库进行交互
这些类注解之间的关系通过源码可以看到

通过上⾯示例,我们可以看出,通常我们 bean 使⽤的都是标准的⼤驼峰命名,⽽读取的时候⾸字⺟⼩ 写就可以获取到 bean 了,如下图所示:

实际上小驼峰的类名就是BeanName,因为现在采取更简单的存储Bean方法,所以不需要去一行一行的注册,通过注解的方式来实现第一种取出Bean,但是没有了ID
User user = (User) context.getBean(“user”);
此时getBean里面的参数就是类名,但是这个类名有点古怪
先说结论:
当类似于User的时候,beanName就是 user
当类似于UCompontent的时候,beanName就不变了
我们可以找BeanName命名方式的源码,进行查找
实际上beanName的命名方式是属于jdk而不是spring的jar包的
连续按两次shift出现查找框




然⽽,当我们写完以上代码,尝试获取 bean 对象中的 user1 时却发现,根本获取不到

因为Bean注解是方法注解,要配合着类注解一起使用才能生效
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
}
这样就大功告成了…