Spring IoC容器详解(一)

Spring IoC容器和beans的简介

org.springframework.beansorg.springframework.context两个包是实现spring IoC功能最基础的两个包,BeanFactory接口提供一种能够管理任何类型对象的高级配置机制.

ApplicationContext是BeanFactory的子接口.ApplicationContext增加与Spring AOP功能的集成;消息处理,事件发布;以及特定应用层相关的Context比如用于web应用中的WebApplicationContext

容器概述

org.springframework.context.ApplicationContext接口代表了Spring IoC 容器,负责实例化,配置,组装前面所说的beans.IoC容器通过解析配置元数据获取有关要实例化,配置和组装的对象的信息.配置元数据包 含在XML文件,java注解,或者java 代码.这些元数据能够表示组成我们应用程序的对象以及这些对象之间丰富的依赖关系.

Spring提供了ApplicationContext接口的几个实现.在独立的应用程序中通常会创建一个
  
ClassPathXmlApplicationContext实例或者FileSystemXmlApplicationContext实例.然而XML文件是传统配置元数据的方式,我们可以通过在XML文件中通过少许的配置,使得容器支持java 注解,java代码方式来配置元数据.
在大多数应用场景中,用户不需要显式的实例化一个或者多个Spring IoC容器.比如在,在一个web应用中,在web.xml文件只需8行配置就可以了.

下图从更抽象的角度来说明了Spring 是如何工作的.ApplicationContext实例化后我们应用程序里的classes与配置元数据就组合在一起,我们就得到一个配置完整且可执行的系统或应用程序.
在这里插入图片描述

配置元数据

配置元数据代表了应用开发者如何告诉spring 容器实例化,配置,组装应用里的对象.配置元数据可以通过传统的XML,也可以通过基于java注解(spring2.5开始),或基于java代码(spring3.0开始).
bean对应的实际对象组成了我们的应用程序.我们通常会定义业务逻辑层,dao,持久化对象等,但是不会定义更细粒度领域对象在容器中,因为定义加载领域对象是DAO和业务逻辑层的职责.然而,你可以使用Spring集成的 AspectJ去配置一个已经生成的但不受Spring IoC容器控制的对象

容器的初始化

初始化一个容器很简单.只要给ApplicationContext的构造函数传入元数据配置文件的路径即可.元数据配置文件可以是系统本地文件,也可以是java classpath的文件,还可以是其他的.

ApplicationContext context = new ClassPathXmlApplicationContext("service.xml","daos.xml")

使用容器

ApplicationContext接口是一个能够管理注册在其上面的beans和这些beans依赖关系的高级factory.使用方法T getBean(String name, Class requiredType)就可以获取你想要的bean的实例.ApplicationContext允许你通过下面的方式去获取bean和使用:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

最灵活的使用的方式是:GenericApplicationContext结合reader 代理. 比如结合XmlBeanDefinitionReader读取xml

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

你可以使用getBean去获取bean实例.ApplicationContext还提供了其获取bean实例的方法,理想情况下你的应用程序就不该使用它们.事实上,你的应用程序代码根本不应该调用该 getBean()方法,因此完全不依赖于Spring API.

Bean概述

一个Spring IoC容器管理一个或者多个Beans.这些bean通过你提供给容器的配置元数据(configuration metadata )创建.比如,以XML 定义的形式.
在容器本身中,这些bean定义使用BeanDefinition对象来表示,其中主要包含(还有其他信息)以下信息:

包限定类名称:通常是所定义的bean的实际实现类
Bean的行为状态 ,说明它在容器的行为状态(作用域,生命周期回调等等)
Bean的依赖关系
在新创建的对象中设置的其他配置设置,例如,用于管理连接池的Bean的连接数量或池的大小限制。
这些信息转换成类属性就就组成了bean定义.
下图是bean定义
在这里插入图片描述

命名Beans

每个bean有一个或多个标识.这些标识在容器中必须是唯一的.一个bean通常只有一个标识,如果需要更多的标识,那么其他的标识就可以当作别名.
在xml配置文件中,可以使用id和name属性标识一个bean.id属性只能设置一个.如果想要给bean取其他别名,可以通过name属性,名字之间可以通过逗号,分号, 空格进行分割.
name 和id不是必须提供的.如果没有提供name或者id,容器会生成一个唯一标识给这个bean.如果想通过name来引用一个bean,你必须提供一个name.如果不提供name,可以通过使用inner bean和autowiring collaborators

实例化Beans

bean定义本质上是创建一个或多个对象的配方.当请求获取bean时,容器就会查找bean的配方,然后用bean定义封装的元数据去创建一个实际对象.
如果你使用基于XML的元数据配置,你需要在中指定实例化对象对应的class.class属性是必须的,它是BeanDefinition实例的一个类属性.class属性可以通过以下两种方式之一来使用:

容器通过反射的方式调用其构造函数创建bean,这与java代码使用new创建一个对象是一样的.
通过指定创建class对象的静态工厂方法,容器调用这个类的静态工厂方法来创建bean.通过静态工厂返回的对象可能是同一个类也可能不是.

通过构造函数实例化

当通过构造函数创建一个bean的时候,所有普通的类都可以被Spring使用和兼容.这样正在开发的类不需要实现特定的接口或者使用特殊的方式编写.只需要指定bean对应的class即可.根据bean使用的IoC类型,需要提供一个默认的无参构造函数.
 Spring IoC容器可以管理任何你想让容器管理的对象.不仅仅限于管理真正JavaBeans.大多数Spring用户,仅使用只有默认构造函数(无参)和setter,getter属性方法的JavaBeans.你可以把更多非JavaBeans类型的类放到容器中去.

通过静态工厂方法实例化

当你定义一个使用静态工厂方法来创建的bean时,你在的class属性指定这个带静态工厂方法的类,同时在factory-method指定这个类的静态工厂方法名字.您应该能够调用此方法(使用后面介绍的可选参数)并返回一个活动对象,这个对象就像使用通过构造函数创建的的一样.这种bean实例化方法可以用于那些使用静态工厂创建对象的老代码中.

<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}

public static ClientService createInstance() {
    return clientService;
}
}
通过实例工厂方法实例化

与通过静态工厂方法实例化相似,通过’实例工厂方法’实例化调用一个已存在bean的非静态方法创建一个新的bean.使用这种方式,class属性需要留空,factory-bean属性设置当前容器已存在的bean,factory-method设置为factory-bean中用来创建对象的方法.

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {

private static ClientService clientService = new ClientServiceImpl();

public ClientService createClientServiceInstance() {
    return clientService;
}
   }

一个工厂类可以包含多个工厂方法

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>

<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {

private static ClientService clientService = new ClientServiceImpl();

private static AccountService accountService = new AccountServiceImpl();

public ClientService createClientServiceInstance() {
    return clientService;
}

public AccountService createAccountServiceInstance() {
    return accountService;
}
}

这种方法表明,factory bean本身可以通过依赖注入(DI)进行管理和配置.在Spring文档中,factory bean指的是在Spring容器中配置的bean,它将通过实例或 静态工厂方法创建对象 。相反,FactoryBean(注意大写字母)是指特定于Spring的 FactoryBean 。

依赖注入

依赖注入(DI)是对象获取它依赖的过程.也就说,其他和它一起工作的对象通过构造函数参数,工厂方法参数,工厂方法实例化或构造函数实例后设置属性这些方式注入.容器创建bean的时候会注入它的依赖,这个过程跟前面对象获取它依赖的方式相反,因此也叫控制反转(IoC),bean它本身控制依赖的实例或定位是通过这些类的直接构造或服务定位器模式.依赖注入(DI)与控制反转(IoC)表达的都是对象获取它依赖的方法.通过注入的方式获取依赖叫做依赖注入,通过注入的方式获取依赖与通常获取依赖的方式相反,所以叫控制反转

通过DI代码更加清晰,并且在对象提供依赖关系时解耦更有效.该对象不查找其依赖项,并且不知道依赖项的位置或类.这样,你的类测试更加容易,特别是当依赖的是一个接口或抽象基类时,它们允许在单元测试中使用存根或模拟实现

基于构造函数的依赖注入

基于构造函数的依赖注入的实现通过调用包含多个参数的构造函数实现的,每个参数就是一个依赖.与通过调用包含特殊参数的静态工厂方法创建bean是等价的.下面的例子展示了通过构造函数实现依赖注入.注意到例子中的类没有什么特别之处,它是一个不依赖容器任何接口,基类或者注解的POJO.

public class SimpleMovieLister {

// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;

// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

// business logic that actually uses the injected MovieFinder is omitted...
}
构造函数参数解析

构造函数参数解析发生在使用这些参数的类型的时候.如果bean定义的构造函数参数中没有歧义,那么在bean定义中定义的构造函数参数的顺序就是在实例化bean时将这些参数提供给相应构造函数的顺序.思考下面的类:

package x.y;

public class Foo {

public Foo(Bar bar, Baz baz) {
    // ...
}
}

如果没有歧义存在,假设Bar和Baz与继承无关.那么下面的配置文件就没有问题,你也不需要在描述构造函数参数的索引及类型

<beans>
<bean id="foo" class="x.y.Foo">
    <constructor-arg ref="bar"/>
    <constructor-arg ref="baz"/>
</bean>

<bean id="bar" class="x.y.Bar"/>

<bean id="baz" class="x.y.Baz"/>
</beans>

当引用其他bean时,类型是知道的,解析时候可以匹配上(像前面的例子一样).当使用基本数据类型时,比如true,Spring不能决定这个类型的值,因此根据类型就不能匹配上.思考下面的类:

package examples;

public class ExampleBean {

// Number of years to calculate the Ultimate Answer
private int years;

// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;

public ExampleBean(int years, String ultimateAnswer) {
    this.years = years;
    this.ultimateAnswer = ultimateAnswer;
}
}
构造函数参数类型匹配

在前面的场景中,如果使用type属性明确指定构造函数参数类型,那么容器的参数类型匹配也可以用于基本参数类型,比如:

<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
构造函数参数索引

可以使用index属性明确描述构造函数参数的的索引.比如:

<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
构造函数参数名称

你还可以使用构造函数参数名称去标识value以消除歧义.比如

<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
基于Setter的依赖注入

基于Setter的注入实现方式是:容器通过调用无参的构造函数或者静态工厂方法实例化bean后,然后调用bean的setter方法把依赖注入进来.
下面的例子展示了一个类用纯setter方法实现依赖注入.这个类是传统的java类,不依赖容器任何接口,基类或者注解的POJO.

public class SimpleMovieLister {

// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;

// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}

// business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext管理的bean支持基于构造函数的方式注入和基于setter方法的注入.它同样支持基于构造函数注入部分bean之后,再使用setter依赖注入其他依赖.你可以配置依赖通过BeanDefinition的形式.然而,大多数Spring的用户都不会直接去使用这些类而是通过xml bean定义,组件注解(比如@Component,@Controller),使用@Configuration注解的java类里的@Bean方法.然后将这些源内部转换为实例BeanDefinition并用于加载整个Spring IoC容器实例。

基于构造函数注入还是setter方法注入

条比较好经验法则是:必须的依赖通过构造函数注入,可选的依赖通过setter方法注入.请注意,可以使用 setter方法上的@Required注释来使该属性成为必需的依赖项.主张使用构造函数的方式注入,使得人们可以将应用程序组件实现为不可变对象,并确保所需的依赖关系不是null.此外通过构造函数注入的组件,它返回给调用方的是一个已经初始化好的组件.从另一个角度来说,包含大量参数的构造函数有一种烂代码的味道,说明了这个类承担了太多的责任应该拆分开来.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值