详细的描述了Spring中Bean的实例化与依赖注入。
Bean的实例化
构造方法
Spring创建Bean的时候调用无参构造。(有参构造会报错,私有无参也可以,底层使用了反射)
注意:Spring中的报错从下向上看,直到找到有用的信息。
注意:若不存在无参构造方法,会抛出异常:BeanCreationException
添加无参构造后:
运行结果:
这一节讨论的是直接按 class 配置、又没有提供构造参数时的实例化方式:容器需要找到可调用的无参构造器,再完成对象创建。补上无参构造器解决的是该配置下的实例化条件,并不意味着 Spring 一律禁止有参构造;后文的 constructor-arg 就是在显式描述构造参数。排查创建失败时应继续查看异常原因链,区分构造器选择、依赖缺失与初始化失败。
静态工厂
创建对象不需要自己new,创建一个工厂创建对象达到解耦的效果
样例代码:
package wang.factory;
import wang.dao.Dao;import wang.dao.impl.DaoImpl;
public class DaoFactory { public static Dao getDao() { System.out.println("factory set up ..."); return new DaoImpl(); }}配置文件添加如下代码:
<bean id="daoFactory" class="wang.factory.DaoFactory" factory-method="getDao"/>主函数:
package wang;
import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import wang.dao.Dao;
public class App3 { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); Dao dao = (Dao) ctx.getBean("daoFactory"); dao.save(); }}运行结果:
这里 bean 的 class 指向 DaoFactory,factory-method 指向静态方法 getDao。容器调用这个方法取得它返回的 DaoImpl,并把该返回对象作为名称为 daoFactory 的 Bean 提供给应用。因此按 daoFactory 名称获取 Bean 的使用者拿到的是 Dao 类型的产品,而不是工厂本身;主函数再调用 dao.save()。对象创建逻辑被集中到工厂方法,调用者无需直接写出产品实现类的构造语句。
实例工厂
工厂代码同上
配置文件修改为如下代码:
<bean id="daoFactorytemp" class="wang.factory.DaoFactory"/> <!-- 创建工厂bean --><bean id="daoFactory" factory-bean="daoFactorytemp" factory-method="getDao"/><!-- factory-bean指定工厂bean -->第一行为一个无用的bean,改良后即为第四种方法
主函数不变
运行结果:
工厂方式的共同目标是让容器对外提供 Dao 产品,主函数继续按产品的 Bean 名称获取对象并调用 save()。实例工厂配置使用 factory-bean 找到工厂对象,再由 factory-method 指定生产方法;FactoryBean 方式则实现统一的 getObject() 与 getObjectType() 契约。使用 FactoryBean 时,普通名称取到产品,带 & 前缀的名称才取得工厂自身。两者都要正确提供创建逻辑,不能只凭调用端相同就认定配置已经等价。
FactoryBean(重要!)
在第三种方法存在两个问题
一、factory-bean属性使用时会创建一个无用的Bean
二、factory-method属性方法名不固定,非动态实现
因此,对此方法做了改良
factory实现一个接口FactoryBean
工厂代码
package wang.factory;
import org.springframework.beans.factory.FactoryBean;import wang.dao.Dao;import wang.dao.impl.DaoImpl;
public class DaoFactoryBean implements FactoryBean<Dao> { // 代替原始实例工厂创建对象的方法 @Override public Dao getObject() throws Exception { return new DaoImpl(); } // 得到Bean的类型 @Override public Class<?> getObjectType() { return Dao.class; } // 设置Bean是否单例子,return true为单例,false为非单例 // 该方法一般不重写 /*@Override public boolean isSingleton() { return true; }*/}配置文件修改为如下代码:
<bean id="daoFactory" class="wang.factory.DaoFactoryBean"/>主函数不变
运行结果:
工厂方式的共同目标是让容器对外提供 Dao 产品,主函数继续按产品的 Bean 名称获取对象并调用 save()。实例工厂配置使用 factory-bean 找到工厂对象,再由 factory-method 指定生产方法;FactoryBean 方式则实现统一的 getObject() 与 getObjectType() 契约。使用 FactoryBean 时,普通名称取到产品,带 & 前缀的名称才取得工厂自身。两者都要正确提供创建逻辑,不能只凭调用端相同就认定配置已经等价。
补充:Bean的生命周期
控制生命周期
方法一:
控制方法代码:
public static DaoImpl implements Dao { public void save() { System.out.println("Dao save ... ") } public void init() { System.out.println("Dao init ... ") } public void destroy() { System.out.println("Dao destroy ... ") }}配置控制方法:
<bean id="Dao" class="wang.dao.impl.DaoImpl" init-method="init" destroy-method="destory"/>方法二
实现InitializingBean, DisposableBean接口
public static DaoImpl implements Dao, InitializingBean, DisposableBean { public void save() { System.out.println("Dao save ... ") } public void init() throws Exception { System.out.println("Dao init ... ") } public void destroy() throws Exception { System.out.println("Dao destroy ... ") }}生命周期
初始化容器
1.创建对象(内存分配)
2.执行构造方法
3.执行属性注入( set操作)
4.执行bean初始化方法使用bean
1.行业务操作关闭/销毁容器
1.执行bean销毁方法
注:**销毁的时机:**容器关闭前触发bean的销毁
关闭容器方式:
手工关闭容器
ConfigurableApplicationContext接口close()
操作注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
ConfigurableApplicationcontext接口registerShutdownHook ()操作
依赖注入
setter注入
引用类型
详情见博客 SSM01-Spring中的Bean
步骤如下
1.在bean中定义引用类型属性并提供可访问的set方法
2.配置中使用property标签ref属性注入引用类型对象
简单类型
步骤如下
1.在bean中定义引用类型属性并提供可访问的set方法
2.配置中使用property标签value属性注入简单类型数据
构造器注入
引用类型(仅做了解)
步骤如下
1.在bean中定义引用类型属性并提供可访问的构造方法
2.配置中使用constructor-arg标签ref属性注入引用类型对象
简单类型
步骤如下
1.在bean中定义引用类型属性并提供可访问的set方法
2.配置中使用property标签value属性注入简单类型数据
注意:
配置中使用constructor-arg标签type属性设置按形参类型注入
配置中使用constructor-arg标签index属性设置按形参位置注入
依赖注入方式的选择(总结自黑马程序员,侵删)
1.强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
2.可选依赖使用setter注入进行,灵活性强
3.Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
4.如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
5.实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
6.自己开发的模块推荐使用setter注入
自动装配(优先级低于setter注入与构造器注入)
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
自动装配方式
按类型(常用)(不能对简单类型装配)
<bean id="daoFactory" class="wang.dao.impl.DaoImpl" autowire="byType"/>注意:需要set方法
注意:按类型(byType)时,同类型的应该只有一个bean,否则分不清。
按名称(set方法第一个字母改为小写的名称,即标椎的变量名)
<bean id="daoFactory" class="wang.dao.impl.DaoImpl" autowire="byName"/>注意:因为变量名与byName耦合度高,不建议使用。
按构造方法
集合注入(主要学习格式)
Dao接口
package wang.dao;
public interface DataDao { public void save();}Dao实现
package wang.dao.impl;
import wang.dao.DataDao;
import java.util.*;
public class DataDaoImpl implements DataDao {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
public void setArray(int[] array) { this.array = array; }
public void setList(List<String> list) { this.list = list; }
public void setSet(Set<String> set) { this.set = set; }
public void setMap(Map<String, String> map) { this.map = map; }
public void setProperties(Properties properties) { this.properties = properties; }
public void save() { System.out.println("book dao save ...");
System.out.println("遍历数组:" + Arrays.toString(array));
System.out.println("遍历List" + list);
System.out.println("遍历Set" + set);
System.out.println("遍历Map" + map);
System.out.println("遍历Properties" + properties); }}配置文件:
<?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"> <bean id="dataDao" class="wang.dao.impl.DataDaoImpl"> <!--数组注入--> <property name="array"> <array> <value>100</value> <value>200</value> <value>300</value> </array> </property> <!--list集合注入--> <property name="list"> <list> <value>java</value> <value>c++</value> <value>python</value> <value>php</value> </list> </property> <!--set集合注入--> <property name="set"> <set> <value>java</value> <value>python</value> <value>java</value> <value>php</value> </set> </property> <!--map集合注入--> <property name="map"> <map> <entry key="country" value="china"/> <entry key="province" value="shanxi"/> <entry key="city" value="xian"/> </map> </property> <!--Properties注入--> <property name="properties"> <props> <prop key="country">china</prop> <prop key="province">shanxi</prop> <prop key="city">xian</prop> </props> </property> </bean></beans>主函数:
package wang.dao;
import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App6 { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); DataDao dataDao = (DataDao) ctx.getBean("dataDao"); dataDao.save(); }}运行结果:
DataDaoImpl 的五个 setter 分别接收数组、List、Set、Map 和 Properties。XML 中 array 与 list 保留配置的元素顺序,set 表达不重复元素,因此配置里重复的 java 不应成为两个独立集合元素;map 与 props 则把键对应到值。save() 会逐项打印这些已注入的属性,检查重点是容器类型与内容是否符合配置,而不是照着某个 Set、Map 或 Properties 的显示顺序判断注入是否正确。
第三方资源注入
容器
两种加载容器的方式
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //推荐ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\ssm\\ssm\\springtest\\springtest\\src\\main\\resources\\applicationContext.xml") //绝对路径寻找两种获取bean的方式
Dao dao = (DataDao) ctx.getBean("dao");Dao dao = ctx.getBean("dao", Dao.class); //多个同种类型报错对比ApplicationContext和BeanFactory
BeanFactory是顶级接口的Bean是延迟加载
ApplicationContext是子接口(常用)也可以延迟加载,在bean中加入参数 lazy-init=“true” 即可
加载第三方Bean
加载Properties文件
步骤如下:
- 开启Context命名空间
- 使用context加载properties文件
- 使用属性占位符${}读取properties配置文件中的属性
样例代码如下:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!--步骤--> <!--1.开启命名空间--> <!--2.使用context加载properties文件--> <context:property-placeholder location="xxxx.properties"/>
<!--3.使用属性占位符${}读取properties配置文件中的属性--> <bean class="xxxx"> <property name="xxxx" value="${ 配置中的变量名称 }"/> </bean></beans>注意:加载时可以关闭系统属性,因为系统属性的优先级高于
注意:多个配置文件用逗号隔开
注意:专业的写法加载所有配置文件如下
<!-- 加载properties文件标准格式 --><context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/><!-- 从类路径或jar包中搜索并加载properties文件<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>总结(来自黑马程序员,侵删)
容器相关
原理示意:类路径配置与文件系统配置分别交给相应的 ApplicationContext 实现读取,再由容器创建对象、装配依赖并执行初始化。应用按名称和类型获取 Bean;普通上下文通常在刷新时创建非懒加载单例,懒加载设置可推迟创建。关闭容器则为其管理的单例触发销毁回调,不应把所有作用域都理解成同样的生命周期。
bean相关
原理示意:Bean 先实例化,再接受依赖注入,随后执行初始化回调,之后才供应用使用;容器关闭时对受管单例执行销毁回调。XML 可以通过 init-method 与 destroy-method 指定方法,注解写法则标记相应方法。singleton 在容器内共享实例,prototype 每次请求可创建新实例,其完整销毁过程不由容器代管。
依赖注入相关
依赖注入可以先按入口区分:setter 注入使用 property,构造器注入使用 constructor-arg;再按数据区分,ref 指向另一个 Bean,value 提供简单值。集合使用 array、list、set、map、props 描述内容。自动装配则让容器按规则寻找依赖,按类型时要解决候选歧义,按名称时要对应属性与 Bean 名称。选择方式应结合强制依赖与可选依赖,而不是把所有对象都机械地改成同一种装配形式。