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
2
3
4
5
6
7
8
//1.一级缓存,最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean
private final Map<String,Object>singletonObjects =new ConcurrentHashMap(36);

//2.二级缓存,早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用
private final Map<String,Object> earlyingSingletonObjects=new ConcurrentHashMap(16);

//3.三级缓存,单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean
private final Map<String,ObjectFactory<?>>singletonFactories=new HashMap(16);

UserService和UserDao循环依赖的过程结合上述三级缓存:

  1. UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存
  2. UserService属性注入,需要UserDao,从缓存中获取,没有UserDao;
  3. UserDao实例化对象,但尚未初始化,将UserDao存储到三级缓存
  4. UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存
  5. UserDao执行其他生命周期过程,最终完成一个Bean,存储到一级缓存,删除二三级缓存
  6. UserService注入UserDao
  7. UserService执行其他生命周期过程,最终完成一个Bean,存储到一级缓存,删除二三级缓存

基于注解管理bean

  • 从Java5开始,Java增加了对注解(Annotation)的支持,它是代码中一种特殊的标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在代码中嵌入补充信息

Spring通过注解实现自动装配的步骤如下:

  1. 引入依赖
  2. 开启组件扫描
  3. 使用注释定义Bean
  4. 依赖注入

开启组件扫描

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface PaymentService {
void pay();
}

@Service
@Primary
public class AliPaymentServiceImpl implements PaymentService {
@Override
public void pay() {
System.out.println("使用阿里支付");
}
}

@Service
public class WxPaymentServiceImpl implements PaymentService {
@Override
public void pay() {
System.out.println("使用微信支付");
}
}

@Autowired
private PaymentService 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
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class SpringConfiguration{
@Bean(name="userService")
public UserService01 userService01(){
return new UserService01();
}

@Bean(name="userService")
public UserService01 userService02(){
return new UserService02();
}
}

例如以上代码:只会保存UserService01这个实例

如果使用@Autowred注解,去根据类型进行注入,因为IOC容器中只存在UserService01这个实例,所以在启动时,会提示找不到UserService02这个实例

1
2
3
4
@Autowired
private UserService01 userService01;
@Autowired
private UserService02 userService02;

如果使用@Resource注解去根据名字实现依赖注入,在Spring容器中,根据id只能获取到UserService01这个实例

将这个实例赋值给UserService02时,会报类型不匹配的错误

1
2
3
4
@Resource(name="userService")
private UserService01 userService01;
@Resource(name="userService")
private UserService02 userService02;