3. 高级装配Bean
3.1 Bean的作用域
默认情况下,Spring中的bean都是以单例的形式存在的,无论注入多少次,每次注入的都是同一个实例。
考虑到某些bean可能是可变的,Spring定义了不同的作用域,可以基于这些作用域创建不同的bean,
单例是默认的作用域,如果选择@Scope
注解选择其他作用域,这可以和@Component
和@Bean
一起使用。
1 | @Configuration |
3.2 Lazy懒加载
顾名思义,懒加载推迟加载Bean。默认情况下,在IOC容器初始化时,会将各个Bean注册到容器中;如果在定义Bean时,使用@Lazy
声明,则该Bean只有在第一次使用时,才会被注册到IOC容器中。
下面的例子中,person实例将会在第一次被获取的时候才会初始化。
1 | @Configuration |
可以使用如下测试程序进行测试:
1 | public class Cap4Test { |
3.3 Conditional条件注册Bean
Spring4引入了@Conditional
注解,用于条件化注册Bean。如果给定的条件,计算结果为true,就会创建这个bean,否则的话,bean会被忽略。
下面的例子中,将IOC容器注册bean时, 当操作系统为WINDOWS时,注册Lison实例; 当操作系统为LINUX时, 注册James实例,此时要用得@Conditional注解进行定制化条件选择注册bean;
1 | @Configuration |
注意到,我们需要自己实现对应的WinCondition.class
和LinCondition.class
类,以其中的一个为例,如下可以看到,需要实现自己的match函数,
1 | public class LinCondition implements Condition{ |
3.4 import注册Bean
这节中,首先总结一下Spring中常见的注入Bean的方法。
- @Bean: [导入第三方的类或包的组件],比如Person为第三方的类, 需要在我们的IOC容器中使用
- 包扫描+组件的标注注解(@ComponentScan: @Controller, @Service @Repository,@Component),一般是针对我们自己写的类。
- @Import:快速给容器导入一个组件
- a, @Import(要导入到容器中的组件):容器会自动注册这个组件,bean的id为全类名
- b, ImportSelector:是一个接口,返回需要导入到容器的组件的全类名数组。
- c, ImportBeanDefinitionRegistrar:可以手动添加组件到IOC容器, 所有Bean的注册可以使用BeanDifinitionRegistry,只需要实现。ImportBeanDefinitionRegistrar接口即可
- 使用Spring提供的FactoryBean(工厂bean)进行注册
前两种方法在上一章已经介绍了,现在主要介绍剩下两类。
下面的配置类中,直接将Dog和Cat import到配置中,本身配置类中也定义了person的实例bean以及自定义的factoryBean。
1 | @Configuration |
在JamesImportSelector.class
实现中,只需要返回所有需要import的class类名即可。
1 | public class JamesImportSelector implements ImportSelector{ |
在JamesImportBeanDefinitionRegistrar.class
中,根据需要可以手动注入需要的bean实例,
1 | public class JamesImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { |
注意到上面,也可以通过FactoryBean的方法来将所需要的bean注入到IOC容器中,在这其中,需要手动实现其中的getObject等方法。
1 | public class JamesFactoryBean implements FactoryBean<Monkey>{ |
下面是实际的测试程序,需要注意的是,直接使用getBean(bean name)是取出FactoryBean里面封装的Monkey实例,如果需要拿到FactoryBean本身,需要加上&
符号。
1 | public class Cap6Test { |
Spring中出现了BeanFactory和FactoryBean,下面对两者的区别进行解释:
BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。
FactoryBean是一个Bean,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。
1. BeanFactory
BeanFactory,以Factory结尾,表示它是一个工厂类(接口),它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。
ApplicationContext包含BeanFactory的所有功能,通常建议比BeanFactory优先 。ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,ApplicationContext包还提供了以下的功能:
- MessageSource, 提供国际化的消息访问
- 资源访问,如URL和文件
- 事件传播
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层;
BeanFactory提供的方法及其简单,仅提供了六种方法供客户调用:
- boolean containsBean(String beanName) 判断工厂中是否包含给定名称的bean定义,若有则返回true
- Object getBean(String) 返回给定名称注册的bean实例。根据bean的配置情况,如果是singleton模式将返回一个共享实例,否则将返回一个新建的实例,如果没有找到指定bean,该方法可能会抛出异常
- Object getBean(String, Class) 返回以给定名称注册的bean实例,并转换为给定class类型
- Class getType(String name) 返回给定名称的bean的Class,如果没有找到指定的bean实例,则排除NoSuchBeanDefinitionException异常
- boolean isSingleton(String) 判断给定名称的bean定义是否为单例模式
- String[] getAliases(String name) 返回给定bean名称的所有别名
2. FactoryBean
一般情况下,Spring通过反射机制利用
Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean
以Bean结尾,表示它是一个Bean,不同于普通Bean的是:它是实现了FactoryBean
3.5 运行时注入
本节介绍Spring在运行时的两种常见注入方式,@Value和@Autowired。
@Value
该注解的作用是将我们配置文件的属性读出来,有@Value(“${}”)
和@Value(“#{}”)
两种方式。
**1. @Value(“${}”)**:注入的是外部配置文件对应的property
在application.propertites配置属性如下:
在程序中动态读取server.port属性,
@w=300
这样server.port=8000就注入到了对应的参数中。
**2. @Value(“#{}”)**:常用的方式是#{obj.property ? :default_value}
,注意与上一种方式不同的是,这种方式中的obj需要是一个对象。也可以在其中填写SpEL表达式
。
Spring表达式语言全称为“Spring Expression Language”,缩写为“SpEL”,类似于Struts2x中使用的OGNL表达式语言,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与Spring功能完美整合,如能用来配置Bean定义。
下面的例子中,首先定义UserBean并从property文件中读取属性,属性值为mysql。
@w=400
接着在另一个Controller类中注入UserBean的属性。
@w=300
@Autowired
Spring中常利用@Autowired
完成依赖注入(DI), 对IOC容器中的各个组件的依赖关系赋值。
下面的例子中,是常见的DAO、Service、Controller模型,采用Autowired可以方便的在Service层和Controller层中注入对应的Bean实例。
@Autowired实现原理就是:默认优先按类型去容器中找对应的组件,相当于anno.getBean(TestDao.class)去容器获取id为testDao的bean, 并注入到TestService的bean中;
但是当容器中有多个testDao时,使用默认的@Autowired就会发生异常,IOC容器此时无法确定哪个bean作为依赖注入的对象,Spring引入了Qualifier和Primary来解决这个问题。
假定有两个testDao,其bean id分别为testDao1和testDao2,此时可以使用@Autowired和@Qualifier
结合来指定注入哪一个bean,下面的例子中,指定bean id为testDao,注意还可以加上required=false
当容器中找不到这个bean时,也不会报错,此时该对象注入失败为null。
如果不使用@Qualifier
,可以使用@Primary
来指定默认的首选bean。此时通过getBean和autowired获取到的都是@Primary
指定的bean。
当@Qualifier
和@Primary
共存时,@Qualifier
会按照bean id来获取指定的bean,不会受到@Primary
的影响。此时使用getBean获取到的就是@Primary
标识的bean。
扩展:
- @Resource
@Resource和Autowired一样可以装配bean
@Resource缺点: 不能支持@Primary功能,不能支持@Autowired(required = false)的功能
- @Inject
@Inject和Autowired一样可以装配bean, 支持@Primary功能, 可用于非spring框架.
@Inject缺点: 但不能支持@Autowired(required = false)
的功能,需要引入第三方包javax.inject
@w=350
Autowired属于spring的, 不能脱离spring, @Resource和@Inject都是JAVA规范
推荐使用@Autowired。
3.6 @Bean Vs @Component
@Component
主要和ComponentScan结合,用于自动检测和配置Bean,Bean和被注解的类是一一对应的关系。
@Bean
用于显式声明一个单独的Bean,而不是让Spring自动完成该过程,通过该注解可以将类的定义和Bean的声明解耦。特别是使用第三方的库时,只能通过@Bean来将某些类注入到容器中。
本文由『后端精进之路』原创,首发于博客 http://teckee.github.io/ , 转载请注明出处
搜索『后端精进之路』关注公众号,立刻获取最新文章和价值2000元的BATJ精品面试课程。