1.初识Spring
Spring 是包含了众多⼯具方法的 IoC 容器(控制反转容器)
- 容器: 容器是用来容纳某种物品的基本装置
- Spring 也是容器
-
IoC: Inversion of Control 翻译成中⽂是 “控制反转” 的意思
- Spring 让控制权发生了反转, 不再是上级对象创建并控制下级对象了, 而是把下级对象注入到当前对象中, 下级的控制权不再由上级类控制了, 这样即使下级类发生任何改变, 当前类都是不受影响的, 这就是典型的控制反转, 也就是 IoC 的实现思想
1.1.SpringIoC容器的核心功能
Spring 是⼀个 IoC 容器, 说的就是将对象的创建和销毁的权利都交给 Spring 来管理了, 它本身又具备了存储对象和获取对象的能力
- 既然Spring 是⼀个 IoC 容器, 那容器就会拥有两个最基础的功能
- 将Bean(对象)存储到Spring(容器)中
- 将Bean(对象)从Spring(容器)中取出来
- 所以学 Spring 最核心的功能, 就是学如何将对象存⼊到 Spring 中, 再从 Spring 中获取对象的过 程
1.2.DI 和 IoC
说到 IoC 不得不提的⼀个词就是 “DI”
- DI 是 Dependency Injection 的缩写, 翻译成中⽂是 “依赖注入” 的意思
- 所谓依赖注入, 就是由 IoC 容器在运行期间, 动态地将某种依赖关系注⼊到对象中(这里控制权就发生了反转). 所以依赖注⼊(DI)和控制反转(IoC)是从不同的角度描述的同⼀件事情
IoC和DI的区别
- IoC是一种实现的思想
- 例: 多线程中的乐观锁(思想)
- DI是对思想的具体实现
- 例: 多线程中的CAS(乐观锁的具体实现)
2.Spring的创建和使用
图示Spring的创建和使用
2.1.创建Spring项目
使⽤ Maven ⽅式来创建⼀个 Spring 项⽬分为3步
- 创建⼀个普通 Maven 项⽬
- 添加 Spring 框架支持(spring-context、spring-beans)
- 添加启动类
2.1.1.创建普通Maven项目
首先选择New --> Project 新建一个Maven项目.
选择Maven 后 点击Next 即可.不需要勾选任何添加.
选择项目要创建的目录,并更改项目名称
2.1.2.添加 Spring 框架支持
在pom.xml中添加依赖并刷新依赖.等待右下角进度条下载完成即可.
下面是我导入的依赖.大家也可以在*仓库中搜索依赖并添加.
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
</dependencies>
2.1.3.添加启动类
在src->main->java 目录下.创建启动类 名称可以自定义.
在启动类中添加main方法即可.
这样,我们的Spring项目就创建好了.
2.2.存储Bean对象
所谓的 Bean 就是 Java 语⾔中的⼀个普通对象.
存储 Bean 分为以下 2 步
- 存储 Bean 之前, 先要有 Bean 才可以, 因此先要创建⼀个 Bean
- 将创建的 Bean 注册到 Spring 容器中
2.2.1.创建Bean
首先在java目录下创建一个文件夹用于存放我们的Bean.在文件夹下创建一个User对象.
给User类添加一个方法.这样我们就创建好了用于展示的Bean.
User类代码.
package com.beans;
/**
* name User
* @deprecated exercises:
* 普通类,普通Bean对象
*/
public class User {
public void sayHi(String name) {
System.out.println("Hello: " + name);
}
}
2.2.2.创建xml配置文件
- 首先在 resources 目录下创建配置文件.可以自定义名称(这里使用spring.xml)
- 添加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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
2.2.3.将 Bean 注册到容器中
- 在刚创建的配置文件中,添加 bean 标签,即可将Bean注册到容器中.
- 存放方式类似于Map<String,Object>
- id是存放入容器中的key值,用来获取Bean对象
- class是存放对象的包名+类名
- 存放方式类似于Map<String,Object>
<bean id="user" class="com.beans.User"></bean>
2.3.获取并使用 Bean 对象
获取并使用 Bean 对象, 分为以下 3 步
- 得到 Spring 上下文对象, 因为对象都交给 Spring 管理了, 所以获取对象要从 Spring 中获取, 那么就要先得到 Spring 的上下文
- 两种获取方式,注意参数要和配置文件名称相同
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“spring.xml”);
- BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(“spring.xml”));
- 两种获取方式,注意参数要和配置文件名称相同
- 通过 Spring 上下文,获取某⼀个指定的 Bean 对象
- 获取有三种方式
- 通过id获取Bean对象
- 该方法返回Object类型,所以获取后需要强转
- 通过类型获取Bean对象
- 该方法的好处是直接确定了类型, 不需要强转
- 缺点是如果Spring容器中注册了多个此类型对象时, Spring容器会不知道你获取的对象是谁, 从而会报错
- 通过id+类型获取Bean对象
- 不需要强转, 也不会查询到多个, 因为注册到Spring容器中对象的id是唯一的
- 缺点是书写比较麻烦
- 通过id获取Bean对象
- 获取有三种方式
- 使用 Bean 对象
- 如果取多个 Bean 的话重复以上第 2、3 步骤
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
public class App {
public static void main(String[] args) {
//获取上下文对象,两种方式 都可以用来获取Bean对象
//1.得到Spring上下文对象(参数要和配置文件名称相同)
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//2.bean工厂
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring.xml"));
//根据上下文对象提供的方法获取到bean 获取有三种方式
//1.根据id获取bean 方法的参数要和xml中的id对应,方法返回Object类型,需要强转.
User user1 = (User) applicationContext.getBean("user");
//使用
user1.sayHi("echo");
//2.根据类型获取bean对象,只适用于Spring容器中只注册了一个此类型对象
//该方式的好处是 不需要强转
//(当Spring中存在多个同一个类型对象不适用)
User user2 = applicationContext.getBean(User.class);
user2.sayHi("echo");
//3.根据类型+id 获取bean对象
//不需要强转,也不会查询到多个,因为注册到Spring容器中对象的id是唯一的.
User user3 = applicationContext.getBean("user", User.class);
user3.sayHi("echo");
//根据beanFactory来获取对象.三种方式同样适用
User user4 = (User) beanFactory.getBean("user");
user4.sayHi("XX");
User user5 = beanFactory.getBean(User.class);
user5.sayHi("XX");
User user6 = beanFactory.getBean("user", User.class);
user6.sayHi("XX");
}
}
2.3.1.ApplicationContext VS BeanFactory
相同点: 都可以实现从容器中获取Bean,都提供了getBean方法
不同点:
- ApplicationContext 属于 BeanFactory的子类
- BeanFactory 只提供了基础访问Bean的方法
- ApplicationContext 除了拥有 BeanFactory 的所有功能之外,还提供了更多的方法实现
- 比如对国际化的支持、资源访问的支持、以及事件和传播等方面的支持
- 从性能方面来说二者是不同
- BeanFactory 是按需加载 Bean,更轻量
- ApplicationContext 是饿汉方式, 在创建时会将所有的Bean都加载起来,以备以后使用
3.Spring 更简单的存储和读取对象 – 注解
3.1.配置扫描路径
要使用注解的方式, 我们首先要 配置扫描路径 让Spring启动时扫描 配置(com.beans)包 中的注解.只有被配置的包下的所有类, 添加注解才能被正确的识别并保存到 Spring 中.
在我们之前的配置文件 “spring.xml” 中配置扫描路径(这里配置的 com.beans Spring启动时就会扫描这个包)
<?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="com.beans">
<!-- spring.xml(resources下创建)(com.beans):所有要存放spring的根目录 -->
</content:component-scan>
</beans>
3.2.更简单的存储对象(认识注解)
- 五大注解(类注解)
- @Controller
- @Service
- @Repoistory
- @Configuration
- @Component
- 方法注解
- @Bean
五大注解是在类之前添加的注解, 添加之后可以让这个类注册到Spring容器中.而方法注解则是要在方法前添加的注解, 可以将方法的返回值注册到Spring容器中.要注意的是方法注解一定要配合五大注解来使用.
3.2.1.五大注解的使用
五大注解的功能是相同的, 都是将类注册到Spring容器中. 既然功能是相同的, 那为什么还要有五大注解? 原因就是可以使程序员通过注解来了解类的作用.
- @Controller :控制器
- @Service :服务
- @Repoistory :仓库
- @Configuration :配置
- @Component :组件
如图,保证这个类所在的包在注册的包范围内.并且在类前添加了五大注解.这个类就被注册到Spring中了
添加到Spring容器中之后, 我们就可以通过之前的方法来获取和使用Bean对象了.
五大注解的命名规则
五大注解的命名规则是根据 JDK Introspector 中的 decapitalize 方法 来执行的.(图中代码为源码)
根据源码,我们可以知道命名规则:
如果名称长度大于1, 并且名称中第一个字符和第二个字符都是大写, 就返回原类名.
否则就将类名中的第一个字符从大写转变为小写返回.
知道了五大注解的命名规则. 我们在获取通过五大注解注册的Bean对象时就不会发生不知道名称的问题了.
五大注解之间的关系
观察 @Controller / @Service / @Repository / @Configuration 注解的源码就会发现. 这四个注解都是 @Component 注解的子类
3.2.2.方法注解 @Bean
类注解是添加到某个类上的, 而方法注解是放到某个方法上的. 但是要注意的是 方法注解是要配合类注解来使用的. 如果只写了方法注解,没有在类上添加注解. Spring在启动的时候是不会扫描到的.(可以理解为方法注解只会缩小扫描范围,不会扩大扫描范围)
同样: 添加好方法注解和类注解之后, 方法的返回值就被添加到Spring容器中. 我们就可以之前的方法来获取对象.
方法注解的使用方式
- 直接在方法上添加@Bean注解
- 使用@Bean注解, 并添加name属性用来重命名 -> @Bean(name = “name”)
- 设置名称可以设置单个,也可以通过 {} 设置多个 -> @Bean(name = {“name1”, “name2”})
@Bean 的命名规则
方法注解的命名规则和五大注解的命名规则大有不同.
- 当没有设置name属性时, 那么Bean默认的名称就是方法名
- 当设置了name属性之后,就只能通过重命名的name属性对应的值来获取
- 重命名之后,无法再使用方法名获取bean对象
3.3.更简单的获取对象(对象注入)
3.3.1.对象注入有两种方式
- @Autowired 注解
- @Resource 注解
- 两种注解实现的效果是相同的
- 直接使用注解的时候,会根据类型来寻找对象.
- 如果可以找到的对象只有一个, 就会直接注入成功.
- 如果找到的对象有多个,就会继续根据名称来寻找.
- 如果有名称相同的就注入,没有就会报错.
- 在不能更改对象名称的时候
- 也就是不能更改名称,又要查询另一个名称时
- 可以使用@Resource注解的name属性设置对象名称
- @Autowired没有此属性.所以需要@Qualifier注解配合
- @Qualifier只有value一个属性,所以可以省略
这样在根据类型查询到多个对象时,就会属性设置的新名称来查询
@Resource vS @Autowired
既然两种注解实现的效果是相同的,那这两个注解有什么不同呢?
- 出身不同
- @Autowired是Spring框架提供的
- @Resource 来自于JDK (Java亲儿子)
- 用法不同
- @Autowired 支持属性注入、构造方法注入和Setter注入
- @Resource 不支持构造方法注入
- 支持的参数不同
- @Autowired 只支持required 这一个参数
- @Resource 支持更多的参数设置,比如 name、type 设置
3.3.2.根据注解完成对象装配(对象注入)的实现方法有以下 3 种
- 属性注入
- 直接在属性上添加 @Autowired注解 或 @Resource注解即可
- 构造方法注入
- 在构造方法上添加 @Autowired注解
- @Resource注解不支持构造方法注入
- Setter 注入
- 在Setter方法上添加 @Autowired注解 或 @Resource注解即可
4.Bean的作用域和生命周期
4.1.Bean的作用域
Bean的作用域类型有六种,注意后 4 种状态是 Spring MVC 中的值, 普通的 Spring 项目中只有前两种
- singleton: 单例模式(Bean默认作用域类型)
- prototype: 原型模式(多例模式)
- request: 请求作用域(存在于Spring MVC中) 范围是一次请求
- session: 会话作用域(存在于Spring MVC中) 范围是一个会话
- application: 全局作用域(存在于Spring MVC中) 范围是全局
- websocket: HTTP WebSocket 作用域(存在于Spring WebSocket中)
Spring中可以使用 @Scope注解 来更改Bean的作用域类型( Spring中的Bean默认作用域类型是单例 )
- 使用单词
- @Scope(“prototype”) : 多例
- @Scope(“singleton”) : 单例
- 使用枚举类
- @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) : 多例
- @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) : 单例
单例作用域(singleton)VS全局作用域(application)
- singleton是Spring Core的作用域, application是 Spring Web中的作用域
- singleton 作用于loC的容器, 而 application 作用于Servlet容器.
4.2.Bean的生命周期
- 实例化(给bean分配内存空间)
- 设置属性(对象注入)
- 初始化(在设置属性之后,避免使用属性时属性为空)
- 执行各种通知(执行各种Aware)
- 执行 BeanPostProcessor 初始化前置方法
- 执行 @PostConstruct 初始化方法,依赖注⼊操作之后被执行
- 执行自己指定的 init-method 方法
- 执行 BeanPostProcessor 初始化后置方法
- 使用Bean
- 销毁Bean
- @PreDestroy
- 重写DisposableBean接口方法
- destroy-method
先设置属性再进行初始化的原因
要先设置属性是因为如果先初始化, 初始化的时候就可能需要使用属性.这个时候属性为null, 就会空指针异常.