Spring Bean
Spring Bean
bean的作用域
- 在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参考如下表:
取值 | 含义 | 创建对象的时机 |
---|---|---|
singleton(默认) | 在IoC容器中,这个bean的对象始终为单实例 | IoC容器初始化时 |
prototype | 这个bean在IoC容器中有多个实例 | 获取bean时 |
多实例——每个实例地址不同
bean的生命周期
Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段:
- Bean的实例化阶段: Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;
- Bean的初始化阶段: Bean创建之后还仅仅是个”半成品”,还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等spring高频面试题Bean的循环引用问题都是在这个阶段体现的;
- Bean的完成阶段: 经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期
Bean的初始化过程
- Bean实例的属性填充
- Aware接口属性注入
- BeanPostProcessor的before()方法回调
- InitializingBean接口的初始化方法回调
- 自定义初始化方法init回调
- BeanPostProcessor的after()方法回调
Bean实例属性填充
Spring在进行属性注入时,分为:
- 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去
- 注入单向引用属性时(UserService中的UserDao属性),从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入Bean实例(完成整个生命周期)后,在进行诸如操作
- 注入双向对象引用属性时(UserService中有UserDao属性,UserDao中有UserService属性 ),就比较复杂了,涉及到循环引用(循环依赖)问题
循环依赖问题解决方案
Spring提供了三级缓存存储完整Bean实例和半成品Bean实例,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供了三个Map:
1 | //1.一级缓存,最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean |
UserService和UserDao循环依赖的过程结合上述三级缓存:
- UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存
- UserService属性注入,需要UserDao,从缓存中获取,没有UserDao;
- UserDao实例化对象,但尚未初始化,将UserDao存储到三级缓存
- UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存
- UserDao执行其他生命周期过程,最终完成一个Bean,存储到一级缓存,删除二三级缓存
- UserService注入UserDao
- UserService执行其他生命周期过程,最终完成一个Bean,存储到一级缓存,删除二三级缓存
基于注解管理bean
- 从Java5开始,Java增加了对注解(Annotation)的支持,它是代码中一种特殊的标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在代码中嵌入补充信息
Spring通过注解实现自动装配的步骤如下:
- 引入依赖
- 开启组件扫描
- 使用注释定义Bean
- 依赖注入
开启组件扫描
Spring默认不使用注解装配Bean,因此我们需要在Spring的XML配置中,通过context:component-scan元素开启Spring Beans的自动扫描功能。开启此功能后,Spring会自动从扫描指定(base-pakage属性配置)及其子包下的所有类,如果类上使用了@Component注解,就将该类配到容器中
1 | <context:component-scan base-package="com.wereash"/> |
使用注解定义Bean
注解 | 说明 |
---|---|
@Component | 该注解用于描述Spring中的Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如Service层、Dao层等 |
@Repository | 该注解通常作用在数据访问层(Dao),用于将Dao的类标识为Spring中的Bean,其功能与@Component相同 |
@Service | 该注解通常作用在业务层(Service),用于将业务层的类标识为Spring中的Bean,其功能与@Component相同 |
@Controller | 该注解通常作用在控制层(如SpringMVC的Controller),用于将控制层的标识为Spring中的Bean,其功能于@Component相同 |
依赖注入
主要有@Autowired和@Resource两种注解
@Autowired注入
- 单独使用Autowired注解,默认根据类型装配(ByType),将Autowired加在属性上,完成注入
- 内部有一个require的属性,默认值是true,表示强制要求bean实例的一个注入
- 在应用启动时,如果对应的IOC容器中不存在对应类型的bean,就会报错
- 如果IOC容器中存在多个相同类型的实例,会报错
- @Primary:优先使用带有Primary注解的bean
- @Qualifier:根据名字找到对应目标的bean
1 | public interface PaymentService { |
在这个例子中,我们定义了两个PaymentService的实现类,AliPaymentServiceImpl上应用了@Primary注解,表示它是优先选择的实现类。在使用@Autowired注入PaymentService的时候,Spring 会自动选择AliPaymentServiceImpl这个类。如果没有使用@Primary,会抛出NoUniqueBeanDefinitionException。
总之,@Primary注解是一个非常有用的注解,它可以让开发人员在多个同类型的bean中选择一个首选bean,避免了在使用注入时出现歧义的问题。
@Resource注入
@Resource注解也可以完成属性注入
- @Resource注解是JDK扩展包中的,也就是JDK的一部分。所以该注解是标准注解,更加具有通用性
- @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name,通过name找不到的话会自动启动通过类型byType装配
- @Resource注解用在属性上,setter方法上
- @Resource注解属于JDK扩展包,所以不在JDK当中,需要引入依赖
全注解开发
全注解开发就是不再使用Spring配置文件了,写一个配置类代替文件
bean id冲突
在同一个XML配置文件中,不能存在id相同的两个bean,否则Spring容器在启动时会报错
- id属性表示Bean里面的唯一标志符号
- Spring在启动时,会验证id的唯一性
- 一旦发现重复就会报错,错误发生在对XML文件进行解析,转化为beanDefinition的阶段
在不同的Spring配置文件中,可以存在id相同的两个bean,IOC容器在加载bean的时候,默认会把多个相同id的bean进行覆盖
在Spring 3.x版本之后:
- 提供了一个@Configuration注解,去声明一个配置类
- 然后使用@Bean注解去实现Bean的声明,取代了XML配置方式
如果我们在同一个配置类中,去声明多个相同名字的Bean,那么Spring IOC容器在解析的时候
- 只会注册第一个声明Bean的实例
- 后面重复出现的不会再注册了
1 |
|
例如以上代码:只会保存UserService01这个实例
如果使用@Autowred注解,去根据类型进行注入,因为IOC容器中只存在UserService01这个实例,所以在启动时,会提示找不到UserService02这个实例
1 |
|
如果使用@Resource注解去根据名字实现依赖注入,在Spring容器中,根据id只能获取到UserService01这个实例
将这个实例赋值给UserService02时,会报类型不匹配的错误
1 |
|