目录
1 IoC容器概念
1.1 Spring IoC容器和beans的介绍
IoC也被称为依赖注入(DI)。这是对象定义他们依赖关系的过程,也就是说,其他对象仅仅通过构造器参数,工厂方法参数,或者是在它被构造或从一个工厂方法中返回后设置到对象实例上的属性来工作。当他创建bean的时候,容器接着注入这些依赖。这个过程从根本上是相反的,因此,名称“控制反转”,bean它本身控制实例或者是通过使用类的直接构造器或一个机制,例如服务定位器模式来定位他的依赖。
包org.springframework.beans
和org.springframework.context
是Spring框架的IoC容器的基础。BeanFactory
接口提供了一个先进的配置机制能够管理任何类型的对象。ApplicationContext
是BeanFactory的一个子接口。它增加了与Spring AOP特征的更加简单的集成;消息资源处理(用于国际化中),事件发布和应用层特定的上下文例如用于Web应用中的WebApplicationContext。
简而言之,BeanFactory
提供了配置框架和基本的功能,并且ApplicationContext
添加了更多的企业特定的功能。ApplicationContext
是BeanFactory
的一个完备的超级,并且被唯一使用在描述Spring IoC容器的这一章节中。关于使用BeanFactory
而不是ApplicationContext
的更多信息,请查看1.16小节的BeanFactory的介绍
。
在Spring中,那些组成你应用骨干的和那些被Spring IoC容器管理的对象被称为_beans_。一个Bean是一个被实例化的,组装的或其他被Spring IoC容器管理的对象。此外,简单的说,一个Bean就是你的应用程序中的对象中的一个。Beans和他们之间的依赖被映射到由容器使用的配置元数据中。
1.2 容器概览
①BeanFactoy: 由org.springframework.beans.factory.BeanFactory接口定义,是IOC容器的基本实现
②ApplicationContext: 由org.springframework.context.ApplicationContext接口定义,提供了更多的高级特性. 是BeanFactory的子接口。
(2)BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;ApplicationContext 提供了更高级的特性,面向使用 Spring 框架的开发者。几乎所有的应用场合都直接使用 ApplicationContext 而非底层的 BeanFactory。
(3)在 Spring IOC 容器读取 Bean 配置创建 Bean 实例之前, 必须对它进行实例化. 只有在容器实例化后, 才可以从 IOC 容器里获取 Bean 实例并使用.也就是说,必须先实例化IOC容器对象。
(4)ApplicationContext的3个常用实现类:
ClassPathXmlApplicationContext:从 类路径下加载XML配置文件
FileSystemXmlApplicationContext: 从文件系统中加载XML配置文件
XmlWebApplicationContext:从Web系统中加载XML配置文件
(5)实例化ApplicationContext容器
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext context=new FileSystemXmlApplicationContext("d:/applicationContext.xml");
(6)类图
2 配置使用容器
接口org.springframework.context.ApplicationContext
代表着Spring IoC容器并且负责实例化,配置和组装上述的beans。该容器使它的指令通过读取配置元数据作用于哪些对象去初始化,配置和组装。配置元数据表现在XML,Java注解或Java代码中。它允许您表达组成应用程度的对象以及这些对象之间的丰富的依赖性。
很多ApplicationContext接口的实现为开箱即用的。在独立的应用程序中,创建一个ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
的实例是通用的。虽然XML是定义配置元数据的原始格式,但是你可以通过提供少量的XML配置来指示容器使用Java注释或代码作为元数据格式,以声明性地支持这些附加的元数据格式。
在大多数的应用程序场景中,不需要显式的用户代码来实例化Spring IoC容器的一个或多个实例。例如,在一个Web应用场景中,在应用程序的web.xml文件中,一个简单的样板Web描述符XML通常就足够了。如果你正在使用基于Eclipse开发环境的Spring工具套件,这个样板配置可以通过很少的鼠标点击或按键来简单的创建。
下面的图表是Spring如何工作的高层视图。你的应用程序是与配置元数据绑定在一起的,所以在ApplicationContext
被创建和初始化之后,你将会有一个完成的配置完成的和可执行的系统或应用程序。
2.1 配置元数据
正如前面的图标显示,Spring IoC容器消费了一种配置元数据的形式;这个配置元数据表示应用程序开发人员告诉Spring容器如何在应用程序中实例化,配置和组装对象。
传统上,配置元数据是以简单直观的XML格式提供的,这篇章节主要使用它来传递Spring IoC容器的关键概念和特征。
基于XML的元数据不是唯一允许的配置元数据的形式。Spring IoC容器本身与实际写入配置元数据的格式完全分离。这些天,很多开发者选择_基于Java的配置_用于他们的Spring应用程序。
关于Spring容器中使用其他元数据格式的更多信息,请查看:
- 基于注解的配置:Spring 2.5引入了基于注解的配置元数据的支持。
- 基于Java的配置:从Spring 3.0开始,很多由Spring的Java配置项目提供的特征变成了核心Spring框架的一部分。因此,你可以在你的应用程序类之外通过使用Java而不是XML文件来定义beans。为了使用这些新的特征,请查看
@Configuration
,@Bean
,@Import
和@DependsOn
注解。
Spring配置至少包含一个或超过一个容器必须要管理的bean定义。基于XML的配置元数据显示了这些beans在上层<beans/>
元素中被配置成<bean/>
元素。Java配置典型地在一个@Configuration
类中使用@Bean
注解方法。
这些bean定义对应于组成你的应用程序的实际对象。典型地就是你定义服务层对象,数据访问对象(DAOs),表示对象,例如Struts Action
实例,基础对象,例如HibernateSessionFactories
,JMS Queues
等。通常情况下,容器中不配置细粒度域对象,因为通常创建和加载域对象是DAOs和业务逻辑的责任。然而,你可以使用带有AspectJ的Spring集成来配置在IoC容器控制之外被创建的对象。请查看使用AspectJ来依赖注入Spring的域对象。
下列示例展示了基于XML配置元数据的基本结构:
<?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="..." class="...">
<!-- 在这里配置该bean的合作者和相关配置 -->
</bean>
<bean id="..." class="...">
<!-- 在这里配置该bean的合作者和相关配置 -->
</bean>
<!-- 更多bean的配置 -->
</beans>
id
属性是一个字符床,你可以使用该字符串来声明个人的bean定义。class
属性定义bean的类型并且使用完全限定的类名。id属性的值指向协作对象。指向协作对象的XML没有在这个示例中展示出来;查看依赖
获取更多信息。
2.2 实例化一个容器
实例化一个Spring IoC容器是直截了当的。位置路径或提供给AplicationContext
构造器的路径实际是资源字符串,它允许容器从外部资源,如本地文件系统,从Java CLASSPATH
等中加载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("service.xml","daos.xml");
在你学习Spring的IoC容器之后,你可能想要了解更多的关于Spring Resource的信息,正如在Resource
中描述的,Resource
提供了一个方便的机制用于从在URI语法中定义的位置中读取一个输入流。特别的,Resource路径被用于构建应用上下文,正如在Application contexts和Resouces paths
中描述的。
下面的示例展示了服务层对象(services.xml)配置文件:
<?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">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
下列示例展示了数据访问对象daos.xml
文件:
<?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="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
在前面的例子中,服务层包含类PetStoreServiceImpl
,还有两个类型为JpaAccountDao
和JpaItemDao(基于JPA的对象/关系映射标准)
的数据访问对象。property name
元素指向JavaBean属性的名称,ref
指向另外bean定义的名称。id
和ref
的链接表示协作对象之间的依赖。想要了解配置一个对象依赖的更多细节,请查看依赖。
组合基于XML配置的元数据
bean定义跨越多个XML文件是有用的。通常情况下,每一个独立的XML配置文件在你的架构中代表一个逻辑层或模块。
你可以使用应用上下文构造器从这些XML片段中加载bean定义。这个构造器采用多个Resource
位置,就像在上一小节中展示的那样。相应替代的,使用一个或多个<import/>
元素从其他一个或多个文件中加载bean定义。例如:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
在上面的示例中,额外的bean定义从三个文件中被加载上来:services.xml
, messageSource.xml
和themeSource.xml
。在做importing的时候,所有位置路径都是与定义文件相对的,所以在做importing的时候,service.xml
必须在相同的目录或作为文件的类路径的位置,然而messageSource.xml
和themeSource.xml
必须位于引入文件的位置下面的resource
位置。正如你所看到的,主斜杠是被忽略的,但是考虑到这些路径都是相对的,不使用主斜杠是最好的形式。被引入文件的内容,包括顶层的<bean/>
元素,根据Spring模式来说,都必须要是有效的XML的bean定义。
使用相对路径"…/"在父目录中引入文件是可以的,但是不被推荐的。这样做在文件中也就是在当前应用外面创建了一个依赖。特别地,对于“classpath:”路径(例如,“classpath:…/service.xml”)来说这样的引用是不被推荐的,在这种情况下,运行时解决进程选择“最近的”类路径然后寻找他的父目录。类路径配置的改变可能会导致一个不同的,错误的目录的选择。
你可以一直使用完全合格的资源位置而不是相对路径:例如,“file:C:/config/services.xml"或"classpath:/config/services.xml”。然而,要知道你正在连接你的应用的配置到特定的绝对路径位置。一般来说,对于这样绝对位置保持间接方向是更可取的,例如,通过在运行时对JVM系统属性进行解析的占位符"${…}"。
导入指令是有beans名称空间本身提供的一个特征。除了普通的bean定义之外,更多的配置特征在由Spring,“context”和"util"名称空间提供的XML名称空间的选择中是可用的。
Groovy Bean定义 DSL
作为一个用于外部的配置元数据的例子,bean定义可以在Spring的Groovy Bean定义DSL中被表达,正如从Grails框架中所知道的那样。特别的,这样的配置将会和下面展示的一样作为一个结果存放在".groovy"文件中。
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
这个配置样式很大程度上与XML的bean定义相同,甚至支持Spring的XML配置命名空间。这也允许直接通过一个"importBeans"来导入XML bean定义文件。
2.3 使用容器
ApplicationContext
是能够维护不用bean及其依赖的注册表的高级工厂的接口。使用方法T getBean(String name,Class<T> requiredType)
,你可以检索你的bean的实例。
// 创建和配置bean
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml","daos.xml");
// 检索配置的实例
PetStoreService service = context.getBean("petStore",PetStoreService.class);
// 使用配置的实例
List<String> userList = service.getUsernameList();
使用Groovy配置,bootstrapping看上去很相似,仅仅是一个不同的上下文实现类,该实现类是Groovy认识的。(但是也理解XML的bean定义)。
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy","daos.groovy");
最灵活的变量便是与读者代理(reader delegates),例如用于XML文件的XmlBeanDefinitionReader
相结合的GenericApplicationContext。
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
这样的阅读者代理可以混合的,并且可以在相同的应用上下文相匹配,如果希望的话,可以从不同的配置源读取bean定义。
然后你可以使用getBean
来取回你的bean的实例。ApplicationContext
接口有一些其他的方法来取回beans,但是理想情况下你的应用代码中应该永远不使用他们。事实上,你的应用代码不应该有对getBean()
方法的调用,因此,也不应该有对Spring API的依赖。例如,Spring伴随着web框架的集成为多个web框架组件,例如控制器和JSF管理beans,的依赖注入,允许你通过元数据在一特定的bean上声明一个依赖(例如autowiring注解)。
2.4 实例化Bean
一个Spring IoC容器管理一个或多个beans。这些beans被你提供给容器的配置元数据创建(例如,XML中的<bean/>
定义)。
在容器本身,这些bean定义被代表成BeanDefinition
对象,该对象包含下述元数据:
- 包限定类名:特别的,被定义的bean的实际实现类。
- Bean行为配置元素,这标记bean在容器内的行为(范围,声明周期回调等等)。
- 该bean需要的其他bean的引用来做这个工作。这些引用也被称为协作者或依赖。
- 在新的创建的对象中要去设置的其他配置设定 - 例如,池子的大小限制,或管理连接池的bean中要使用的连接数。
这个元数据被翻译成一组属性来组成每一个bean定义。下面的表格描述了这些属性:
属性 | 在…中描述 |
---|---|
Class | 实例化Beans |
Name | 命名Beans |
Scope | Beans范围 |
Constructor arguments | 依赖注入 |
Properties | 依赖注入 |
Autowiring mode | Autowiring 协作者 |
Lazy initialization mode | 懒实例化Beans |
Initialization method | 实例化回调 |
Destruction method | 析构回调 |
除了包含如何创建一个特定bean的bean定义之外,ApplicationContext
实现也允许在容器外被创建的现存的对象的注册。这可以通过getBeanFactory()
方法访问ApplicationContext的BeanFactory来实现,这返回BeanFactory的DefaultListableBeanFactory
的实现。DefaultListableBeanFactory
支持通过registerSingleton(..)
和registerBeanDefinition(..)
来注册。然而,典型的应用程序只使用通过常规beans定义元数据定义的beans.
Bean元数据和人工提供的单实例需要尽早被注册,以便容器在autowiring阶段和其他自动创建阶段正确的解释他们。然而,重载现存的元数据和现存的单一实例在一定程度上是被支持的,新的beans的注册在运行时(同时访问工厂)是不被官方支持的,并且可能会导致同时访问异常,在bean容器中的 不一致的状态或两者都会发生。
一个bean定义实际上是创建一个或多个对象的菜单。当容器被询问的时候,它搜索菜单找到被命名的bean ,然后使用被该bean定义封装的配置的元数据来创建或获取一个真实的对象。
如果你使用基于XML的配置元数据,你指定<bean/>
元素的class
属性中要被实例化的对象的类型。这个class属性(该属性内部是位于BeanDefinition
实例的Class
属性)通常是强制的。(对于异常情况,查看通过使用一个实例工厂方法实例化
和Bean定义继承
)你可以通过下述任一一个方法来使用Class
属性。
-
典型的,在容器本身直接创建
bean
的情况下,通过调用他的构造函数来创建bean
类,与Java编码的new
操作符是一样的,从而指定要被构造的bean
类。 -
指定包含
static
工厂方法的实际的类,该方法被调用来创建对象,在不太通常的情况下,容器调用类中的static
工厂方法来创建bean
。从static
工厂方法中返回的对象类型可能完全是相同的类或是其他类。
3 IOC实例
3.1 实例应用
项目搭建好了,让我们来开发接口,此处我们只需实现打印“Hello World!”,所以我们定义一个“sayHello”接口,代码如下:
package com.ljq.test;
public interface HelloService {
public void sayHello();
}
接口开发好了,让我们来通过实现接口来完成打印“Hello World!”功能;
package com.ljq.test;
public class HelloServiceImpl implements HelloService{
public void sayHello(){
System.out.println("Hello World!");
}
}
接口和实现都开发好了,那如何使用Spring IOC容器来管理它们呢?这就需要配置文件,让IOC容器知道要管理哪些对象。让我们来看下配置文件helloworld.xml(放到src目录下):
<?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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- id 表示组件的名字,class表示组件类 -->
<bean id="helloService" class="com.ljq.test.HelloServiceImpl" />
</beans>
现在万一具备,那如何获取IOC容器并完成我们需要的功能呢?首先应该实例化一个IOC容器,然后从容器中获取需要的对象,然后调用接口完成我们需要的功能,代码示例如下:
package com.ljq.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 测试
*
* @author 林计钦
* @version 1.0 2013-11-4 下午10:56:04
*/
public class HelloServiceTest {
@Test
public void testHelloWorld() {
// 1、读取配置文件实例化一个IOC容器
ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml");
// 2、从容器中获取Bean,注意此处完全“面向接口编程,而不是面向实现”
HelloService helloService = context.getBean("helloService", HelloService.class);
// 3、执行业务逻辑
helloService.sayHello();
}
}
自此一个完整的Spring Hello World已完成,是不是很简单,让我们深入理解下容器和Bean吧。
3.2 详解IOC容器
在Spring IOC容器的代表就是org.springframework.beans包中的BeanFactory接口,BeanFactory接口提供了IOC容器最基本功能;而org.springframework.context包下的ApplicationContext接口扩展了BeanFactory,还提供了与Spring AOP集成、国际化处理、事件传播及提供不同层次的context实现 (如针对web应用的WebApplicationContext)。简单说, BeanFactory提供了IOC容器最基本功能,而 ApplicationContext 则增加了更多支持企业级功能支持。ApplicationContext完全继承BeanFactory,因而BeanFactory所具有的语义也适用于ApplicationContext。
容器实现一览:
• XmlBeanFactory:BeanFactory实现,提供基本的IOC容器功能,可以从classpath或文件系统等获取资源;
(1)File file = new File("fileSystemConfig.xml");
Resource resource = new FileSystemResource(file);
BeanFactory beanFactory = new XmlBeanFactory(resource);
(2)
Resource resource = new ClassPathResource("classpath.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
• ClassPathXmlApplicationContext:ApplicationContext实现,从classpath获取配置文件;
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath.xml");
• FileSystemXmlApplicationContext:ApplicationContext实现,从文件系统获取配置文件。
BeanFactory beanFactory = new FileSystemXmlApplicationContext("fileSystemConfig.xml");
ApplicationContext接口获取Bean方法简介:
• Object getBean(String name) 根据名称返回一个Bean,客户端需要自己进行类型转换;
• T getBean(String name, Class<T> requiredType) 根据名称和指定的类型返回一个Bean,客户端无需自己进行类型转换,如果类型转换失败,容器抛出异常;
• T getBean(Class<T> requiredType) 根据指定的类型返回一个Bean,客户端无需自己进行类型转换,如果没有或有多于一个Bean存在容器将抛出异常;
• Map<String, T> getBeansOfType(Class<T> type) 根据指定的类型返回一个键值为名字和值为Bean对象的Map,如果没有Bean对象存在则返回空的Map。
让我们来看下IOC容器到底是如何工作。在此我们以xml配置方式来分析一下:
一、准备配置文件:就像前边Hello World配置文件一样,在配置文件中声明Bean定义也就是为Bean配置元数据。
二、由IOC容器进行解析元数据: IOC容器的Bean Reader读取并解析配置文件,根据定义生成BeanDefinition配置元数据对象,IOC容器根据BeanDefinition进行实例化、配置及组装Bean。
三、实例化IOC容器:由客户端实例化容器,获取需要的Bean。
整个过程是不是很简单,执行过程如下,其实IOC容器很容易使用,主要是如何进行Bean定义。下一章我们详细介绍定义Bean。
3.3 小结
除了测试程序的代码外,也就是程序入口,所有代码都没有出现Spring任何组件,而且所有我们写的代码没有实现框架拥有的接口,因而能非常容易的替换掉Spring,是不是非入侵。
客户端代码完全面向接口编程,无需知道实现类,可以通过修改配置文件来更换接口实现,客户端代码不需要任何修改。是不是低耦合。
如果在开发初期没有真正的实现,我们可以模拟一个实现来测试,不耦合代码,是不是很方便测试。
Bean之间几乎没有依赖关系,是不是很容易重用。