Spring 容器
在 Spring IOC 容器读取 Bean 配置创建 Bean 实例之前, 必须对它进行实例化. 只有在容器实例化后, 才可以从 IOC 容器里获取 Bean 实例并使用.
Spring 提供了两种类型的 IOC 容器实现.
BeanFactory: IOC 容器的基本实现.
ApplicationContext: 提供了更多的高级特性. 是 BeanFactory 的子接口.
BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;
ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合都直接使用 ApplicationContext 而非底层的 BeanFactory
无论使用何种方式, 配置文件时相同的.
ApplicationContext 的主要实现类:
ClassPathXmlApplicationContext:从 类路径下加载配置文件
FileSystemXmlApplicationContext: 从文件系统中加载配置文件
ConfigurableApplicationContext 扩展于 ApplicationContext,新增加两个主要方法:refresh() 和 close(), 让 ApplicationContext 具有启动、刷新和关闭上下文的能力。(后面的博文会提到Bean的声明周期方法的时候会详细介绍)
ApplicationContext 在初始化上下文时就实例化所有单例的 Bean。(顺便提一下,是Bean可以不是单实例的,这个等到Bean的作用域大的时候再说)
WebApplicationContext 是专门为 WEB 应用而准备的,它允许从相对于 WEB 根目录的路径中完成初始化工作
我们之前用的ClassPathXmlApplicationContext对象的getBean方法其实源自接口BeanFactory。
在 Spring 的 IOC 容器里配置 Bean(在applicationContext.xml中配置)
<bean id="BKBeanId" class="com.happBKs.spring.iocaop.beans.BKBean"> <property name="name" value="defaultName"></property> </bean>
id是Bean 的名称,之前我在博文中已经介绍过,这里我们对其细节归纳为以下三点:
1. 在 IOC 容器中必须是唯一的
2. 若 id 没有指定,Spring 自动将权限定性类名作为 Bean 的名字
3. id 可以指定多个名字,名字之间可用逗号、分号、或空格分隔
使用Spring IOC容器的方法如下:
@Test public void test2() { //创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。实现类从类路径下加载配置文件。 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); //从IOC容器中获取Bean实例 BKBean dfBean=(BKBean)ctx.getBean("BKBeanId"); //调用对象方法 dfBean.doJob(); }
这个BeanFactory的getBean方法有多个重载方法,我们还可以利用Bean的类来进行getBean
@Test public void test5() { //创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); //从IOC容器中获取Bean实例 BKBean dfBean=(BKBean)ctx.getBean(BKBean.class); //调用对象方法 dfBean.doJob(); }
但是这里运行的时候报错了!!
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.happBKs.spring.iocaop.beans.BKBean] is defined: expected single matching bean but found 4: BKBeanId,BKBeanId_BK1,BKBeanId_BK2,BKBeanId_BK3 at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:365) at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:968) at com.happBKs.spring.iocaop.TestApp1.test5(TestApp1.java:65) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) at org.junit.runners.ParentRunner.run(ParentRunner.java:300) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
原因是我们的容器中,即applicationContext.xml中存在多个Bean的class制定的类是相同的。
<?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="BKBeanId" class="com.happBKs.spring.iocaop.beans.BKBean"> <property name="name" value="defaultName"></property> </bean> <bean id="BKBeanId_BK1" class="com.happBKs.spring.iocaop.beans.BKBean"> <property name="name" value="BK1"></property> </bean> <bean id="BKBeanId_BK2" class="com.happBKs.spring.iocaop.beans.BKBean"> <property name="name" value="BK2"></property> </bean> <bean id="BKBeanId_BK3" class="com.happBKs.spring.iocaop.beans.BKBean"> <property name="name" value="BK3"></property> </bean> </beans>
因此,getBean无法知道应该加载那个bean的对象。所以我们需要其他相同类的bean去除。
注意:当applicationContext.xml中有多个相同类型的bean时,请不要使用class的这种getBean的方法,而是使用id来getBean。即利用类型返回IOC容器中的bean,但要求IOC容器中只有一个该类型的bean。
依赖注入的方式
Spring 支持 3 种依赖注入的方式
属性注入
构造器注入
工厂方法注入(很少使用,不推荐)
属性注入
属性注入即通过 setter 方法注入Bean 的属性值或依赖的对象
属性注入使用 <property> 元素, 使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值
属性注入是实际应用中最常用的注入方式
这种方式,实际我们之前的例已经介绍过了,这里不再累述。
构造方法注入
通过构造方法注入Bean 的属性值或依赖的对象,它保证了 Bean 实例在实例化后就可以使用。
构造器注入在 <constructor-arg> 元素里声明属性
我们现在先构造一个Car的bean类
package com.happBKs.spring.iocaop.beans; public class Car { String name; double price; public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public Car() { super(); } public Car(String name1, double price1) { super(); this.name = name1; this.price = price1; } @Override public String toString() { return "Car [name=" + name + ", price=" + price + "]"; } }
这里有三种方法,来实现构造器注入的bean声明:
<?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="car_id" class="com.happBKs.spring.iocaop.beans.Car"> <constructor-arg value="BM"></constructor-arg> <constructor-arg value="500000"></constructor-arg> </bean> <bean id="car_Index_id" class="com.happBKs.spring.iocaop.beans.Car"> <constructor-arg value="400000" index="1"></constructor-arg> <constructor-arg value="BM" index="0"></constructor-arg> </bean> <bean id="car_name_id" class="com.happBKs.spring.iocaop.beans.Car"> <constructor-arg name="price1" value="300000"></constructor-arg> <constructor-arg name="name1" value="BM"></constructor-arg> </bean> </beans>
第一种方式:利用构造器的参数顺序,依次排列各个construcctor-arg
第二种方式:用index属性指定构造器参数的顺序,从0开始
第三种方式:构造器参数的名字来指定是哪一个参数赋值
测试方法:
@Test public void test6_1() { //创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); //从IOC容器中获取Bean实例 Car cBean=(Car)ctx.getBean("car_id"); //调用对象方法 System.out.println(cBean); } @Test public void test6_2() { //创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); //从IOC容器中获取Bean实例 Car cBean=(Car)ctx.getBean("car_Index_id"); //调用对象方法 System.out.println(cBean); } @Test public void test6_3() { //创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); //从IOC容器中获取Bean实例 Car cBean=(Car)ctx.getBean("car_name_id"); //调用对象方法 System.out.println(cBean); }
运行结果1:
Car [name=BM, price=500000.0]
运行结果2:
Car [name=BM, price=400000.0]
运行结果3:
Car [name=BM, price=300000.0]
注意:方式三的constructor-arg的name属性必须与bean的构造方法的形参名称一样,而不是和bean的属性名一样,需要特别注意!
构造方法的重载问题
好,现在我们还说明一个问题。请看这样一个例子。现在有一个bean, Car2的定义如下:
package com.happBKs.spring.iocaop.beans; public class Car2 { String name; double price; int version; double speed; public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public double getSpeed() { return speed; } public void setSpeed(double speed) { this.speed = speed; } public Car2() { super(); } public Car2(String name, double price, int version) { super(); this.name = name; this.price = price; this.version = version; } public Car2(String name, double price, double speed) { super(); this.name = name; this.price = price; this.speed = speed; } public Car2(String name, double price, int version, double speed) { super(); this.name = name; this.price = price; this.version = version; this.speed = speed; } @Override public String toString() { return "Car2 [name=" + name + ", price=" + price + ", version=" + version + ", speed=" + speed + "]"; } }
我们的容器配制如下:applicationContext.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="car2_id" class="com.happBKs.spring.iocaop.beans.Car2"> <constructor-arg value="BM" index="0"></constructor-arg> <constructor-arg value="500000" index="1"></constructor-arg> <constructor-arg value="1" index="2"></constructor-arg> </bean> </beans>
现在我们尝试调用测试:使用赋予参数name、price、version的构造器:
@Test public void test7() { //创建spring IOC容器对象。ClassPathXmlApplicationContext表示配置文件在类路径下,它是接口ApplicationContext的一个实现类。 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); //从IOC容器中获取Bean实例 Car2 c2Bean=(Car2)ctx.getBean("car2_id"); //调用对象方法 System.out.println(c2Bean); }
但是运行结果却是:
Car2 [name=BM, price=500000.0, version=0, speed=1.0]
getBean返回的使容器中由构造器public Car2(String name, double price, double speed)返回的对象。这就是构造器重载可能带来的问题。
解决方法是:
方法1:利用constructor-arg标签中的type属性,指定参数类型。
将刚才的容器配制改为:
<bean id="car2_id" class="com.happBKs.spring.iocaop.beans.Car2"> <constructor-arg value="BM" index="0"></constructor-arg> <constructor-arg value="500000" index="1"></constructor-arg> <constructor-arg value="1" index="2" type="int"></constructor-arg> </bean>
在运行test7测试方法:
Car2 [name=BM, price=500000.0, version=1, speed=0.0]
这样就OK了。
方法2:利用constructor-arg标签中的name属性,指定参数名称。
<bean id="car2_id" class="com.happBKs.spring.iocaop.beans.Car2"> <constructor-arg value="BM" index="0"></constructor-arg> <constructor-arg value="500000" index="1"></constructor-arg> <constructor-arg value="1" index="2" name="version"></constructor-arg> </bean>
在运行test7测试方法:
Car2 [name=BM, price=500000.0, version=1, speed=0.0]
大家记住spring的容器对应于applicationContext.xml配制文件,容器构造对象时在加载该配制文件的时候,而不是实际getBean的时候。