Spring中的bean
1 Bean的配置
Spring容器支持XML和Properties两种格式的配置文件,在实际开发中,最常使用的就是XML格式的配置方式
在Spring中,XML配置文件的根元素是beans, beans中包含了多个bean子元素,每一个bean子元素定义了一个Bean,并描述了该Bean如何被装配到Spring容器中
< bean>元素的常用属性及其子元素
属性或子元素名称 | 描述 |
---|---|
id | 是一个Bean的唯一标识符, Spring容器对Bean的配置、管理都通过该属性来完成 |
name | Spring容器同样可以通过此属性对容器中的Bean进行配置和管理,name属性中可以为指定多个名称,每个名称之间用逗号或分号隔开 |
class | 该属性指定了Bean的具体实现类,它必须是一个完整的类名,使用类的全限定名 |
scope | 用来设定Bean实例的作用域,其属性值有: singleton(单例)、 prototype(原型)、 request、session、 global Session、 application和 websocket。其默认值为 singletonbean |
constructor-arg | < bean>元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index属性指constructor-arg「定构造参数的序号(从0开始),ype属性指定构造参数的类型,参数值可以通过ref属性或 value属性直接指定,也可以通过ref或 value子元素指定 |
property | < bean>元素的子元素,用于调用Bean实例中的 setter方法完成属性赋值,从而完成依赖property注入。该元素的name属性指定Bean实例中的相应属性名,ref属性或 value属性用于指定参数值 |
ref | < property>、< constructor-arg>等元素的属性或子元素,可以用于指定对Bean工厂中某个Bean实例的引用 |
value | < property>、< constructor-arg>等元素的属性或子元素,可以用于直接指定一个常量值 |
list | 用于封装List或数组类型的依赖注入 |
set | 用于封装Set类型属性的依赖注入 |
map | 用于封装Map类型属性的依赖注入 |
entry | < map>元素的子元素,用于设置一个键值对。其key属性指定字符串类型的键值,ref或entryvalue子元素指定其值,也可以通过 value-ref或 value属性指定其值 |
1. 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-4.3.xsd">
<!-- 使用id来配置bean -->
<bean id="userDao" class="com.clarence.ioc.UserDaoImpl"></bean>
<!-- 使用name配置bean -->
<bean name="userService" class="com.clarence.ioc.UserServiceImpl"/>
//初级情况下,id和name的作用基本一致
//如果在Bean中未指定id和name,则Spring会将class值当作id使用。
</beans>
2 Bean的实例化
实例化Bean有三种方式,分别为构造器实例化、静态工厂方式实例化和实例工厂方式实例化(其中最常用的是构造器实例化)
2.1 构造器实例化
构造器实例化是指Spring容器通过Bean对应类中默认的无参构造方法来实例化Bean
1. Bean1.java
public class Bean1 {
}
2. xml配置
<bean id="bean1" class="com.clarence.instance.constructor.Bean1"/>
实例化结果
2.2 静态工厂实例化
该方式要求开发者创建一个静态工厂的方法来创建Bean的实例,其Bean配置中的class属性所指定的不再是Bean实例的实现类,而是静态工厂类,同时还需要使用factory-method属性来指定所创建的静态工厂方法
1. xml文件配置
<bean id="bean2" class="com.clarence.instance.static_factory.MyBean2Factory"
factory-method="createBean"/>
//由于这种方式配置Bean后,Spring容器不知道哪个是所需要的工厂方法,
//所以增加了factory-method属性来告诉Spring容器,其方法名称为createBean
2. 静态工厂
public class MyBean2Factory {
//使用自己的工厂创建Bean2实例
public static Bean2 createBean() {
return new Bean2();
}
}
实例化结果
2.3 实例工厂方式实例化
此种方式的工厂类中,不再使用静态方法创建Bean实例,而是采用直接创建Bean实例的方式。同时,在配置文件中,需要实例化的Bean也不是通过class属性直接指向的实例化类,而是通过factory-bean属性指向配置的实例工厂,然后使用factory-method属性确定使用工厂中的哪个方法
1. 实例工厂
public class MyBean3Factory {
public MyBean3Factory() {
System.out.println("bean3工厂实例化中.......");
}
public Bean3 createBean() {
return new Bean3();
}
}
2. xml文件配置
<bean id="myBean3Factory" class="com.clarence.instance.factory.MyBean3Factory"/>
<bean id="bean3" factory-bean="myBean3Factory" factory-method="createBean"/>
实例化结果
2.4 三种方式的异同
构造器直接实例化Bean,静态工厂在实例化Bean不会实例化工厂类,实例工厂在实例化bean时会先实例化工厂类
实际应用中的异同有待进一步的探究
3 Bean的作用域
前文提到,由scope元素来指定作用域,Spring4.3定义了7中作用域
作用域名称 | 说明 |
---|---|
singleton(单例) | 使用 singleton 定义的Bean在 Spring容器中将只有一个实例,也就是说,无论有多少个Bean引用它,始终将指向同一个对象 。这也是 Spring容器默认的作用域 |
prototype(原型) | 每次通过 Spring容器获取的 prototype定义的Bean时,容器都将创建一个新的Bean实例 |
request | 在一次HTTP请求中,容器会返回该Bean的同一个实例。对不同的HTTP请求则会产生request个新的Bean,而且该Bean仅在当前HTTP Request内有效 |
session | 在一次HTTP Session中,容器会返回该Bean的同一个实例。对不同的HTTP请求则会产生一个新的Bean,而且该Bean仅在当前HTTP Session内有效 |
globalSession | 在一个全局的HTTPSession中,容器会返回该Bean的同一个实例。仅在使用portlet上下文时有效 |
application | 为每个 Servletcontext 对象创建一个实例。仅在Web相关的Application Context中生效 |
websocket | 为每个 websocket对象创建一个实例。仅在Web相关的 Applicationcontext 中生效 |
3.1 singleton作用域
singleton是Spring容器默认的作用域,当Bean的作用域为singleton时,Spring容器就只会存在一个共享的Bean实例,并且所有对Bean的请求,只要id与该Bean的id属性相匹配,就会返回同一个Bean实例。singleton作用域对于无会话状态的Bean(如Dao组件、Service组件)来说,是最理想的选择。
1.xml文件配置
<!-- 作用域为单例,也是默认作用域 -->
<bean id="scope" class="com.clarence.scope.Scope"
scope="singleton"/>
2.测试用例
System.out.println(applicationContext.getBean("scope"));
//两次打印对象相同
System.out.println(applicationContext.getBean("scope"));
运行结果:
说明两次返回的是同一个对象
3.2 prototype作用域
对需要保持会话状态的Bean(如Struts2的Action类)应该使用prototype作用域。在使用prototype作用域时,Spring容器会为每个对该Bean的请求都创建一个新的实例。
1.xml文件配置
<!-- 作用域为原型,每次实例化都会产生一个新的对象 -->
<bean id="scope2" class="com.clarence.scope.Scope"
scope="prototype"/>
运行结果
疑惑点:两次运行,单例返回的对象都是一样的,说明该对象实际并没有在运行结束后销毁,那什么时候销毁?
4 Bean的生命周期
Spring容器可以管理singleton作用域的Bean的生命周期,在此作用域下,Spring能够精确地知道该Bean何时被创建,何时初始化完成以及何时被销毁。
对于prototype作用域的Bean, Spring只负责创建,当容器创建了Bean实例后,Bean的实例就交给客户端代码来管理,Spring容器将不再跟踪其生命周期。
每次客户端请求prototype作用域的Bean时,Spring容器都会创建一个新的实例,并且不会管那些被配置成prototype作用域的Bean的生命周期。
在一般情况下,常会在Bean的postinitiation(初始化后)和predestruction(销毁前)执行一些相关操作
(1)根据配置情况调用Bean构造方法或工厂方法实例化Bean。
(2)利用依赖注入完成Bean中所有属性值的配置注入。
(3)如果Bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName()方法传入当前Bean的id值。
(4)如果Bean实现了BeanFactoryAware接口,则Spring调用setBeanFactory()方法传入当前工厂实例的引用。
(5)如果Bean实现了ApplicationContextAware接口,则Spring调用setApplicationContext()方法传入当前ApplicationContext实例的引用。
(6)如果BeanPostProcessor和Bean关联,则Spring将调用该接口的预初始化方法postProcessBeforeInitialzation()对Bean进行加工操作,这个非常重要,Spring的AOP就是用它实现的。
(7)如果Bean实现了InitializingBean接口,则Spring将调用afterPropertiesSet()方法。
(8)如果在配置文件中通过init-method属性指定了初始化方法,则调用该初始化方法。
(9)如果有BeanPostProcessor和Bean关联,则Spring将调用该接口的初始化方法post ProcessAfterInitialization()。此时,Bean已经可以被应用系统使用了。(10)如果在< bean> 中指定了该Bean的作用范围为scope=“singleton”,则将该Bean放入Spring IoC的缓存池中,将触发Spring对该Bean的生命周期管理;如果在< bean>中指定了该Bean的作用范围为scope=“prototype”,则将该Bean交给调用者,调用者管理该Bean的生命周期,Spring不再管理该Bean。
(11)如果Bean实现了DisposableBean接口,则Spring会调用destory()方法将Spring中的Bean销毁;如果在配置文件中通过destory-method属性指定了Bean的销毁方法,则Spring将调用该方法进行销毁
5 Bean的装配方式
Bean的装配可以理解为依赖关系注入,Bean的装配方式即Bean依赖注入的方式。Spring容器支持多种形式的Bean的装配方式,如基于XML的装配、基于注解(Annotation)的装配和自动装配等(其中最常用的是基于注解的装配)
5.1 基于XML的装配
Spring提供了两种基于XML的装配方式:设值注入(Setter Injection)和构造注入(Constructor Injection)。
在Spring实例化Bean的过程中,Spring首先会调用Bean的默认构造方法来实例化Bean对象,然后通过反射的方式调用setter方法来注入属性值。
因此,设值注入要求一个Bean必须满足以下两点要求。
- Bean类必须提供一个默认的无参构造方法。
- Bean类必须为需要注入的属性提供对应的setter方法。
使用设值注入时,在Spring配置文件中,需要使用< bean>元素的子元素< property>来为每个属性注入值;而使用构造注入时,在配置文件里,需要使用< bean>元素的子元素< constructor-arg>来定义构造方法的参数,可以使用其value属性(或子元素)来设置该参数的值。
1.xml文件配置
<!-- 使用构造注入方式装配User实例 ,调用的是有参构造器-->
<bean id="user1" class="com.clarence.assmeble.User">
<!-- 配置的是参数列表 -->
<constructor-arg index="0" value="tom"></constructor-arg>
<constructor-arg index="1" value="123456"></constructor-arg>
<constructor-arg index="2">
<list>
<value>"constructorvalue1"</value>
<value>"constructorvalue2"</value>
</list>
</constructor-arg>
</bean>
<!-- 使用设值注入方式装配User实例 -->
<bean id="user2" class="com.clarence.assmeble.User">
<!-- 配置的是每个独立的属性 -->
<property name="username" value="jack"></property>
<property name="password" value="654321"></property>
<property name="list">
<list>
<value>"constructorvalue1"</value>
<value>"constructorvalue2"</value>
</list>
</property>
</bean>
两种方式的异同:
- 构造注入需要有相匹配的构造方法,例如,有两参构造不能输入三个参数,而设值注入可以注入任意多的值,每次都是独立的注入
- 设值注入必须提供无参构造和相应的setter方法,否则注入失败
- 两者xml写法不同
- 个人更愿意使用设值注入
5.2 基于Annotation的装配
在Spring中,尽管使用XML配置文件可以实现Bean的装配工作,但如果应用中有很多Bean时,会导致XML配置文件过于臃肿,给后续的维护和升级工作带来一定的困难。为此,Spring提供了对Annotation(注解)技术的全面支持。
常用注解 | 功能 |
---|---|
@Component | 可以使用此注解描述Spring中的Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可 |
@Repository | 用于将数据访问层(DAO层)的类标识为Spring中的Bean,其功能与@Component相同 |
@Service | 通常作用在业务层(Service层),用于将业务层的类标识为Spring中的Bean,其功能与@Component相同 |
@Controller | 通常作用在控制层(如Spring MVC的Controller),用于将控制层的类标识为Spring中的Bean,其功能与@Component相同 |
@Autowired | 用于对Bean的属性变量、属性的setter方法及构造方法进行标注,配合对应的注解处理器完成Bean的自动配置工作。默认按照Bean的类型进行装配 |
@Resource | 其作用与Autowired一样。其区别在于@Autowired默认按照Bean类型装配,而@Resource默认按照Bean实例名称进行装配 |
@Qualifier | 与@Autowired注解配合使用,会将默认的按Bean类型装配修改为按Bean的实例名称装配,Bean的实例名称由@Qualifier注解的参数指定 |
注解详细解释
- @Resource中有两个重要属性:name和type。Spring将name属性解析为Bean实例名称,type属性解析为Bean实例类型。如果指定name属性,则按实例名称进行装配;如果指定type属性,则按Bean类型进行装配;如果都不指定,则先按Bean实例名称装配,如果不能匹配,再按照Bean类型进行装配;如果都无法匹配,则抛出NoSuchBeanDefinitionException异常
- 虽然@Repository、@Service与@Controller功能与@Component注解的功能相同,但为了使标注类本身用途更加清晰,建议在实际开发中使用@Repository、@Service与@Controller分别对实现类进行标注
- 注解必须写在相应语句的上一行,如:
1.
@Resource(name="userDao")
private UserDao userDao;//将userDao装配给了userDao
private UserDao userDao2;
2.
@Resource(name="userDao")
private UserDao userDao2;//将userDao装配给了userDao2
private UserDao userDao;
注解与xml的关系
注:@Repository、@Service与@Controller基本一致,只是bean所在的逻辑层不同,以此区分
注解 | xml写法 |
---|---|
@Repository(“userDao”) | < bean id="userDao"class=“com.clarence.annotation.UserDaoImpl”/> |
@Resource(name=“userDao”) | < property name=“userDao” ref=“userDao”/> |
1.xml配置
<!--
使用config新添加的约束信息
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation=
"http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd"
-->
<!-- 使用context需要添加配置信息 -->
<!-- 需要导入额外的包spring-aop -->
<!--
<context:annotation-config />
<bean id="userDao" class = "com.clarence.annotation.UserDaoImpl"/>
<bean id="userService" class = "com.clarence.annotation.UserServiceImpl"/>
<bean id="userController" class = "com.clarence.annotation.UserController"/>
-->
<!-- 对bean所在的包自动扫描,和前面配置作用一样 -->
<context:component-scan base-package="com.clarence.annotation"/>
<!--配合注解使用,减少xml文件的冗余-->
2.注解写法
@Repository("userDao")
@Service("userService")
@Controller("userController")
运行结果:
5.3 自动装配
Spring的< bean>元素中包含一个autowire属性,我们可以通过设置autowire的属性值来自动装配Bean。所谓自动装配,就是将一个Bean自动地注入到其他Bean的Property中
< bean>元素的autowire属性值及说明
属性值 | 说明 |
---|---|
default(默认值) | 由< bean>的上级标签< beans>的 default- autowire属性值确定。例如< beans default(默认值) autowire=" byName">,则该< bean>元素中的 autowire属性对应的属性值就为 byName |
byName | 根据属性的名称自动装配。容器将根据名称查找与属性完全一致的Bean,并将其属性自动装配 |
byType | 根据属性的数据类型(Type)自动装配,如果一个Bean的数据类型,兼容另一个Bean中属性的数据类型,则自动装配 |
constructor | 根据构造函数参数的数据类型,进行 bytype模式的自动装配 |
no | 在默认情况下,不使用自动装配,Bean依赖必须通过ref元素定义 |
1.xml文件配置
<bean id="userDao" class = "com.clarence.annotation.UserDaoImpl" />
<bean id="userService" class = "com.clarence.annotation.UserServiceImpl" autowire="byName"/>
<bean id="userController" class = "com.clarence.annotation.UserController" autowire="byName"/>
1.2.在Service、Controller分别加入属性的setter方法
2.使用注解方式,只需在@Autowired,自动装填
然后使用@Qualifier()标记需要装填的属性
两种输入方式没特别的大的区别,看自身习惯使用
运行结果
5.4 三种方式的比较
没有太大的区别,个人觉得,使用更多的看个人习惯,自动装配相对于XML装配要简单一些。注解如果习惯的话,应该是更好用一些。