一.Spring装配Bean的简要概述
1.要使应用程序中的Spring容器成功启动, 需要同时具备以下三个方面的条件:
(1)Spring框架的类包都已经被Maven项目依赖成功.
(2)应用程序为Spring提供了完整的Bean配置信息.
(3)Bean的类都已经放在应用程序的类路径
2.Spring是如何在容器中存储Bean的信息?
Spring启动时读取应用程序提供的Bean的配置信息, 并在Spring容器中生成一份相关的Bean配置注册表, 然后根据这张注册表实例化Bean. 装配好Bean之间的依赖关系, 为上层应用提供准备就绪的运行环境.
Bean配置信息是Bean的元数据信息, 由四个方面组成:
(1)Bean的实现类.
(2)Bean的属性信息, 如数据源的连接数, 用户名, 密码等.
(3)Bean的依赖关系, Spring根据Bean的依赖关系完成Bean之间的装配.
(4)Bean的行为配置, 如生命周期范围及生命周期各过程的回调函数等.
Bean的元数据信息在Spring容器中的内部对应物是由一个个BeanDefinition形成的Bean注册表.
Spring支持多种Bean配置方式, Spring1.0仅支持基于XML的配置, Spring2.0新增基于注解的配置, Spring3.0新增基于Java类的配置, Spring4.0新增基于Groovy动态语言的配置.
Bean配置信息首先定义了Bean的实现及依赖关系, Spring容器根据各种形式的Bean配置信息在容器内部建立起Bean定义注册表; 然后根据注册表加载, 实例化Bean, 并建立Bean和Bean之间的依赖关系, 最后将这些准备就绪的Bean放在Bean缓存池中, 以供外层的应用程序进行调用.
二.基于XML的配置
1.Bean的命名
id属性: 一般情况下, 在配置一个Bean时, 需要为其指定一个id属性作为Bean的名称. id在IoC容器中必须是唯一的, 而且id的命名必须满足XML对id的命名规范: 必须以字母开始, 后面可以是字母,数字,连字符,下划线,句号,冒号等完整结束的符号. 逗号和空格这些非完整结束符是非法的. Spring不允许出现两个id相同的<bean>.
name属性: name属性没有字符上的限制, 几乎可以使用任何符号. name可以指定多个名字, 名字之间可用逗号,分号或者空格进行分割. 用户可以使用这多个名字来获取IoC容器中的Bean. Spring容器允许出现两个相同name的<bean>, 如果有多个name相同的<bean>, 那么通过getBean(beanName)获取Bean时, 将返回后面的那个Bean. 原因是后面的Bean覆盖了前面同名的Bean.
如果id和name两个属性都未指定, Spring自动将全限定类名作为Bean的名称. 如下图所示:
第一个Bean可以通过 getBean("com.bean.Car")获得.
第二个Bean可以通过 getBean("com.bean.Car#1")获得
第三个Bean可以通过 getBean("com.bean.Car#2")获得
2.依赖注入
Spring支持两种依赖注入的方式, 分别是属性注入和构造函数注入, 除此之外, Spring还支持工厂方法注入方式.
(1)属性注入
属性注入指通过setXxx()方法注入Bean的属性值或依赖对象.
1.属性注入实例
属性注入要求Bean提供一个默认的构造方法, 并为需要注入的属性提供setter方法. Spring先调用Bean的默认构造方法实例化Bean对象, 然后通过反射的方法调用Setter方法注入属性值.
配置文件配置了一个Man Bean, 为该Bean的两个属性提供了属性值, Bean的每一个属性对应一个<property>标签, name为属性的名称, 在Bean的实现类中有与其对应的Setter方法. 注意:###Spring只会检查Bean中是否有对应的Setter方法, 对Bean中是否有对应的属性成员变量则不做要求.
如上图, 在xml配置文件中通过属性注入方式, 不需要在Man 类中添加属性des, 只需提供其对应的setDes()方法就可以了.
(2)构造函数注入
构造函数注入确保一些属性在Bean实例化时就得到设置, 确保Bean在实例化后就可以使用.
(1)联合使用类型和索引匹配入参
(2)通过自身类型反射匹配入参
如果Bean构造函数入参的类型是可辨别的, 由于Java反射机制可以获取到构造函数的入参类型, 即使构造函数注入的配置不提供类型和索引的信息, Spring依旧可以完成构造函数的注入工作.
(3)循环依赖问题
Spring容器能对构造函数配置的Bean进行实例化有一个前提, 即Bean构造函数入参引用的对象必须已经准备就绪, 由于这个机制的限制, 如果两个Bean都采用构造函数注入, 而且都通过构造函数入参引用对方, 就会发生类似于线程死锁的循环依赖问题.
上述情况就会造成线程死锁问题, 解决方法是将构造函数注入方式调整为属性注入即可.
将配置文件做如下更改, 则可以测试成功.
(3)工厂方法注入
1.非静态工厂方法
有些工厂方法是非静态的, 即必须实例化工厂类后才能调用工厂方法.
输出结果 Dog Bean:
由于工厂方法不是静态的, 所以首先需要定义一个工厂类的Bean, 通过factory-bean引入工厂类实例, 通过factory-method指定对应的工厂方法.
2.静态工厂方法
静态工厂方法意味着用户无需在创建工厂类实例的情况下就可以调用工厂类方法.
直接在<bean>中通过 class 属性指定工厂类, 再通过 factory-method指定对应的工厂方法.
3.选择注入方式的考量
构造函数注入的好处:
(1)保证一些重要的属性在Bean实例化时就设置好, 避免因一些重要属性没有提供而导致一个无用Bean实例的情况.
(2)不需要为每个属性提供Setter方法, 减少了类的方法个数.
(3)更好的封装类变量, 避免外部错误调用
构造函数注入的缺点:
(1)如果一个类属性众多, 那么使用构造函数注入, 使程序可读性变差.
(2)灵活性不强.
(3)不利于类的继承和扩展.
(4)构造函数注入可能会造成循环依赖问题.