第四章SpringFramework之Ioc

文章目录

IoC思想

认识什么叫依赖/耦合

  • 依赖指的是某某某离不开某某某
  • 在软件系统中,层与层之间是存在依赖的。我们也称之为耦合。
    • 我们系统架构或者是设计的一个原则是: 高内聚低耦合。
    • 层内部的组成应该是高度聚合的,而层与层之间的关系应该是低耦合的,最理想的情况0耦合(就是没有耦合)
public class FruitServiceImpl implements FruitService {

    private FruitDAO fruitDAO=new FruitDAOImpl();//调用FruitDAO
    @Override
    public List<Fruit> getFruitList(String keyword, Integer pageNo) {
        return fruitDAO.getFruitList(keyword,pageNo);
    }
}
  • 比如这串代码中,如果这个 FruitServiceImpl的实例对象能够正常被使用,那么必须也要有一个 FruitDAOImpl对象,也就是说 FruitServiceImpl的实例对象是依赖于 FruitDAOImpl对象的

控制反转和依赖注入的理解

IoC:Inversion of Control,控制反转。

①获取资源的传统方式
自己做饭:买菜、洗菜、择菜、改刀、炒菜,全过程参与,费时费力,必须清楚了解资源创建整个过程中的全部细节且熟练掌握。
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。

②反转控制方式获取资源

点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节。

反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。

DI:Dependency Injection,翻译过来是依赖注入。

DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。

为什么需要这样

拿生活中例子

在这里插入图片描述

  • IoC(控制反转)思想依赖DI(依赖注入)形式,两者只是从不同的角度去描述同一个事情,随着软件规模的发展,我们的对象经济也不可避免的从小农经济发展到商品经济
  • IoC Container(Ioc容器):它是一个容器,放的是对象——就是一个对象市场,里面放置的就是待出售的对象
  • 作为对象的买方,我们只在代码的中声明我们的依赖(需要购买)哪些类型的对象,最终的对象就是通过IoC这套流程注入到我们的代码里(类似对象送货上门)

齿轮的例子来突出Ioc的重要

在这里插入图片描述

  • 描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。
  • 齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。所以提出了IoC思想

在这里插入图片描述

  • 由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方**”IOC容器**,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。

为什么要叫IoC(控制反转)

  • 软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
  • 软件系统在引入IOC容器之后,这种情形就完全改变了,如图2所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
  • 通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

IOC也叫依赖注入(DI)

  • 既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。
  • 所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

IOC容器在Spring中的实现

什么是容器?

  • 容器是用来容纳某种物品的(基本)装置。
  • 我们想想,之前课程我们接触的容器有哪些?
    • List/Map -> 数据存储容器
    • Tomcat -> Servlet容器

容器会管理内部对象的整个生命周期。对象在容器中才能够正常的工作,得到来自容器的全方位的支持。

  • 创建对象
  • 初始化
  • 工作
  • 清理

容器本身也是对象

  • 特点1:往往是非常大的对象
  • 特点2:通常的单例的

Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean

  • IOC容器负责对象的创建和初始化等一系列工作,被创建或者被管理的对象在Ioc容器中统称为Bean

Spring 就是一个包含了众多工具方法的 IoC 容器。

  • 既然是容器那么它将对象存储到容器(Spring)中;
  • 从容器中将对象取出来

image-20230113205743389

Spring 是一个 IoC 容器,说的是对象的创建和销毁的权利都交给 Spring 来管理了,它本身又具备了存储对象和获取对象的能力。

在创建bean 之前,首先需要创建 IOC 容器

Spring 提供了 IOC 容器的两种实现方式

BeanFactory

  • 这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

ApplicationContext

  • BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用ApplicationContext 而不是底层的 BeanFactory。

image-20230112232721461

类型名简介
ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器 对象
FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容 器对象
ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、 关闭和刷新上下文的能力。
WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对 象,并将对象引入存入 ServletContext 域中
  • 我们一般使用的是ClassPathXmlApplicationContext ,因为我们的项目并不只是在一台机器运行,所以会对应不同的文件系统,所以我们应该用ClassPathXmlApplicationContext

ApplicationContext和BeanFactory的区别

  • 包目录不同,BeanFactory是AppliacationContext的父类接口
  • 关于延迟加载,BeanFactory采用的是延迟加载形式注入Bean,即只有再使用到某个bean时(getBean),才对概Bean进行加载实例化,而ApplicationContext恰恰相反,它是在容器启动时,一次性创建了所有的bean,在容器启动的时候就可以发现Spring中存在的配置错误
  • BeanFactory是Spring框架的基础设置,提供了基础的访问容器的能力,而ApplicationContext实现了BeanFactory,支持Spring的各种插件,支持AOP,Web等,还以一种面对框架的方式各种以集对上下文进行分层和实现继承
  • BeanFactory是面对框架,而AppliacationContext面对开发者

基于XML管理Bean

入门案例

导入相关的依赖

  <dependencies>
        <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

image-20230113183712979

  • 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包

创建对应的Spring配置

image-20230113183517990

<?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的配置文件中配置bean

    <bean id="myFirstBean" class="com.lsc.spring.Bean.User"></bean>

测试

/**
 * @BelongsProject: SSM
 * @BelongsPackage: com.lsc.spring.ioc
 * @Author: LiuSongCheng
 * @Date: 2023/1/13 18:30
 * @Description: TODO
 */
public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");//获得Ioc容器
        User user=(User) context.getBean("myFirstBean");
        user.sayHello();
    }
}

思路

image-20230113190311604

  • Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要无参构造器时,没有无参构造器,则会抛出异常:

获取Bean的方式

方式一:根据id获取

由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。上个实验中我们使用的就是这种方式

方式二:根据类型获取

public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        User user=(User) context.getBean(User.class);
        user.sayHello();
    }
}
//输出结果
//hello I is the first Bean

方式三:根据id和类型

public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        User user=(User) context.getBean("myFirstBean",User.class);
        user.sayHello();
    }
}

注意点

当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个
当IOC容器中一共配置了两个:

<bean id="myFirstBean" class="com.lsc.spring.Bean.User"></bean>
<bean id="mySecondBean" class="com.lsc.spring.Bean.User"></bean>

根据类型获取时会抛出异常:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean
of type 'com.lsc.spring.bean.User' available: expected single matching bean but
found 2: helloworldOne,helloworldTwo

如果组件类实现了接口,根据接口类型可以获取 bean 吗?

Person接口

public interface Person {
}

实现Person接口的User

public class User  implements Person{
    private Integer id;

    public User() {
    }

    public User(Integer id) {
        this.id = id;
    }

    public void sayHello(){
        System.out.println("hello I is the first Bean");
    }
}
  <bean id="myFirstBean" class="com.lsc.spring.Bean.User"></bean>
public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        User user=(User) context.getBean(Person.class);
        user.sayHello();
    }
}

  • 可以,前提是bean唯一

  • 如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?

    • 不行,因为bean不唯一
    • 如果一个类有多个bean配置,那么也不能正常获取

结论

  • 根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。

Spring的依赖注入

Spring什么时候发生依赖注入

  • 我们的Spring作为Ioc容器,类似我们的对象市场
  • 只有需要我们的Spring提供对象,Spring才会为其注入对象,类似去市场买东西也就是买方
  • 把对象交给我们的Spring Ioc容器管理,Spring才能在控制对象实例化的过程,才能有能力在实例化的过程期间为其注入

得出结论,只有对于SpringIoc容器,即是买方又是卖方才会发生注入依赖
IoC(控制反转只是一种理念),其中很多时候通过注入依赖(DI)开具体实现,依赖注入是发生在Spring管理的不同bean之间的,Spring 是IoC市场占主导地位的一款产品,所以我们有必要学习Spring,IoC只是Spring的核心能力

依赖注入之setter注入

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String gender;
    //还有所有属性对应的getter setter toString方法
     public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}

配置bean时为属性赋值

 <bean id="studentOne" class="com.lsc.spring.Bean.Student">
  
	  <!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)-->
	  <!-- value属性:指定属性值 -->
        <property name="id" value="1"></property>
        <property name="age" value="22"></property>
        <property name="name" value="刘颂成"></property>
        <property name="gender" value=""></property>
    </bean>
  • <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
  • 这个name——>getName() setName(),而不是对应着我们的成员变量

测试

public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        Student studentOne = context.getBean("studentOne", Student.class);
        System.out.println(studentOne);
    }
}
//输出结果
Student{id=1, name='刘颂成', age=22, gender='男'}

依赖注入之构造器注入

在Student类中添加对应的构造方法

 public Student(Integer id, String name, Integer age, String gender) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

配置bean

  <bean id="studentTwo" class="com.lsc.spring.Bean.Student">
        <constructor-arg value="2"></constructor-arg>
        <constructor-arg value="刘颂成2"></constructor-arg>
        <constructor-arg value="22"></constructor-arg>
        <constructor-arg value=""></constructor-arg>
    </bean>

测试

public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        Student studentTwo = context.getBean("studentTwo", Student.class);
        System.out.println(studentTwo);
    }
}
//输出结果
Student{id=2, name='刘颂成2', age=22, gender='男'}

注意:
constructor-arg标签还有两个属性可以进一步描述构造器参数:

  • index属性:指定参数所在位置的索引(从0开始)
  • name属性:指定参数名
 public Student(Integer id, String name,  String gender,Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    public Student(Integer id, String name,  String gender, Double score) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.score = score;
	}
 <bean id="studentTwo" class="com.lsc.spring.Bean.Student">
        <constructor-arg value="2"></constructor-arg>
        <constructor-arg value="刘颂成2"></constructor-arg>
        <constructor-arg value=""></constructor-arg> 
        <constructor-arg value="22"></constructor-arg>
    </bean>
  • 默认会匹配到第二个构造方法
    • Student{id=2, name=‘刘颂成2’, age=null, gender=‘男’, score=22.0}
  • 但是我们想匹配第二个构造方法,如何实现
<bean id="studentTwo" class="com.lsc.spring.Bean.Student">
        <constructor-arg value="2"></constructor-arg>
        <constructor-arg value="刘颂成2"></constructor-arg>
        <constructor-arg value=""></constructor-arg>
        <constructor-arg value="22" name="age"></constructor-arg>
</bean>
  • 我们推荐使用的是setter注入

一些特殊值处理

前提

<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="name" value="张三"/>  

null值

<property name="name">
	<null />
</property>
<property name="name" value="null"></property>  
  • 第二种赋值,赋的值是null字符串

xml实体

!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a &lt; b"/>

CDATA节

<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>  

为类类型属性赋值

创建Clazz班级类

public class Clazz {
    private Integer id;
    private String clazzName;
    //还有对应的getter setter toString方法
}

给Student添加对应Clazz类对象

 public class Student {
 	private Clazz clazz;
    public void setScore(Double score) {
        this.score = score;
    }

    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }
 }

使用setter方法进行注入

第一种使用外部已声明bean

配置外部Clazz对象

 <bean id="clazzFirst" class="com.lsc.spring.Bean.Clazz">
        <property name="id" value="1"></property>
        <property name="clazzName" value="一班"></property>
 </bean>
<bean id="studentThree" class="com.lsc.spring.Bean.Student">
        <property name="id" value="3"></property>
        <property name="name" value="刘颂成3"></property>
        <property name="age" value="33"></property>
        <property name="gender" value=""></property>
        <property name="score" value="333"></property>
        <property name="clazz" ref="clazzFirst"></property>
</bean>
  • ref表示引用Spring管理的对象

测试

public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        Student studentThree = context.getBean("studentThree", Student.class);
        System.out.println(studentThree);
    }
}
//输出结果
Student{id=3, name='刘颂成3', age=33, gender='男', score=333.0, clazz=Clazz{id=1, clazzName='一班'}}

第二种使用内部bean

  <bean id="studentFour" class="com.lsc.spring.Bean.Student">
        <property name="id" value="4"></property>
        <property name="name" value="刘颂成4"></property>
        <property name="age" value="44"></property>
        <property name="gender" value=""></property>
        <property name="score" value="444"></property>
        <property name="clazz" >
            <!-- 在一个bean中再声明一个bean就是内部bean -->
            <!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
            <bean id="clazzInner" class="com.lsc.spring.Bean.Clazz">
                <property name="id" value="2222"></property>
                <property name="clazzName" value="二班"></property>
            </bean>
        </property>
    </bean>
public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        Student studentFour = context.getBean("studentFour", Student.class);
        System.out.println(studentFour);
    }
}
//输出结果
Student{id=4, name='刘颂成4', age=44, gender='男', score=444.0, clazz=Clazz{id=2222, clazzName='二班'}}

不能通过容器获得内部bean

public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        Clazz clazz = (Clazz) context.getBean("clazzInner");
        System.out.println(clazz);
    }
}

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'clazz' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:863)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1339)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:309)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1161)
	at com.lsc.spring.ioc.Test.main(Test.java:20)

  • 报错,没有对应的bean

第三种级联属性赋值

<bean id="studentFour" class="com.atguigu.spring.bean.Student">
	<property name="id" value="1004"></property>
	<property name="name" value="赵六"></property>
	<property name="age" value="26"></property>
	<property name="sex" value=""></property>
	<!-- 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性 -->
	<property name="clazz" ref="clazzOne"></property>
	<property name="clazz.clazzId" value="3333"></property>
	<property name="clazz.clazzName" value="最强王者班"></property>
</bean>
<bean id="clazzOne" class="com.lsc.spring.Bean.Clazz"> </bean>

为数组类型属性赋值

在Student中添加代码

public class Student {
private String[] hobbies;
    public String[] getHobbies() {
        return hobbies;
    } 
    public void setHobbies(String[] hobbies) {
        this.hobbies = hobbies;
    }
} 

配置bean

<bean id="studentFive" class="com.lsc.spring.Bean.Student">
        <property name="id" value="5"></property>
        <property name="name" value="刘颂成5"></property>
        <property name="age" value="55"></property>
        <property name="gender" value=""></property>
        <property name="score" value="555"></property>
        <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
        <property name="clazz" ref="clazzTwo"></property>
        <property name="hobbies">
            <array>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </array>
        </property>
    </bean>
public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        Student studentFive=context.getBean("studentFive",Student.class);
        System.out.println(studentFive);
    }
}
//输出结果
Student{id=5, name='刘颂成5', age=55, gender='男', score=555.0, clazz=Clazz{id=null, clazzName='null'}, hobbies=[抽烟, 喝酒, 烫头]}

为集合类型属性赋值

为List集合赋值

在Clazz类中加代码

private List<Student> students;
	public List<Student> getStudents() {
        return students;
    }
    public void setStudents(List<Student> students) {
        this.students = students;
    }

配置bean

<bean id="clazzThree" class="com.lsc.spring.Bean.Clazz">
        <property name="id" value="3"></property>
        <property name="clazzName" value="三班"></property>
        <property name="students">
            <list>
                <ref bean="studentOne"></ref>
                <ref bean="studentTwo"></ref>
            </list>
        </property>
    </bean>

若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可

测试

public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        Clazz clazzThree=context.getBean("students",Clazz.class);
        System.out.println(clazzThree);
    }
}
//输出结果
Clazz{id=3, clazzName='三班',
students=[Student{id=1, name='刘颂成', age=11, gender='男', score=null, clazz=null, hobbies=null}, 
		 Student{id=2, name='刘颂成2', age=22, gender='男', score=null, clazz=null, hobbies=null}]}

为Map集合赋值

新增的Teacher类

public class Teacher {
    private Integer teacherId;
    private String teacherName;
    //还有对应的getter setter 和toString方法和构造方法
}

在Studeng类添加对应Map属性

 private Map<String, Teacher> teacherMap;
    public Map<String, Teacher> getTeacherMap() {
        return teacherMap;
    } 
    public void setTeacherMap(Map<String, Teacher> teacherMap) {
        this.teacherMap = teacherMap;
    }

配置bean

	 <bean id="studentSix" class="com.lsc.spring.Bean.Student">
        <property name="id" value="6"></property>
        <property name="name" value="刘颂成6"></property>
        <property name="age" value="66"></property>
        <property name="gender" value=""></property>
        <property name="score" value="666"></property>
        <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
        <property name="clazz" ref="clazzTwo"></property>
        <property name="hobbies">
            <array>
                <value>抽烟6</value>
                <value>喝酒6</value>
                <value>烫头6</value>
            </array>
        </property>
        <property name="teacherMap">
            <map>
                <entry key="teacher1" value-ref="teacherFirst"></entry>
                <entry key="teahcer2" value-ref="teacherTwo"></entry>
            </map>
        </property>
    </bean>
	<bean id="teacherFirst" class="com.lsc.spring.Bean.Teacher">
        <property name="teacherId" value="1"></property>
        <property name="teacherName" value="一号老师"></property>
    </bean>
    <bean id="teacherTwo" class="com.lsc.spring.Bean.Teacher">
        <property name="teacherId" value="2"></property>
        <property name="teacherName" value="二号老师"></property>
    </bean>

测试

public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        Student studentSix = context.getBean("studentSix", Student.class);
        System.out.println(studentSix);
    }
}
//输出结果
Student{id=6, name='刘颂成6', age=66, gender='男', score=666.0, 
clazz=Clazz{id=null, clazzName='null', students=null}, 
hobbies=[抽烟6, 喝酒6, 烫头6], 
teacherMap={teacher1=Teacher{teacherId=1, teacherName='一号老师'}, teahcer2=Teacher{teacherId=2, teacherName='二号老师'}}}

引用集合类型的bean来进行赋值

<!--list集合类型的bean-->
<util:list id="students">
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</util:list>
<!--map集合类型的bean-->
<util:map id="teacherMap">
	<entry>
		<key>
			<value>10010</value>
		</key>
		<ref bean="teacherOne"></ref>
	</entry>
	<entry>
		<key>
			<value>10086</value>
		</key>
		<ref bean="teacherTwo"></ref>
	</entry>
</util:map>

使用util:list、util:map标签必须引入相应的命名空间,可以通过idea的提示功能选择

p命名空间

引入p命名空间后,可以通过以下方式为bean的各个属性赋值

bean的配置

 <bean id="studentSeven" class="com.lsc.spring.Bean.Student"
    p:name="刘颂成7" p:age="77" p:gender="" p:teacherMap-ref="teacherMap">
 </bean>

测试

public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc.xml");
        Student studentSeven= context.getBean("studentSeven", Student.class);
        System.out.println(studentSeven);
    }
}
//输出结果
Student{id=null, name='刘颂成7', age=77, gender='男', score=null, clazz=null, hobbies=null, 
	teacherMap={10010=Teacher{teacherId=1, teacherName='一号老师'}, 
				10086=Teacher{teacherId=2, teacherName='二号老师'}}}

引入外部文件

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.username=root
jdbc.password=123456

bean的配置

<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="druidDataSource"  class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

测试

public class DataSourceTest {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-datasource.xml");
        DruidDataSource dataSource = context.getBean(DruidDataSource.class);
        System.out.println(dataSource);
    }
}
//输出结果
{
	CreateTime:"2023-01-14 22:37:16",
	ActiveCount:0,
	PoolingCount:0,
	CreateCount:0,
	DestroyCount:0,
	CloseCount:0,
	ConnectCount:0,
	Connections:[
	]
}

工厂方式产生Bean

如何在SpringIoC容器里面卖对象呢?

  • 自己生产好对象,再卖到IoC容器中,分为静态工厂方法和实例化工厂方法
  • 将生产对象的配方(告诉Spring使用什么方式去实例化对象)告诉IoC容器,最终售卖的对象直接有IoC容器生产(这种使用的比较多),其实实质就是通过我们的构造方法创建对象

直接生产好对象进行售卖

  • 以静态工厂的方法售卖对象(自己new一个对象再放到IoC容器去卖),Java所有的代码都挂靠在某个方法里,方法又得挂靠在某个类上,如果不想用对象直接调用方法,那么就只能使用静态方法,这种专门实例化对象的方法一般称为工厂方法(factory-method)
  • 先给我们的实例化工厂的类创建一个bean对象这个对象叫FactoryBean,使用实例化工厂创建对象的时候,用factory-bean来指明我们的实例化工厂的bean

factory-method

对应的静态工厂类

public class staticFactoryBean {
    public static User getUserByStaticFactory(){
        return new User(2);
    }
}

bean的配置

 <bean id="staticFactoryBean" class="com.lsc.spring.factort.staticFactoryBean" factory-method="getUserByStaticFactory"></bean>

测试

public class FactoryTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-factory.xml");
        User bean = context.getBean(User.class);
        System.out.println(bean);
    }
}
//输出结果
User{id=2}
  • class配置对应的静态工厂的类
  • factory-method配置对应的创建对象的静态方法

FactoryBean

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

创建对应的实例化工厂类

public class factoryBean implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        System.out.println("使用实例化工厂的getObject来获取对象");
        return  new User(3);
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

配置对应的bean

 <bean class="com.lsc.spring.factort.factoryBean"></bean>

测试

public class FactoryTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-factory.xml");
        User bean = context.getBean(User.class);
        System.out.println(bean);
    }
}
//输出结果
使用实例化工厂的getObject来获取对象
User{id=3}

基于xml的自动装配

自动装配

Ioc容器根据bean所依赖的的资源在容器中自动查找并注入到bean的过程叫自动装配,所以我们知道我们需要只能注入的是引用类型(类类型或接口类型属性),不能简单类型的注入,而且被注入也是必须被Spring管理的对象

  • 被自动装配类需要提供对应需要注入属性setter方法,需要我们在bean标签配置上autowired属性

  • 属性可选值:

    • byType 通过类型,通过属性的类型去查找

    • byName 通过name,也就是通过bean的id,通过属性的名称去查找id

    • default, 默认 跟no一样

    • no 不自动装配

场景模拟

Controller层

public class Controller {
    private Service service;
    public void controller(){
        service.service();
    }
    public void setService(Service service) {
        this.service = service;
    }
}

service层

对应接口

public interface Service {
    void service();
}

对应实现类

public class ServiceImpl implements Service {
    private DAO dao;
    @Override
    public void service(){
        dao.dao();
    }
    public void setDao(DAO dao) {
        this.dao = dao;
    }
}

DAO层

对应接口

public interface DAO {
    void dao();
}

对应实现类

public class DAOImpl implements DAO {
    @Override
   public void dao(){
       System.out.println("正在服务");
   }
}

采用自动装配的bean配置

通过类型自动装配

<bean id="controller" class="com.lsc.spring.controller.Controller" autowire="byType"></bean>
<bean id="service" class="com.lsc.spring.service.impl.ServiceImpl" autowire="byType"></bean>
<bean id="dao" class="com.lsc.spring.dao.impl.DAOImpl" autowire="byType"></bean>

byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值

  • 若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null
  • 若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException

提供name自动装配

<bean id="controller" class="com.lsc.spring.controller.Controller" autowire="byName"></bean>
<bean id="service" class="com.lsc.spring.service.impl.ServiceImpl" autowire="byName"></bean>
<bean id="dao" class="com.lsc.spring.dao.impl.DAOImpl" autowire="byName"></bean>

自动装配方式:byName

  • byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值

测试

public class autowireTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-autowire-xml.xml");
        Controller bean = context.getBean(Controller.class);
        bean.controller();
    }
}
//输出结果
正在服务

基于注解管理Bean

标记与扫描

注解
和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。

image-20230115235934424

举例:元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上拉花,黄色的地方贴上气球。班长做了所有标记,同学们来完成具体工作。墙上的标记相当于我们在代码中使用的注解,后面同学们做的工作,相当于框架的具体操作。

扫描

Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

前置工作:配置扫描路径

注意:想要将对象成功的存储到 Spring 中,我们需要配置一下存储对象的扫描包路径,只有被配置的包下的所有类,添加了注解才能被正确的识别并保存到 Spring 中。

  • 单单用注解的方式来将Bean注册到Spring,交给Spring Ioc进行管理是不够的
  • Bean对应的类也必须在我们配置的扫描路径之下

基本配置方式

<context:component-scan base-package="com.lsc.spring"></context:component-scan>
  • base-package 配置下包都会被扫描

指定要排除的组件

<context:component-scan base-package="com.lsc.spring">
<!-- context:exclude-filter标签:指定排除规则 -->

<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--com.lsc.spring下所有被@Controller修饰的类不会被扫描-->
<context:exclude-filter type="assignable" expression="com.lsc.spring.controller.UserController"/>
<!--com.lsc.spring.controller.UserController 这个类不会被扫描-->
</context:component-scan>
  • context:exclude-filter标签:指定排除规则
    • type:设置排除或包含的依据
      • type=“annotation”,根据注解排除,expression中设置要排除的注解的全类名
      • type=“assignable”,根据类型排除,expression中设置要排除的类型的全类名

指定要包含的组件

<context:component-scan base-package="com.lsc.spring" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->

<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="assignable" expression="com.lsc.spring.controller.UserController"/>-->
</context:component-scan>
  • context:include-filter标签:指定在原有扫描规则的基础上追加的规则
    • use-default-filters属性:取值false表示关闭默认扫描规则
      • 此时必须设置use-default-filters=“false”,因为默认规则即扫描指定包下所有类
    • type:设置包含的依据
      • type=“annotation”,根据注解包含,expression中设置要扫描的注解的全类名
      • type=“assignable”,根据类型包含,expression中设置要扫描的类型的全类名

注解存储 Bean 对象 的方式

想要将对象存储在 Spring 中,有两种注解类型可以实现:

  • 类注解:@Controller、@Service、@Repository、@Component、@Configuration。
  • 方法注解:@Bean。

类注解

利用注解进行Spring注册:前提是需要指定扫描的包下的类

  • @Component 组件(如果其他注解的意义都用不上,就可以用@Component)
  • @Controller 控制器(当一个类表示控制器的作用,也就是对应我们的Controller)
  • @Service 服务(当一个类提供业务服务逻辑功能的时候,也就是我们对应的Service层)
  • @Repository 仓库(当一个类提供从MySQL(也可以是其他存储软件)中进行数据的读写时,也就是对应我们DAO层)
  • @Configuration 配置(设置当前这个类为配置类),也就是表示我们的bean配置的xml文件,然后用CompenentScan()来写我们要扫描的类
  • 这五个注解在进行类注册的这个作用的时候,对Spring执行代码没有上面区别(但是@Configuration是有点区别的),主要是给程序员看的,便于分辨类的功能

image-20230116021603242

通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。对于Spring使用IOC容器管理这些组件来说没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。

实例

Controller层

@Controller
public class UserController {
    private UserService service;
    public void controller(){
        service.service();
    }

    public void setService(UserService service) {
        this.service = service;
    }
}

Service层

对应的接口

public interface UserService {
    void service();
}

对应的实现类

@Service
public class UserServiceImpl implements UserService {
    private UserDAO dao;
    @Override
    public void service(){
        dao.dao();
    }

    public void setDao(UserDAO dao) {
        this.dao = dao;
    }
}

DAO层

对应的接口

public interface UserDAO {
    void dao();
}

对应的实现类

@Repository
public class UserDAOImpl implements UserDAO {
    @Override
   public void dao(){
       System.out.println("正在服务");
   }

}

测试

public class annotationTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
        UserController userController = context.getBean(UserController.class);
        UserService userService = context.getBean(UserService.class);
        UserDAO userDAO = context.getBean(UserDAO.class);
        System.out.println(userController);
        System.out.println(userService);
        System.out.println(userDAO);
    }
}
//输出结果
com.lsc.spring.controller.UserController@4e41089d
com.lsc.spring.service.impl.UserServiceImpl@32a068d1
com.lsc.spring.dao.impl.UserDAOImpl@33cb5951

组件所对应的bean的id

在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。
默认情况

  • 类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。

自定义bean的id

  • 可通过标识组件的注解的value属性设置自定义的bean的id

  • @Service(“userService”)//默认为userServiceImpl public class UserServiceImpl implements UserService {}

给UserServiceImpl的bean对象起别名

@Service("myService")
public class UserServiceImpl implements UserService {
    private UserDAO dao;
    @Override
    public void service(){
        dao.dao();
    }

    public void setDao(UserDAO dao) {
        this.dao = dao;
    }
}

测试

public class annotationTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
        UserController userController = context.getBean("userController",UserController.class);
        UserService userServiceImpl = context.getBean("myService", UserService.class);
        UserDAO userDAOImpl = context.getBean("userDAOImpl", UserDAO.class);
        System.out.println(userController);
        System.out.println(userServiceImpl);
        System.out.println(userDAOImpl);
    }
}
//输出结果
com.lsc.spring.controller.UserController@2d127a61
com.lsc.spring.service.impl.UserServiceImpl@2bbaf4f0
com.lsc.spring.dao.impl.UserDAOImpl@11c20519

方法注解

运用方法注解也就是对应我们之前的直接卖生产好的对象

  • 方法必须出现在已经注册到Spring类中(用@Component系列修饰的类)原则上用哪个注释都行,但是更建议@Configuration注释修饰
  • @Bean注释修饰方法

User类

public class User {
    private Integer id;

    public User(Integer id) {
        this.id = id;
    }
    public User() {
      
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                '}';
    }
}

配置类

@Configuration
public class FactoryCreateBean {
    public FactoryCreateBean() {
         System.out.println("利用无参构造函数创建了一个FactoryCreateBean对象来创建User对象");
    }

    @Bean
    public User getUser(){
        System.out.println("利用FactoryCreateBean的getUser()方法来创建对象给Spring管理");
        return  new User(1);
    }
}

测试

public class factoryBean {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
        User bean = context.getBean("getUser",User.class);
        System.out.println(bean);
    }
}
//输出结果
利用无参构造函数创建了一个FactoryCreateBean对象来创建User对象
利用一个FactoryCreateBeangetUser()方法来创建对象给Spring管理
User{id=1}
  • 利用@Bean来修饰方法来创建对象给Spring管理
  • 对应的bean的类型是我们的方法的返回值类型,bean的默认id是方法名
	@Bean(name = {"u1"})
    public User getUser(){
        System.out.println("利用FactoryCreateBean的getUser()方法来创建对象给Spring管理");
        return  new User(1);
    }
  • 也可以通过这种方式进行自定义id

如何选择

  • 当注册是类,是我们自己写的,我们能添加注解去修饰,就使用Component系列去进行注册
  • 当这个类不是我们自己写的,不能修改别人的代码,我们就为一个类添加注解,然后使用@Bean的工厂方式进行注册
    • 或者可以通过xml的方式进行配置

基于注解的自动装配

  • 成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。以后我们在项目中的正式用法就是这样。
  • 构造方法注入,用@Autowried修饰构造方法,虽然不加也可以,但是建议带上(因为方便人识别)
  • setter方法注入,用@Autowried修饰setter方法
  • 如果是简单数据类型,那我们就是@Value来传入需要的数据
  • 在进行注入的时候,只有这个类注册到了Spring下,被Spring实例化出来才能进行注入,如果是我们自己用代码手动new出来的对象是无法进行注入的

场景模拟

Controller层

基于成员变量

@Controller
public class UserController {
    @Autowired
    private UserService service;
    public void controller(){
        service.service();
    }

    public void setService(UserService service) {
        this.service = service;
    }
}

基于构造方法

@Controller
public class UserController {
    private UserService service;
    @Autowired
    public UserController(UserService service) {
        this.service = service;
    }

    public void controller(){
        service.service();
    }

    public void setService(UserService service) {
        this.service = service;
    }
}

注意事项:如果类只有一个构造方法,那么 @Autowired 注解可以省略;如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法。

Service层

对应的接口

public interface UserService {
    void service();
}

对应的实现类

基于setter方法

@Service
public class UserServiceImpl implements UserService {
    private UserDAO dao;
    @Override
    public void service(){
        dao.dao();
    }
	@Autowired
    public void setDao(UserDAO dao) {
        this.dao = dao;
    }
}

DAO层

对应的接口

public interface UserDAO {
    void dao();
}

对应的实现类

@Repository
public class UserDAOImpl implements UserDAO {
    @Override
   public void dao(){
       System.out.println("正在服务");
   }

}

测试

public class autowireTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
        UserController userController = context.getBean(UserController.class);
        userController.controller();
    }
}
//输出结果
正在服务
  • 属性注入的优点是简洁,使用方便;缺点是只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)。
  • 构造方法注入是 Spring 推荐的注入方式,它的缺点是如果有多个注入会显得比较臃肿,但出现这种情况你应该考虑一下当前类是否符合程序的单一职责的设计模式了,它的优点是通用性,在使用之前一定能把保证注入的类不为空。
  • Setter 方式是 Spring 前期版本推荐的注入方式,但通用性不如构造方法,所有 Spring 现版本已经推荐使用构造方法注入的方式来进行类注入了。

对于@Bean修饰的工厂方法的自动装配

User类

@Component
public class User {
    @Value("3")
    private Integer id;

    public User(Integer id) {
        this.id = id;
    }

    public User() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                '}';
    }
}
  • 用@Value(),给简单类型的属性赋值

工厂方法进行自动装配

@Configuration
public class FactoryCreateBean {
    public FactoryCreateBean() {
        System.out.println("利用无参构造函数创建了一个FactoryCreateBean对象来创建User对象");
    }

    @Bean
    public User getUser(@Autowired User user){
        System.out.println("用FactoryCreateBean的getUser()方法来创建对象给Spring管理");
        return  user;
    }
}

测试

public class factoryBean {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
        User bean = context.getBean("getUser",User.class);
        System.out.println(bean);
    }
}
//输出结果
利用无参构造函数创建了一个FactoryCreateBean对象来创建User对象
用FactoryCreateBeangetUser()方法来创建对象给Spring管理
User{id=3}

@Autowired工作流程

image-20230116025707423

首先根据所需要的组件类型到IOC容器中查找

  • 能够找到唯一的bean:直接执行装配
  • 如果完全找不到匹配这个类型的bean:装配失败
  • 和所需类型匹配的bean不止一个
    • 没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为bean的id进行匹配
      • 能够找到:执行装配
      • 找不到:装配失败
    • 使用@Qualifier注解:根据@Qualifier注解中指定的名称作为bean的id进行匹配
      • 能够找到:执行装配
      • 找不到:装配失败
@Controller
public class UserController {
	@Autowired
	@Qualifier("userServiceImpl")
	private UserService userService;
	public void saveUser(){
		userService.saveUser();
	}
}

@Autowired中有属性required,默认值为true,因此在自动装配无法找到相应的bean时,会装配失败可以将属性required的值设置为true,则表示能装就装,装不上就不装,此时自动装配的属性为默认值

  • 但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。

@Resource:另一种注入关键字

@Controller
public class UserControllerResource {
    @Resource
    private UserService service;
    
    public void controller(){
       service.service();
    }
}

@Autowired 和 @Resource 的区别

  • 先说相同,两者都可以写在字段和setter方法,构造方法上,两者写在字段上,那么都不需要再写setter方法

  • 出身不同,@Autowired 来自于 Spring,而 @Resource 来自于 JDK 的注解;

  • 使用时设置的参数不同:相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如name 设置,根据名称获取 Bean

    • @Qualifier跟按名获取bean一样的功能
  • @Resource默认是按照byName进行装配,如果没有匹配的。则回退为按照一个原始类型进行匹配

  • 延迟加载:Bean

    • 延迟实例化的优点:(BeanFactory

      • 应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势;
    • 不延迟实例化的优点: (ApplicationContext

      • 所有的Bean在启动的时候都加载,系统运行的速度快;

      • 在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题

      • 建议web应用,在启动的时候就把所有的Bean都加载了。(把费时的操作放到系统启动中完成)

Bean的作用域

Bean的概念

  • Bean其实就是对象
  • Java Bean其实就是POJO对象(简单java对象”。POJO的内在含义是指那些没有从任何类继承、也没有实现任何接口,更没有被其它框架侵入的java对象。无参构造+getter+setter)
  • Spring Bean 就是被Spring管理的对象

限定程序中变量的可用范围叫做作用域,或者说在源代码中定义变量的某个区域就叫做作用域。

通过一个案例来看bean作用域的问题

bean的配置

 <bean class="com.lsc.spring.Bean.User"></bean>

测试

public class ScopeTest {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-scope.xml");
        User bean1 = context.getBean(User.class);
        User bean2=context.getBean(User.class);
        System.out.println(bean1==bean2);
    }

}
//输出 结果
true
  • 我们知道对于引用对象,我们的==比较的的内存地址,只有是指向同一个对象,才会输出true
    • 所以说明我们从User类型的对象是同一个对象

而Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式,比如 singleton 单例作用域,就表示 Bean 在整个 Spring Ioc中只有一份,它是全局共享的,那么当其他人修改了这个值之后,那么另一个人读取到的就是被修改的

Bean 的 6 种作用域

  • singleton:单例作用域
  • prototype:原型作用域(多例作用域)
  • request:请求作用域
  • session:回话作用域
  • application:全局作用域
  • websocket:HTTP WebSocket 作用域

singleton

  • 官方说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
  • 描述:该作用域下的Bean在IoC容器中只存在一个实例:获取Bean(即通过applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注入)都是同一个对象。
  • 场景:通常无状态的Bean使用该作用域。无状态表示Bean对象的属性状态不需要更新
  • 备注:Spring默认选择该作用域
  • 创建对象的时机 IOC容器初始化时

prototype

  • 官方说明:Scopes a single bean definition to any number of object instances.
  • 描述:每次对该作用域下的Bean的请求都会创建新的实例:获取Bean(即通过applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注入)都是新的对象实例。
  • 场景:通常有状态的Bean使用该作用域
  • 创建对象的时机 :获取bean时
 <bean class="com.lsc.spring.Bean.User" scope="prototype"></bean>
public class ScopeTest {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-scope.xml");
        User bean1 = context.getBean(User.class);
        User bean2=context.getBean(User.class);
        System.out.println(bean1==bean2);
    }

}
//输出结果
false

request

  • 官方说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is,each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:每次http请求会创建新的Bean实例,类似于prototype
  • 场景:一次http的请求和响应的共享Bean
  • 备注:限定SpringMVC中使用

session

  • 官方说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:在一个http session中,定义一个Bean实例
  • 场景:用户回话的共享Bean, 比如:记录一个用户的登陆信息
  • 备注:限定SpringMVC中使用

application

  • 官方说明:Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:在一个http servlet Context中,定义一个Bean实例
  • 场景:Web应用的上下文信息,比如:记录一个应用的共享信息
  • 备注:限定SpringMVC中使用

websocket

  • 官方说明:Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:在一个HTTP WebSocket的生命周期中,定义一个Bean实例
  • 场景:WebSocket的每次会话中,保存了一个Map结构的头信息,将用来包裹客户端消息头。第一次初始化后,直到WebSocket结束都是同一个Bean。
  • 备注:限定Spring WebSocket中使用

单例作用域(singleton)和全局作用域(application)区别

  • singleton 是 Spring Core 的作用域;application 是 Spring Web 中的作用域;
  • singleton 作用于 IoC 的容器,而 application 作用于 Servlet 容器。

bean的生命周期

入门案例

User类

public class User  implements Person{
    private Integer id;

    public User() {
        System.out.println("生命周期:1、实例化");
    }

    public User(Integer id) {
        this.id = id;
    }
    public void initMethod(){
        System.out.println("生命周期:3、初始化");
    }
    public void destroyMethod(){
        System.out.println("生命周期:5、销毁");
    }
    public void sayHello(){
        System.out.println("hello I is the first Bean");
    }
    public void setId(Integer id) {
        System.out.println("生命周期:2、依赖注入");
        this.id = id;
    }
}
  • 注意其中的initMethod()和destroyMethod(),可以通过配置bean指定为初始化和销毁的方法

bean的配置

<bean class="com.lsc.spring.Bean.User" scope="prototype" destroy-method="destroyMethod"  init-method="initMethod">
        <property name="id" value="1"></property>
</bean>

测试

public class LifeStyleTest {
    public static void main(String[] args) {
        ConfigurableApplicationContext context=new ClassPathXmlApplicationContext("spring-lifestyle.xml");
        User bean1 = context.getBean(User.class);
        System.out.println("生命周期:4、通过IOC容器获取bean并使用, bean的Id="+bean1.getId());
        System.out.println("Ioc容器关闭");
        context.close();
    }
}
//输出结果
生命周期:1、实例化
生命周期:2、依赖注入
生命周期:3、初始化
生命周期:4、通过IOC容器获取bean并使用, bean的Id=1
Ioc容器关闭
生命周期:5、销毁

实例化和初始化的区别

  • 实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可人工干预和修改;而初始化是给开发者提供的
  • 初始化要在实例化和依赖注入之后

bean的后置处理器

bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行

myBeanPostProcessor

public class myBeanPostProcessor  implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //此方法在bean的生命周期初始化之前执行
        System.out.println("MyBeanPostProcessor-->后置处理器postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //此方法在bean的生命周期初始化之后执行
        System.out.println("MyBeanPostProcessor-->后置处理器 postProcessAfterInitialization");
        return bean;
    }
}

配置后置处理器的bean

 <!--   bean的后置处理器要放入IOC容器才能生效  -->
    <bean id="myBeanProcessor" class="com.lsc.spring.process.myBeanPostProcessor"/>

添加后置处理器之后的流程

public class LifeStyleTest {
    public static void main(String[] args) {
        ConfigurableApplicationContext context=new ClassPathXmlApplicationContext("spring-lifestyle.xml");
        User bean1 = context.getBean(User.class);
        System.out.println("生命周期:4、通过IOC容器获取bean并使用, bean的Id="+bean1.getId());
        System.out.println("Ioc容器关闭");
        context.close();
    }
}
//输出结果
生命周期:1、实例化
生命周期:2、依赖注入
MyBeanPostProcessor-->后置处理器postProcessBeforeInitialization
生命周期:3、初始化
MyBeanPostProcessor-->后置处理器 postProcessAfterInitialization
生命周期:4、通过IOC容器获取bean并使用, bean的Id=1
Ioc容器关闭
生命周期:5、销毁

具体的生命周期过程

  • bean对象创建(调用无参构造器)
  • 给bean对象设置属性
  • bean对象初始化之前操作(由bean的后置处理器负责)
  • bean对象初始化(需在配置bean时指定初始化方法)
  • bean对象初始化之后操作(由bean的后置处理器负责)
  • bean对象就绪可以使用
  • bean对象销毁(需在配置bean时指定销毁方法)
  • IOC容器关闭

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0A9LdET4-1673889014512)(第四章SpringFramework之Ioc.assets/f4b7687d6f824cf298fed5db57c01507.png)]

类似于

  • 先买房(实例化,从无到有);
  • 装修(设置属性);
  • 买家电,如洗衣机、冰箱、电视、空调等([各种]初始化);
  • 入住(使用 Bean);
  • 卖出去(Bean 销毁)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

库里不会投三分

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值