Spring

时间:2023-01-14 09:52:02

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的创建和使用

Spring

2.1.创建Spring项目

使⽤ Maven ⽅式来创建⼀个 Spring 项⽬分为3步

  1. 创建⼀个普通 Maven 项⽬
  2. 添加 Spring 框架支持(spring-context、spring-beans)
  3. 添加启动类

2.1.1.创建普通Maven项目

首先选择New --> Project 新建一个Maven项目.

Spring

选择Maven 后 点击Next 即可.不需要勾选任何添加.

Spring

选择项目要创建的目录,并更改项目名称

Spring

2.1.2.添加 Spring 框架支持

在pom.xml中添加依赖并刷新依赖.等待右下角进度条下载完成即可.
Spring

下面是我导入的依赖.大家也可以在*仓库中搜索依赖并添加.

<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 目录下.创建启动类 名称可以自定义.

Spring

在启动类中添加main方法即可.

Spring

这样,我们的Spring项目就创建好了.

2.2.存储Bean对象

所谓的 Bean 就是 Java 语⾔中的⼀个普通对象.

存储 Bean 分为以下 2 步

  1. 存储 Bean 之前, 先要有 Bean 才可以, 因此先要创建⼀个 Bean
  2. 将创建的 Bean 注册到 Spring 容器中

2.2.1.创建Bean

首先在java目录下创建一个文件夹用于存放我们的Bean.在文件夹下创建一个User对象.
Spring

给User类添加一个方法.这样我们就创建好了用于展示的Bean.
Spring

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配置文件

  1. 首先在 resources 目录下创建配置文件.可以自定义名称(这里使用spring.xml)
  2. 添加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>

Spring

2.2.3.将 Bean 注册到容器中

  • 在刚创建的配置文件中,添加 bean 标签,即可将Bean注册到容器中.
    • 存放方式类似于Map<String,Object>
      • id是存放入容器中的key值,用来获取Bean对象
      • class是存放对象的包名+类名

Spring

<bean id="user" class="com.beans.User"></bean>

2.3.获取并使用 Bean 对象

获取并使用 Bean 对象, 分为以下 3 步

  1. 得到 Spring 上下文对象, 因为对象都交给 Spring 管理了, 所以获取对象要从 Spring 中获取, 那么就要先得到 Spring 的上下文
    • 两种获取方式,注意参数要和配置文件名称相同
      • ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“spring.xml”);
      • BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(“spring.xml”));
  2. 通过 Spring 上下文,获取某⼀个指定的 Bean 对象
    • 获取有三种方式
      1. 通过id获取Bean对象
        • 该方法返回Object类型,所以获取后需要强转
      2. 通过类型获取Bean对象
        • 该方法的好处是直接确定了类型, 不需要强转
        • 缺点是如果Spring容器中注册了多个此类型对象时, Spring容器会不知道你获取的对象是谁, 从而会报错
      3. 通过id+类型获取Bean对象
        • 不需要强转, 也不会查询到多个, 因为注册到Spring容器中对象的id是唯一的
        • 缺点是书写比较麻烦
  3. 使用 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方法

不同点:

  1. ApplicationContext 属于 BeanFactory的子类
    • BeanFactory 只提供了基础访问Bean的方法
    • ApplicationContext 除了拥有 BeanFactory 的所有功能之外,还提供了更多的方法实现
      • 比如对国际化的支持、资源访问的支持、以及事件和传播等方面的支持
  2. 从性能方面来说二者是不同
    • BeanFactory 是按需加载 Bean,更轻量
    • ApplicationContext 是饿汉方式, 在创建时会将所有的Bean都加载起来,以备以后使用

3.Spring 更简单的存储和读取对象 – 注解

3.1.配置扫描路径

要使用注解的方式, 我们首先要 配置扫描路径 让Spring启动时扫描 配置(com.beans)包 中的注解.只有被配置的包下的所有类, 添加注解才能被正确的识别并保存到 Spring 中.

在我们之前的配置文件 “spring.xml” 中配置扫描路径(这里配置的 com.beans Spring启动时就会扫描这个包)

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.更简单的存储对象(认识注解)

  • 五大注解(类注解)
    1. @Controller
    2. @Service
    3. @Repoistory
    4. @Configuration
    5. @Component
  • 方法注解
    • @Bean

五大注解是在类之前添加的注解, 添加之后可以让这个类注册到Spring容器中.而方法注解则是要在方法前添加的注解, 可以将方法的返回值注册到Spring容器中.要注意的是方法注解一定要配合五大注解来使用.

3.2.1.五大注解的使用

五大注解的功能是相同的, 都是将类注册到Spring容器中. 既然功能是相同的, 那为什么还要有五大注解? 原因就是可以使程序员通过注解来了解类的作用.

  1. @Controller :控制器
  2. @Service :服务
  3. @Repoistory :仓库
  4. @Configuration :配置
  5. @Component :组件

如图,保证这个类所在的包在注册的包范围内.并且在类前添加了五大注解.这个类就被注册到Spring中了
Spring

添加到Spring容器中之后, 我们就可以通过之前的方法来获取和使用Bean对象了.
Spring

五大注解的命名规则

五大注解的命名规则是根据 JDK Introspector 中的 decapitalize 方法 来执行的.(图中代码为源码)
Spring

根据源码,我们可以知道命名规则:

如果名称长度大于1, 并且名称中第一个字符和第二个字符都是大写, 就返回原类名.
否则就将类名中的第一个字符从大写转变为小写返回.

知道了五大注解的命名规则. 我们在获取通过五大注解注册的Bean对象时就不会发生不知道名称的问题了.

五大注解之间的关系

观察 @Controller / @Service / @Repository / @Configuration 注解的源码就会发现. 这四个注解都是 @Component 注解的子类
Spring

3.2.2.方法注解 @Bean

类注解是添加到某个类上的, 而方法注解是放到某个方法上的. 但是要注意的是 方法注解是要配合类注解来使用的. 如果只写了方法注解,没有在类上添加注解. Spring在启动的时候是不会扫描到的.(可以理解为方法注解只会缩小扫描范围,不会扩大扫描范围)
Spring

同样: 添加好方法注解和类注解之后, 方法的返回值就被添加到Spring容器中. 我们就可以之前的方法来获取对象.
Spring

方法注解的使用方式

  1. 直接在方法上添加@Bean注解
  2. 使用@Bean注解, 并添加name属性用来重命名 -> @Bean(name = “name”)
    • 设置名称可以设置单个,也可以通过 {} 设置多个 -> @Bean(name = {“name1”, “name2”})

@Bean 的命名规则

方法注解的命名规则和五大注解的命名规则大有不同.

  • 当没有设置name属性时, 那么Bean默认的名称就是方法名
  • 当设置了name属性之后,就只能通过重命名的name属性对应的值来获取
  • 重命名之后,无法再使用方法名获取bean对象

3.3.更简单的获取对象(对象注入)

3.3.1.对象注入有两种方式

  1. @Autowired 注解
  2. @Resource 注解
  • 两种注解实现的效果是相同的
  • 直接使用注解的时候,会根据类型来寻找对象.
    • 如果可以找到的对象只有一个, 就会直接注入成功.
    • 如果找到的对象有多个,就会继续根据名称来寻找.
    • 如果有名称相同的就注入,没有就会报错.

Spring

  • 在不能更改对象名称的时候
    • 也就是不能更改名称,又要查询另一个名称时
  • 可以使用@Resource注解的name属性设置对象名称
  • @Autowired没有此属性.所以需要@Qualifier注解配合
  • @Qualifier只有value一个属性,所以可以省略

这样在根据类型查询到多个对象时,就会属性设置的新名称来查询

Spring

@Resource vS @Autowired

既然两种注解实现的效果是相同的,那这两个注解有什么不同呢?

  1. 出身不同
    • @Autowired是Spring框架提供的
    • @Resource 来自于JDK (Java亲儿子)
  2. 用法不同
    • @Autowired 支持属性注入、构造方法注入和Setter注入
    • @Resource 不支持构造方法注入
  3. 支持的参数不同
    • @Autowired 只支持required 这一个参数
    • @Resource 支持更多的参数设置,比如 name、type 设置

3.3.2.根据注解完成对象装配(对象注入)的实现方法有以下 3 种

  1. 属性注入
    • 直接在属性上添加 @Autowired注解 或 @Resource注解即可
  2. 构造方法注入
    • 在构造方法上添加 @Autowired注解
    • @Resource注解不支持构造方法注入
  3. Setter 注入
    • 在Setter方法上添加 @Autowired注解 或 @Resource注解即可

Spring

4.Bean的作用域和生命周期

4.1.Bean的作用域

Bean的作用域类型有六种,注意后 4 种状态是 Spring MVC 中的值, 普通的 Spring 项目中只有前两种

  1. singleton: 单例模式(Bean默认作用域类型)
  2. prototype: 原型模式(多例模式)
  3. request: 请求作用域(存在于Spring MVC中) 范围是一次请求
  4. session: 会话作用域(存在于Spring MVC中) 范围是一个会话
  5. application: 全局作用域(存在于Spring MVC中) 范围是全局
  6. websocket: HTTP WebSocket 作用域(存在于Spring WebSocket中)

Spring中可以使用 @Scope注解 来更改Bean的作用域类型( Spring中的Bean默认作用域类型是单例 )

  1. 使用单词
    • @Scope(“prototype”) : 多例
    • @Scope(“singleton”) : 单例
  2. 使用枚举类
    • @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) : 多例
    • @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) : 单例

Spring

单例作用域(singleton)VS全局作用域(application)

  • singleton是Spring Core的作用域, application是 Spring Web中的作用域
  • singleton 作用于loC的容器, 而 application 作用于Servlet容器.

4.2.Bean的生命周期

  1. 实例化(给bean分配内存空间)
  2. 设置属性(对象注入)
  3. 初始化(在设置属性之后,避免使用属性时属性为空)
    1. 执行各种通知(执行各种Aware)
    2. 执行 BeanPostProcessor 初始化前置方法
    3. 执行 @PostConstruct 初始化方法,依赖注⼊操作之后被执行
    4. 执行自己指定的 init-method 方法
    5. 执行 BeanPostProcessor 初始化后置方法
  4. 使用Bean
  5. 销毁Bean
    1. @PreDestroy
    2. 重写DisposableBean接口方法
    3. destroy-method

先设置属性再进行初始化的原因

要先设置属性是因为如果先初始化, 初始化的时候就可能需要使用属性.这个时候属性为null, 就会空指针异常.