如何理解IoC容器,Bean工厂,applicationContext.xml三者之间的关系

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何理解IoC容器,Bean工厂,applicationContext.xml三者之间的关系相关的知识,希望对你有一定的参考价值。

参考技术A :BeanFactory基本的工厂
解析,管理,实例化所有容器内的bean的接口,spring中所有解析配置文件的类都直接或者间接实现该接口
ApplicationContext接口implements BeanFactory
创建对象的特点:
BeanFactory 在解析配置文件时并不会初始化对象,只有在使用对象时(getBean())才会对该对象进行初始化
ApplicationContext 在解析配置文件时对配置文件中的所有对象都初始化了,getBean()方法只是获取对象的过程

Spring Bean管理

IOC容器

工厂只负责创建对象,而Spring当然不仅仅是一个对象工厂;其核心是一个对象容器,由于具备控制反转的能力,所以也叫它IOC容器;

容器可以理解为存放对象的地方,当然不仅仅是存储,还有对象的管理,包括-创建-销毁-装配; 这样原本程序要做的事情交给了Spring,所以这属于IOC,称之为IOC容器;

Spring有两个容器接口ApplicationContext是BeanFactory的子接口。它们都可以作为Spring的容器;

技术图片

两种容器的区别:

  • BeanFactory采取的懒加载的方式,在获取对象时才会实例化
  • ApplicationContext会在工厂初始化时立即实例化对象
  • BeanFactory作为顶级接口主要面向于Spring框架本身,仅提供了基础基本的容器功能如DI

  • ApplicationContext,是BeanFactory的子接口,意味着功能比BeanFactory更多,诸如国际化,注解配置,XML配置等等,因此ApplicationContext使用更多
    • ApplicationContext的两个实现类区别:
    • ClassPath表示从类路径中获取配置文件,
    • FileSystem表示从文件系统获取配置文件

SpringBean的管理

Bean的实例化

1. 使用类构造器(默认无参数)

该方式Bean类中必须存在无参构造器

<bean id="UserService1" class="com.yyh.serviceimpl.UserServiceImpl"/>

2. 使用静态工厂方法

xml配置:

<bean id="userService" class="com.yyh.serviceimpl.ServiceFactory" factory-method="getService"/>

工厂:

import com.yyh.service.UserService;
public class ServiceFactory {
    public static UserService getService() {
          System.out.println("factory static run!");
        return new UserServiceImpl();
    }
}

3. 使用实例工厂方法

xml配置:

<!--工厂Bean-->
<bean id="serviceFactory" class="com.yyh.serviceimpl.ServiceFactory"/>
<!--Service Bean-->
<bean id="userService2" factory-bean="serviceFactory" factory-method="getService2"/>

工厂添加方法:

public UserService getService2() {
    System.out.println("factory instance run!");
    return new UserServiceImpl();
}

Bean的命名

配置Bean时,可以使用 id 或者 name 属性给bean命名。 id 和 name 属性作用上一样,推荐使用id。

  • id取值要求严格些,必须满足XML的命名规范。id是唯一的,配置文件中不允许出现两个id相同的bean。

  • name取值比较随意,甚至可以用数字开头。在配置文件中允许出现多个name相同的bean,在用getBean()返回实例时,最后的一个Bean将被返回。

    注意:在spring5中name和id一样也不允许有重复的名称。

  • 如果没有id,name,则用类的全名作为name

    <bean class="test.Test"> ,可以使用 getBean("test.Test") 返回该实例。

  • 如果存在多个id和name都没有指定,且类都一样的,如:

    <bean class="com.yh.service.UserService"/>
    <bean class="com.yh.service.UserService"/> 
    <bean class="com.yh.service.UserService"/>

    则可以通过getBean(“完整类名#索引”)来获得,如:getBean("com.yh.service.UserService#1"),索引从0开始,若要获取第一个则可以忽略索引,直接写类名

  • name中可以使用分号(“;”)、空格(“ ”)或逗号(“,”)来给这个Bean添加多个名称(相当于别名 alias 的作用)。如:

    "
    name=“a b c d”等同于 name=“a,b,c,d” 这样写相当于有 1 2 3 4(4个)个标识符标识当前bean id=“1 2 3 4” 这样写相当于有 “1 2 3 4”(1个)个标识符标识当前bean
    "

    而id中的任何字符都被作为一个整体 ;

  • 如果既配置了 id ,也配置了 name ,则两个都生效。当然也不能重复;

  • 当注解中出现与xml配置中相同的id或相同name时,优先是用xml中的配置

Bean的作用域

类别 说明
singleton 默认值; 在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在。
prototype 每次从容器中调用Bean时,都返回一个新的实例;
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个HTTP Session 共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext 环境
application Bean的作用域为ServletContext ,仅适用于WebApplicationContext环境。

作用域就是指作用范围:单例则表示对象的作用范围是整个Spring容器,而prototype则表示不管理作用范围,每次get就直接创建新的

生命周期

init和destroy

Spring提供了非入侵(不强制类继承或实现)方式的生命周期方法,可以在Bean的初始化以及销毁时做一些额外的操作

<bean id="service" class="com.yh.service.UserService" scope="singleton"
      init-method="init" destroy-method="destroy"/>
<!--
init-method     用于初始化操作
detroy-method       用于销毁操作

注意:destroy仅在scope为singleton时有效 因为多例情况下

Bean的完整生命周期

技术图片

执行顺序及其含义:

1 构造对象
2 设置属性
3 了解Bean在容器中的name
4 了解关联的beanFactory
5 初始化前处理
6 属性设置完成
7 自定义初始化方法
8 初始化后处理
9 业务方法
10 Bean销毁方法
11 自定义销毁方法

依赖注入

依赖指的是当前对象在运行过程中需要使用到的其他参数,Spring可以帮助我们来完成这个依赖关系的建立,说的简单点就是把你需要参数的给你,而你不用管参数怎么来的,已达到尽可能的解耦 ;

举个例子:

Controller 中需要Service对象,Spring可以把Service自动丢到你的Controller中,你不需要关系Service是怎么来的,用就完了;

要使用依赖注入,必须现在需要依赖的一方(Controller)中为被依赖的一方(Service)定义属性,用以接收注入;

构造方法注入

bean:

public class User2 {
    private String name;
    private int age;
    private Phone phone;

    public User2(String name, int age, Phone phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }
    @Override
    public String toString() {
        return "User2{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", phone=" + phone +
                '}';
    }
}

xml:

 <!--依赖注入-->
    <bean id="user" class="com.yh.demo2.User2">
        <!--按参数名称注入        -->
        <constructor-arg name="name" value="jerry"/>
        <!--按参数位置注入           -->
        <constructor-arg index="1" value="18"/>

        <!--参数类型为其他bean对象时value换成ref        -->
        <constructor-arg name="phone" ref="phone"/>

        <!--type指定类型不常用        -->
        <!--<constructor-arg type="java.lang.String" name="name" value="jerry"/>-->
    </bean>
    <!--user需要的依赖phoneBean-->
    <bean id="phone" class="com.yh.demo2.Phone"/>

setter方法注入

依然对上面的User2类的依赖进行注入

<!--setter方法注入(属性注入)  -->
<bean id="user2" class="com.yh.demo2.User2">
    <property name="name" value="jerry"/> <!--注入常量值-->
    <property name="age" value="20"/>
    <property name="phone" ref="phone"/> <!--注入其他Bean-->
</bean>

注意:上述配置要求User2必须存在空参构造器

c命名标签

上面通过嵌套标签constructor的方式注入依赖,在需要注入的依赖较多时导致xml显得很臃肿,C名称空间来简化xml中<constructor-arg>标签的书写

使用前需要先在xml头部进行声明

xmlns:c="http://www.springframework.org/schema/c"

技术图片

使用:

<!--c命名空间的使用-->
<bean id="user3" class="com.yh.demo2.User2" c:name="jerry" c:_1="21" c:phone-ref="phone"></bean>
<!--
c:name              指定为name参数赋值
c:_1                指定为构造函数的第2个参数赋值
c:phone-ref   指定为构造函数的第phone参数赋值为id为"phone"的Bean
-->

p命名标签

同样的p命名空间则是用于简化<property>标签的书写

声明:

xmlns:p="http://www.springframework.org/schema/p"

使用:

<bean id="user4" class="com.yh.demo2.User2" p:name="jerry" p:age="20" p:phone-ref="phone"/>
<!--
p:name              指定为name属性赋值
p:age               指定为age属性赋值
p:phone-ref   为phone属性赋值为id为"phone"的Bean
-->

SpEL注入

SpEL即Spring Expression Language的缩写,与JSTL一样是表达式语言,可以支持使用更加复杂的语法注入依赖,包括标准数学运算符,关系运算符,逻辑运算符,条件运算符,集合和正则表达式等;

语法:#{表达式}

用例:

<!--SpEL    -->
<bean id="user5" class="com.yh.demo2.User2">
<!--<property name="name" value="#{'jerry'}"/>-->           <!--字符常量-->
<!--<property name="age" value="#{100.0}"/>-->              <!--数字常量-->
<!--<property name="phone" value="#{phone}"/>-->            <!--对象引用-->
<!--<property name="name" value="#{phone.model.concat(' jerry')}"/>--> <!--方法调用-->
<!--<property name="age" value="#{1+100}"/>-->              <!--算数符-->
<!--<property name="name" value="#{'11' > '22'}"/>-->       <!--比较符-->
<!--<property name="name" value="#{true or false}"/>-->     <!--逻辑符-->
<!--<property name="name" value="#{1 > 0?1:0}"/>-->         <!--三目-->
</bean>

容器类型的注入

<!--    容器数据类型注入-->
<bean id="user100" class="com.yh.demo2.User3">
    <!--set注入        -->
    <property name="set">
        <set>
            <value>3</value>
            <value>3</value>
            <value>a</value>
        </set>
    </property>
    <!--list注入        -->
    <property name="list">
        <list>
            <value>3</value>
            <value>3</value>
            <value>a</value>
        </list>
    </property>
    <!--map注入        -->
    <property name="map">
        <map>
            <entry key="name" value="jerry"/>
            <entry key="age" value="18"/>
            <entry key="sex" value="man"/>
        </map>
    </property>
    <!--properties注入        -->
    <property name="properties">
        <props>
            <prop key="jdbc.user">root</prop>
            <prop key="jdbc.password">admin</prop>
            <prop key="jdbc.driver">com.mysql.jdbc.Driver</prop>
        </props>
    </property>
</bean>

强调:Spring的依赖注入要么通过构造函数,要么通过setter,什么接口注入都tm扯犊子;

接口注入不是一种注入方式,只不过由于OOP的多态,Spring在按照类型注入时,会在容器中查找类型匹配的Bean,如果没有则查找该类的子类,如果容器中有多个匹配的子类Bean时会抛出异常,坑了一堆人,然后就开始意淫给这个问题取个名字吧.....接口注入.....

注解配置

注解配置Bean

通用注解

? @Component 用于在Spring中加入Bean

MVC场景下

? @Controller 等价于 @Component 标注控制层

? @Service 等价于 @Component 标注业务层

? @Repository 等价于 @Component 标注数据访问层(DAO)

在实现上没有任何不同,仅仅是为了对Bean进行分层是结构更清晰

使用步骤:

? 1.需要依赖context和aop两个jar包

? 2.添加命名空间

? 3.指定扫描的注解所在的包

<?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:context="http://www.springframework.org/schema/context"

       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">


    <!--指定要扫描注解的包    -->
    <context:component-scan base-package="com.yh.demo"/>


</beans>

要注册的Bean:

import org.springframework.stereotype.Component;

@Component("userService)
public class UserService {

    public String hello(String name){
        return "hello " + name;
    }
}

测试:

public class Tester {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = (UserService) context.getBean("userService");
        System.out.println(service.hello("jerry"));
    }
}

若注解中没有指定id则默认使用简单类名且小写开头,userService

注解注入

@Value用于对基本类型属性进行注入

@Autowired将容器中的其他Bean注入到属性中

@Qualifier("BeanID") 指定要注入的Bean的ID

准备UserDAO类:

import org.springframework.stereotype.Repository;

@Repository("userDAO")
public class UserDAO {
    public void save(){
        System.out.println("user saved!");
    }
}

UserService类:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("userService")
public class UserService {

    @Value("hello")//基本类型
    private String info;
        //@Autowired(required = false) //默认为true表示属性时必须的不能为空
    @Autowired //注入类型匹配的Bean
    //@Qualifier("userDAO") //明确指定需要的BeanID
    private UserDAO userDAO;

//set/get.....
}

测试:

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Tester {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = (UserService) context.getBean("userService");
        System.out.println(service.getInfo());//普通属性测试
        service.getUserDAO().save();//对象属性测试
    }
}

Autowired默认自动注入类型一致的Bean;required属性用于设置属性是否是必须的默认为true

Qualifier需要和Autowired搭配使用,用于明确指定要注入的Bean的ID

注意:

当Spring中存在多个类型都匹配的Bean时直接报错

接口:

public interface PersonDao {
}

两个实现类:

@Repository()
public class PersonDaoImpl1 implements PersonDao{
}
@Repository()
public class PersonDaoImpl2 implements PersonDao{
}

注入:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("userService")
public class UserService {
    @Autowired
    private PersonDao personDao;
}

@Resource

Qualifier和Autowired书写繁琐,@Resource可将两个标签的功能整合,即注入指定ID的Bean

@Resource标准注解的支持是JSR-250中定义的,所以时使用需要导入扩展包,Maven依赖如下:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

Resource默认按照使用属性名称作为id查找,查找失败则使用类型查找

可以利用name属性指定通过id查找

也可通过type指定类型,当出现相同类型的多个Bean时抛出异常

import javax.annotation.Resource;

@Component("userService")
public class UserService {
        //@Resource()//默认按照id/name
    //@Resource(name="xx")//指定name
    //@Resource(type = PersonDaoImpl1.class) //指定type
    @Resource(name="xx",type = PersonDaoImpl1.class)//同时指定name和type
    private PersonDao personDao;
}

@Scope

用于标注Bean的作用域

@Repository()
@Scope("prototype") //每次get都创建新的 
public class UserDAO {
    public void save(){
        System.out.println("user saved!");
    }
}

因为注解的表达能力有限,很多时候无法满足使用需求;我们可以将注解和XML配合使用,让XML负责管理Bean,注解仅负责属性注入;

以上是关于如何理解IoC容器,Bean工厂,applicationContext.xml三者之间的关系的主要内容,如果未能解决你的问题,请参考以下文章

[Spring5]IOC容器_Bean管理_工厂Bean

对spring IOC容器DI的理解

Spring全面详解(学习总结)

对Spring中IOC和AOP的理解

介绍 Spring IoC 容器和 bean

IOC容器和注入方式