3. 高级装配 Bean
3.1 Bean 的作用域
默认情况下, Spring 中的 bean 都是以单例的形式存在的, 无论注入多少次, 每次注入的都是同一个实例.
考虑到某些 bean 可能是可变的, Spring 定义了不同的作用域, 可以基于这些作用域创建不同的 bean,
单例是默认的作用域, 如果选择 @Scope 注解选择其他作用域, 这可以和 @Component 和 @Bean 一起使用.
- @Configuration
- public class Cap3MainConfig {
- // 给容器中注册一个 bean, 类型为返回值的类型, 默认是单实例
- /*
- * prototype: 多实例: IoC 容器启动的时候, IoC 容器启动并不会去调用方法创建对象, 而是每次获取的时候才会调用方法创建对象
- * singleton: 单实例 (默认):IoC 容器启动的时候会调用方法创建对象并放到 IoC 容器中, 以后每次获取的就是直接从容器中拿(大 Map.get) 的同一个 bean
- * request: 主要针对 web 应用, 递交一次请求创建一个实例
- * session: 同一个 session 创建一个实例
- */
- @Scope("prototype")
- @Bean
- public Person person(){
- return new Person("vincent",20);
- }
- }
3.2 Lazy 懒加载
顾名思义, 懒加载推迟加载 Bean. 默认情况下, 在 IoC 容器初始化时, 会将各个 Bean 注册到容器中; 如果在定义 Bean 时, 使用 @Lazy 声明, 则该 Bean 只有在第一次使用时, 才会被注册到 IoC 容器中.
下面的例子中, person 实例将会在第一次被获取的时候才会初始化.
- @Configuration
- public class Cap4MainConfig {
- // 给容器中注册一个 bean, 类型为返回值的类型, 默认是单实例
- /*
- * 懒加载: 主要针对单实例 bean: 默认在容器启动的时候创建对象
- * 懒加载: 容器启动时候不创建对象, 仅当第一次使用(获取)bean 的时候才创建被初始化
- */
- @Lazy
- @Bean
- public Person person(){
- System.out.println("给容器中添加 person.......");
- return new Person("vincent",20);
- }
- }
可以使用如下测试程序进行测试:
- public class Cap4Test {
- @Test
- public void test01(){
- AnnotationConfigApplicationContext App = new AnnotationConfigApplicationContext(Cap4MainConfig.class);
- String[] names = App.getBeanDefinitionNames();
- // 此时可以获取到 person 的 name, 但是 person 依然未实例化
- for(String name:names){
- System.out.println(name);
- }
- System.out.println("IOC 容器创建完成........");
- // 实例化 person
- App.getBean("person");// 执行获取的时候才创建并初始化 bean
- }
- }
3.3 Conditional 条件注册 Bean
Spring4 引入了 @Conditional 注解, 用于条件化注册 Bean. 如果给定的条件, 计算结果为 true, 就会创建这个 bean, 否则的话, bean 会被忽略.
下面的例子中, 将 IoC 容器注册 bean 时, 当操作系统为 Windows 时, 注册 Lison 实例; 当操作系统为 Linux 时, 注册 James 实例, 此时要用得 @Conditional 注解进行定制化条件选择注册 bean;
- @Configuration
- public class Cap5MainConfig {
- @Bean("person")
- public Person person(){
- System.out.println("给容器中添加 person.......");
- return new Person("person",20);
- }
- @Conditional(WinCondition.class)
- @Bean("lison")
- public Person lison(){
- System.out.println("给容器中添加 win.......");
- return new Person("win",58);
- }
- @Conditional(LinCondition.class)
- @Bean("james")//bean 在容器中的 ID 为 james, IoC 容器 MAP, map.put("id",value)
- public Person james(){
- System.out.println("给容器中添加 mac.......");
- return new Person("mac",20);
- }
- }
注意到, 我们需要自己实现对应的 WinCondition.class 和 LinCondition.class 类, 以其中的一个为例, 如下可以看到, 需要实现自己的 match 函数,
- public class LinCondition implements Condition{
- /*
- *ConditionContext: 判断条件可以使用的上下文(环境)
- *AnnotatedTypeMetadata: 注解的信息
- */
- @Override
- public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
- // TODO 是否为 Windows 系统
- // 能获取到 IoC 容器正在使用的 beanFactory
- ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
- // 获取当前环境变量(包括我们操作系统是 WIN 还是 Linux??)
- Environment environment = context.getEnvironment();
- String os_name = environment.getProperty("os.name");
- if(os_name.contains("Mac")){
- return true;
- }
- return false;
- }
- }
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.
- @Configuration
- @Import(value = { Dog.class,Cat.class, JamesImportSelector.class,
- JamesImportBeanDefinitionRegistrar.class })
- public class Cap6MainConfig {
- // 容器启动时初始化 person 的 bean 实例
- @Bean("person")
- public Person persond(){
- System.out.println("aaaaaaaaaaaa");
- return new Person("james",20);
- }
- @Bean
- public JamesFactoryBean jamesFactoryBean(){
- return new JamesFactoryBean();
- }
- }
在 JamesImportSelector.class 实现中, 只需要返回所有需要 import 的 class 类名即可.
- public class JamesImportSelector implements ImportSelector{
- @Override
- public String[] selectImports(AnnotationMetadata importingClassMetadata){
- // 返回全类名的 bean
- return new String[]{"com.enjoy.cap6.bean.Fish","com.enjoy.cap6.bean.Tiger"};
- }
- }
在 JamesImportBeanDefinitionRegistrar.class 中, 根据需要可以手动注入需要的 bean 实例,
- public class JamesImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
- /*
- *AnnotationMetadata: 当前类的注解信息
- *BeanDefinitionRegistry:BeanDefinition 注册类
- * 把所有需要添加到容器中的 bean 加入;
- * @Scope
- */
- @Override
- public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
- boolean bean1 = registry.containsBeanDefinition("com.enjoy.cap6.bean.Dog");
- boolean bean2 = registry.containsBeanDefinition("com.enjoy.cap6.bean.Cat");
- // 如果 Dog 和 Cat 同时存在于我们 IoC 容器中, 那么创建 Pig 类, 加入到容器
- // 对于我们要注册的 bean, 给 bean 进行封装,
- if(bean1 && bean2){
- RootBeanDefinition beanDefinition = new RootBeanDefinition(Pig.class);
- registry.registerBeanDefinition("pig", beanDefinition);
- }
- }
- }
注意到上面, 也可以通过 FactoryBean 的方法来将所需要的 bean 注入到 IoC 容器中, 在这其中, 需要手动实现其中的 getObject 等方法.
- public class JamesFactoryBean implements FactoryBean<Monkey>{
- @Override
- public Monkey getObject() throws Exception {
- // TODO Auto-generated method stub
- return new Monkey();
- }
- @Override
- public Class<?> getObjectType() {
- // TODO Auto-generated method stub
- return Monkey.class;
- }
- @Override
- public boolean isSingleton() {
- return true;
- }
- }
下面是实际的测试程序, 需要注意的是, 直接使用 getBean(bean name)是取出 FactoryBean 里面封装的 Monkey 实例, 如果需要拿到 FactoryBean 本身, 需要加上 & 符号.
- public class Cap6Test {
- @Test
- public void test01(){
- AnnotationConfigApplicationContext App = new AnnotationConfigApplicationContext(Cap6MainConfig.class);
- System.out.println("IOC 容器创建完成........");
- Object bean1 = App.getBean("jamesFactoryBean");
- Object bean2 = App.getBean("jamesFactoryBean");// 取 Monkey bean
- System.out.println("bean 的类型 ="+bean1.getClass());
- System.out.println(bean1 == bean2);
- Object bean3 = App.getBean("&jamesFactoryBean");// 取 factoryBean
- System.out.println("bean 的类型 ="+bean3.getClass());
- // 打印输出所有 bean
- String[] beanDefinitionNames = App.getBeanDefinitionNames();
- for(String name:beanDefinitionNames){
- System.out.println(name);
- }
- }
- }
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 通过反射机制利用的 class 属性指定实现类实例化 Bean, 在某些情况下, 实例化 Bean 过程比较复杂, 如果按照传统的方式, 则需要在中提供大量的配置信息. 配置方式的灵活性是受限的, 这时采用编码的方式可能会得到一个简单的方案.
Spring 为此提供了一个 org.springframework.bean.factory.FactoryBean 的工厂类接口, 用户可以通过实现该接口定制实例化 Bean 的逻辑. FactoryBean 接口对于 Spring 框架来说占用重要的地位, Spring 自身就提供了 70 多个 FactoryBean 的实现. 它们隐藏了实例化一些复杂 Bean 的细节, 给上层应用带来了便利. 从 Spring3.0 开始, FactoryBean 开始支持泛型, 即接口声明改为 FactoryBean 的形式
以 Bean 结尾, 表示它是一个 Bean, 不同于普通 Bean 的是: 它是实现了 FactoryBean 接口的 Bean, 根据该 Bean 的 ID 从 BeanFactory 中获取的实际上是 FactoryBean 的 getObject()返回的对象, 而不是 FactoryBean 本身, 如果要获取 FactoryBean 对象, 请在 id 前面加一个 & 符号来获取.
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 来将某些类注入到容器中.
来源: https://www.cnblogs.com/way2backend/p/12577031.html