Spring框架的核心功能:
- Spring 容器作为超级大工厂,负责创建、管理所有的Java对象,这些Java对象被称为 Bean。
- Spring 容器管理容器中 Bean 之间的依赖关系,Spring 使用一种被称为“依赖注入”的方式来管理 Bean 之间的依赖关系。
当某个Java对象(调用者)需要调用另一个Java对象(被调用者)的方法时,传统方式:
- 原始做法:调用者主动创建被依赖的对象,然后再调用被依赖对象的方法
- 简单工厂模式:调用者先找到被依赖的工厂,然后主动通过工厂去获取被依赖对象,最后再调用被依赖对象的方法。
使用依赖注入,不仅可以为 Bean 注入普通的属性值,还可以注入其它 Bean 的引用。通过这种依赖注入,Java EE 应用中的各个组件不需要以硬编码的方式耦合在一起,甚至无需使用工厂模式。调用者获取被依赖对象的方式由原来的主动获取,变成被动接受,即控制反转。控制反转和依赖注入其实是同一种行为的两种表达,只是描述的角度不同而已。
依赖注入的两种方式:
设值注入
IoC 容器使用成员变量的setter方法来注入被依赖对象。
<property name="axe" ref="steelAxe"></property>
构造注入
IoC容器使用构造器来注入被以来对象:直接调用有参数的构造器,当Bean实例创建完成之后,已经完成了依赖关系的注入
<constructor-arg ref ="steelAxe"></constructor-arg>
两种方式对比
这两种注入方式没有绝对的好坏,只是适应的场景有所不同
设值注入的优点:
- 与传统的 JavaBean的写法相比,程序开发人员更容易理解、接受。通过setter方法设定依赖关系显得更加直观自然。
- 对于复杂的依赖关系,采用构造注入会导致构造器过于臃肿,难以阅读。
- 尤其在某些成员变量可选的情况下,多参数的构造器更加笨重。
构造注入的优点:
- 构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。
- 对于依赖关系无需变化的Bean,构造注入更有用处。因为没有setter方法,所有的依赖关系全部在构造器内设定。因此无需担心后续代码对依赖关系产生破坏。
- 依赖关系只能在构造器中设定,只有组件的创建者才能改变组件的依赖关系。对于组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。
建议采用以设值注入为主,构造注入为辅的注入策略。对于依赖关系无变化的注入,尽量采用构造注入;而其它依赖关系的注入,则考虑采用设值注入。
Spring容器
Spring容器最基本的接口是BeanFactory,它负责配置、创建、管理Bean,它有一个子接口:ApplicationContext,因此也被成为Spring上下文。Spring容器还负责管理Bean与Bean之间的依赖关系。
BeanFactory接口包含以下几个基本方法:
boolean containsBean(String name);
<T> T getBean(Class<T> requiredType);
Object getBean(String name);
<T> T getBean(String name, Class RequiredType);
Class<?> getType(String name);
ApplicationContext是BeanFactory的子接口,对于大部分的Java EE 应用而言,使用它作为Spring容器更为方便,其常用实现类:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext和AnnotationConfigApplicationContext
。如果在Web应用中使用Spring容器,则通常有XmlWebApplicationContext、AnnotationCongifWebApplicationContext
两个实现类。
ApplicationContext允许以声明式方式操作容器,无需手动创建。除了提供BeanFactory所支持的全部功能之外,还有如下额外的功能:
- 默认初始化所有的
singleton Bean
(系统前期创建ApplicationContext将会有较大的系统开销,一旦初始化完成,程序后面获取singleton Bean
实例将会拥有较好的性能),也可以通过配置取消预初始化(lazy-init="true"
)。 - 继承
MessageSource
接口,因此提供国家化支持。 - 资源访问。
- 事件机制。
- 同时加载多个配置文件。
- 以声明式方式启动并创建Spring容器。
创建Bean的三种方式
- 使用构造器创建Bean实例
- 使用静态工厂方法创建Bean实例
<Bean.../>
元素需要制定两个属性:
-
class
:该属性值设置为静态工厂类的类名 -
factory-method
:该属性指定静态工厂方法来生产Bean实例
若静态工厂方法需要参数,则使用<constructor-arg.../>
元素传入
- 使用实例工厂方法创建Bean实例
<Bean.../>
元素需要制定两个属性:
-
factory-bean
:该属性值设置为工厂Bean的id -
factory-method
:该属性指定实例工厂方法来生产Bean实例
使用这种方法时,必须将实例工厂配置成Bean的还是理;而配置静态工厂方法创建Bean时无需配置工厂Bean。
深入理解容器中的Bean
Spring框架绝大部分工作都集中在对容器中Bean的管理上,包括管理容器中Bean的生命周期,使用Bean继承等。
抽象Bean和子Bean
将多个<bean.../>
配种中相同的信息提取出来,集中成配置模板——这个配置模板不是真正的Bean,Spring容器不应该创建该配置模板,于是需要为该<bean.../>
增加abstract="true"
,即抽象Bean。抽象Bean不能实例化。
将大部分相同信息配置成抽象Bean之后,将实际的Bean实例配置成该抽象Bean的子Bean即可。子Bean定义可以从父Bean继承实现类,构造参数、属性值等配置信息。除此之外,子Bean配置可以增加新的配置信息,并可指定新的配置信息覆盖父Bean的定义。
Bean继承与Java继承的区别
- 子Bean和父Bean可以是不同的类型
- Bean的继承是实例之间的关系,主要表现为参数值的延续
- 子Bean不可以作为父Bean使用,不具有多态性
获取Bean本身的id
可借助Spring提供的 BeanNameAware
接口,实现该接口,并实现该接口提供的setBeanName()
方法。这个方法会在容器创建完该Bean之后,由Spring容器自动调用。
强制初始化Bean
为了显示指定被依赖Bean在目标Bean之前初始化,可以使用depends-on
属性,该属性可以在初始化主调Bean之前,强制初始化一个或多个Bean。
容器中Bean的生命周期
Spring可以管理singleton 作用于的Bean的生命周期,可以精确地知道该Bean何时被创建、何时被初始化完成、容器何时准备销毁该Bean实例。
对于prototype作用域的Bean,Spring仅仅负责创建,创建完成之后,Bean实例完全交给客户端代码管理,容器不再跟踪其生命周期。
两个时机:
- 注入依赖关系之后
- 即将销毁Bean之前
依赖关系注入之后的行为
Spring提供两种方式在Bean全部属性设置成功后执行特定行为
使用init-method属性,只是增加一个普通的方法,没有代码污染。
实现InitializingBean接口,实现其 void afterPropertiesSet() Throws Exception方法,污染代码。
若同时使用两种方式,则先执行接口中的方法,再执行属性指定的方法,下同。
Bean销毁之前的行为
Spring也提供了两种方式定制Bean实例销毁之前的特定行为
使用destroy-method属性
实现DisposableBean接口,实现void destroy() Throws Exception方法
如何让Spring容器优雅地关闭呢?在JVM里注册一个关闭钩子(shutdown hook)
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// 相应的业务代码
// 为Spring容器注册关闭钩子,程序将会在退出JVM之前关闭Spring容器
// 并保证关闭Spring之前调用singleton Dean实例的析构回调方法。
ctx.registerShutdownHook();
协调作用于不同步的Bean
当singleton作用域的Bean依赖prototype作用域的Bean时,Spring容器会在初始化singleton作用域的Bean之前,先创建prototype作用域的Bean,然后才初始化singleton Bean,并将prototype Bean注入到singleton Bean。在这种情况下,singleton Bean访问prototype Bean时得到的永远是最初的那个prototype Bean,会产生不同步的情况。解决方法:
- 放弃依赖注入,代码主动向容器请求Bean实例。
- 利用方法注入:lookup方法