1.Bean的基本概念
IoC管理的应用程序对象叫做Bean, Bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。在Spring中由BeanDefinition代表,配置元数据指定如何实例化Bean、如何组装Bean等。
2.Spring IoC管理Java Bean
Spring 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。
Spring IoC容器目的就是管理Bean,这些Bean将根据配置文件中的Bean定义进行创建,而Bean定义在容器内部由BeanDefinition对象表示,该定义主要包含以下信息:
1.全限定类名(FQN):用于定义Bean的实现类;
2.Bean行为定义:这些定义了Bean在容器中的行为;包括作用域(单例、原型创建)、是否惰性初始化及生命周期等;
3.Bean创建方式定义:说明是通过构造器还是工厂方法创建Bean;
4.Bean之间关系定义:即对其他bean的引用,也就是依赖关系定义,这些引用bean也可以称之为同事bean 或依赖bean,也就是依赖注入;
Bean定义只有“全限定类名”在当使用构造器或静态工厂方法进行实例化bean时是必须的,其他都是可选的定义。难道Spring只能通过配置方式来创建Bean吗?回答当然不是,某些SingletonBeanRegistry接口实现类实现也允许将那些非BeanFactory创建的、已有的用户对象注册到容器中,这些对象必须是共享的,比如使用DefaultListableBeanFactory 的registerSingleton() 方法。不过建议采用元数据定义。
IoC容器工作方式:
1.准备配置文件:在配置文件中声明Bean定义也就是为Bean配置元数据。
2.由IoC容器进行解析元数据: IoC容器的Bean Reader读取并解析配置文件,根据定义生成BeanDefinition配置元数据对象,IoC容器根据BeanDefinition进行实例化、配置及组装Bean。
3.实例化IoC容器:由客户端实例化容器,获取需要的Bean。
3.Bean的命名
下面来看看实例:注:这里用的spring-framework-4.3.7.RELEASE,jar包如下:
spring-beans-4.3.7.RELEASE.jar
spring-core-4.3.7.RELEASE.jar
spring-context-4.3.7.RELEASE.jar
spring-expression-4.3.7.RELEASE.jar
commons-logging-1.2.jar
log4j-1.2.17.jar
junit-4.10.jar(用于单元测试)
BeanApi
package com.chensan.spring.chapter1;
public interface BeanApi {
public void sayHello();
}
BeanImpl1
package com.chensan.spring.chapter1;
public class BeanImpl1 implements BeanApi {
//@Override
public void sayHello() {
System.out.println("Welcome to Spring World!");
}
}
1.全限定名方式
不指定id,只配置必须的全限定类名,由IoC容器为其生成一个标识,客户端必须通过接口“T getBean(Class<T> requiredType)”获取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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 不指定id,只配置必须的全限定类名,由IoC容器为其生成一个标识,
客户端必须通过接口“T getBean(Class<T> requiredType)”获取Bean;
-->
<bean class="com.chensan.spring.chapter1.BeanImpl1"/>
</beans>
TestBeanApi1_1
package com.chensan.spring.chapter1.test;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBeanApi1_1 {
@Test
public void testHelloSpringWorld(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean1_1.xml");
BeanApi beanApi = beanFactory.getBean(BeanApi.class);
beanApi.sayHello();
}
}
2.只指定id
id必须在IoC容器中唯一
bean1_2.xml(省略篇幅,xml文件格式不再给出)
<!-- 指定id,必须在IoC容器中唯一 -->
<bean id="bean1_2" class="com.chensan.spring.chapter1.BeanImpl1"/>
TestBeanApi1_2
package com.chensan.spring.chapter1.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBeanApi1_2 {
@Test
public void testHelloSpringWorld(){
//1. 读取配置文件实例化一个IoC容器
ApplicationContext context = new ClassPathXmlApplicationContext("bean1_2.xml");
//2. 从容器中获取Bean,注意此处完全“面向接口编程,而不是面向实现”
BeanApi beanApi = context.getBean("bean1_2", BeanApi.class);
//3. 执行业务逻辑
beanApi.sayHello();
}
}
3.指定一个name,不指定id
name是标识符,必须在Ioc容器中唯一;
bean1_3
<!-- 指定name,这样name就是“标识符”,必须在Ioc容器中唯一; -->
<bean name="bean1_3" class="com.chensan.spring.chapter1.BeanImpl1"/>
TestBeanApi1_3
package com.chensan.spring.chapter1.test;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBeanApi1_3 {
@Test
public void testHelloSpringWorld(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean1_3.xml");
BeanApi beanApi = beanFactory.getBean("bean1_3", BeanApi.class);
beanApi.sayHello();
}
}
4.同时指定id和name,id和name不同名
id是标识符,而name是别名,必须在Ioc容器中唯一;
bean1_4
<!--
指定id和name,id就是标识符,而name就是别名,必须在Ioc容器中唯一;
-->
<bean id="bean1_4_1" name="bean1_4_2" class="com.chensan.spring.chapter1.BeanImpl1"/>
TestBeanApi1_4
package com.chensan.spring.chapter1.test;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBeanApi1_4 {
@Test
public void testHelloSpringWorld(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean1_4.xml");
BeanApi beanApi1 = beanFactory.getBean("bean1_4_1", BeanApi.class);
beanApi1.sayHello();
BeanApi beanApi2 = beanFactory.getBean("bean1_4_2", BeanApi.class);
beanApi2.sayHello();
String[] bean4Alias = beanFactory.getAliases("bean1_4_2");
Assert.assertEquals(1, bean4Alias.length);
}
}
5.同时指定id和name,id和name同名
bean1_5
<!--
指定id和name,id就是标识符,而name就是别名,必须在Ioc容器中唯一;
如果id和name一样,IoC容器能检测到,并消除冲突;
-->
<bean id="bean1_5" name="bean1_5" class="com.chensan.spring.chapter1.BeanImpl1"/>
TestBeanApi1_5
package com.chensan.spring.chapter1.test;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBeanApi1_5 {
@Test
public void testHelloSpringWorld(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean1_5.xml");
BeanApi beanApi = beanFactory.getBean("bean1_5", BeanApi.class);
beanApi.sayHello();
String[] bean5Alias = beanFactory.getAliases("bean1_5");
Assert.assertEquals(0, bean5Alias.length);//bean1_5别名获取数组长度为0,bean1_4别名获取数组长度为1;说明:别名不能和id一样,如果一样则由IoC容器负责消除冲突;
}
}
6.指定多个name,不指定id
指定多个name,第一个name后用“;”,后面的name用“,”隔开第一个被用作标识符,其他的为别名;
bean1_6.xml
<!-- 指定多个name,第一个name后用“;”,后面的name用“,”隔开第一个被用作标识符,其他的为别名; -->
<bean name="bean1_6;alias1_6_1,alias1_6_2,alias1_6_3,alias1_6_4"
class="com.chensan.spring.chapter1.BeanImpl1"/>
TestBeanApi1_6
package com.chensan.spring.chapter1.test;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBeanApi1_6 {
@Test
public void testHelloSpringWorld(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean1_6.xml");
//根据id获取bean
BeanApi beanApi = beanFactory.getBean("bean1_6", BeanApi.class);
beanApi.sayHello();
//根据别名获取bean
BeanApi alias161 = beanFactory.getBean("alias1_6_1", BeanApi.class);//alias51, alias52, alias53都是别名
alias161.sayHello();
String[] aliases = beanFactory.getAliases("bean1_6");
Assert.assertEquals(4, aliases.length);
for(String alias : aliases){
System.out.println(alias);
}
}
}
7.指定多个name,同时指定id
指定多个name,第一个name后用“;”,后面的name用“,”隔开第一个被用作标识符,其他的为别名;当指定id时,name指定的标识符全部为别名;
bean1_7.xml
<!-- 指定多个name,第一个name后用“;”,后面的name用“,”隔开第一个被用作标识符,其他的为别名;
当指定id时,name指定的标识符全部为别名;
-->
<bean id="bean1_7" name="bean1_7_1;alias1_7_1,alias1_7_2,alias1_7_3,alias1_7_4"
class="com.chensan.spring.chapter1.BeanImpl1"/>
TestBeanApi1_7
package com.chensan.spring.chapter1.test;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBeanApi1_7 {
@Test
public void testHelloSpringWorld(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean1_7.xml");
//根据id获取bean
BeanApi beanApi = beanFactory.getBean("bean1_7", BeanApi.class);
beanApi.sayHello();
//根据别名获取bean
BeanApi alias171 = beanFactory.getBean("bean1_7_1", BeanApi.class);//alias51, alias52, alias53都是别名
alias171.sayHello();
String[] aliases = beanFactory.getAliases("bean1_7");
Assert.assertEquals(5, aliases.length);
for(String alias : aliases){
System.out.println(alias);
}
}
}
8.使用<alias>标签指定别名
别名也必须在IoC容器中唯一
bean1_8.xml
<!-- 使用<alias>标签指定别名,别名也必须在IoC容器中唯一 -->
<bean name="bean1_8_1" class="com.chensan.spring.chapter1.BeanImpl1"/>
<alias alias="alias1_8_1" name="bean1_8_1"/>
<alias alias="alias1_8_2" name="bean1_8_1"/>
TestBeanApi1_8
package com.chensan.spring.chapter1.test;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBeanApi1_8 {
@Test
public void testHelloSpringWorld(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean1_8.xml");
//根据别名获取bean
BeanApi beanApi2 = beanFactory.getBean("bean1_8_1", BeanApi.class);
beanApi2.sayHello();
String[] aliases = beanFactory.getAliases("bean1_8_1");
Assert.assertEquals(2, aliases.length);
for(String alias : aliases){
System.out.println(alias);
}
}
}
4.实例化Bean
传统应用程序可以通过new和反射方式进行实例化Bean。而Spring IoC容器则需要根据Bean定义里的配置元数据使用反射机制来创建Bean。在Spring IoC容器中根据Bean定义创建Bean主要有以下几种方式:
1.构造器实例化Bean
构造器实例化Bean是最简单的方式,Spring IoC容器既能使用默认空构造器也能使用有参数构造器两种方式创建Bean,如以下方式指定要创建的Bean类型:
使用空构造器进行定义,使用此种方式,class属性指定的类必须有空构造器。
BeanImpl2
package com.chensan.spring.chapter2;
import com.chensan.spring.chapter1.BeanApi;
public class BeanImpl2 implements BeanApi {
private String message;
public BeanImpl2(){
this.message = "Hello Spring!";
}
public BeanImpl2(String message){
this.message = message;
}
@Override
public void sayHello() {
System.out.println(message);
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
bean2_1.xml
<!-- 使用默认构造参数 -->
<bean name="bean2_1_1" class="com.chensan.spring.chapter2.BeanImpl2"/>
<!-- 使用有参构造参数 -->
<bean name="bean2_1_2" class="com.chensan.spring.chapter2.BeanImpl2">
<!-- 指定构造器参数 -->
<constructor-arg index="0" value="Spring Constructor BeanFactory"/>
</bean>
TestBean2_1
package com.chensan.spring.chapter2.test;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBean2_1 {
//构造器实例化Bean
@Test
public void instantiatingBeanByConstructor(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean2_1.xml");
BeanApi beanApi1 = beanFactory.getBean("bean2_1_1", BeanApi.class);
beanApi1.sayHello();
BeanApi beanApi2 = beanFactory.getBean("bean2_1_2", BeanApi.class);
beanApi2.sayHello();
}
}
注意:看起来bean2_1_1与之前Bean的命名是一样的,区别在于BeanImpl2中的sayHello方法中的参数是从构造器中传入的,所以需要指定对应参数个数的构造器。
2.静态工厂方式实例化Bean
使用静态工厂方式除了指定必须的class属性,还要指定factory-method属性来指定实例化Bean的方法,而且使用静态工厂方法也允许指定方法参数,spring IoC容器将调用此属性指定的方法来获取Bean
BeanApiStaticFactory
package com.chensan.spring.chapter2;
import com.chensan.spring.chapter1.BeanApi;
public class BeanApiStaticFactory {
//静态工厂方法
public static BeanApi newInstance(String message){
return new BeanImpl2(message);
}
}
bean2_2.xml
<!-- 使用静态工厂方法 -->
<bean name="bean2_2" class="com.chensan.spring.chapter2.BeanApiStaticFactory"
factory-method="newInstance">
<!-- 指定构造器参数 -->
<constructor-arg index="0" value="Spring Static BeanFactory"/>
</bean>
TestBean2_2
package com.chensan.spring.chapter2.test;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBean2_2 {
//使用静态工厂方法
@Test
public void instantiatingBeanByStaticFactory(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean2_2.xml");
BeanApi beanApi1 = beanFactory.getBean("bean2_2", BeanApi.class);
beanApi1.sayHello();
}
}
3.实例工厂方法实例化Bean
使用实例化工厂方式不能指定class属性,此时必须使用factory-bean属性来指定工厂Bean,factory-method属性指定实例化Bean的方法,而且使用实例工厂方法允许指定方法参数,方式和使用构造器方式一样
BeanApiInstanceFactory
package com.chensan.spring.chapter2;
import com.chensan.spring.chapter1.BeanApi;
public class BeanApiInstanceFactory {
//实例化工厂方法
public BeanApi newInstance(String message){
return new BeanImpl2(message);
}
}
bean2_3.xml
<!-- 定义实例化工厂Bean -->
<bean id="bean2_3_1" class="com.chensan.spring.chapter2.BeanApiInstanceFactory"/>
<!-- 使用实例化工厂Bean实例化Bean -->
<bean id="bean2_3_2" factory-bean="bean2_3_1" factory-method="newInstance">
<!-- 指定构造器参数 -->
<constructor-arg index="0" value="Spring Instance BeanFactory"/>
</bean>
TestBean2_3
package com.chensan.spring.chapter2.test;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chensan.spring.chapter1.BeanApi;
public class TestBean2_3 {
//使用实例化工厂方法
@Test
public void instantiatingBeanByInstanceFactory(){
BeanFactory beanFactory = new ClassPathXmlApplicationContext("bean2_3.xml");
BeanApi beanApi2 = beanFactory.getBean("bean2_3_2", BeanApi.class);
beanApi2.sayHello();
}
}
这三种方式只是配置不一样,从获取方式看完全一样,没有任何不同。这也是Spring IoC的魅力,Spring IoC帮你创建Bean,我们只管使用就可以。
参考:http://jinnianshilongnian.iteye.com/blog/1413857