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.classLinCondition.class类,以其中的一个为例,如下可以看到,需要实现自己的match函数,

public class LinCondition implements Condition{
/*
*ConditionContext: 判断条件可以使用的上下文(环境)
*AnnotatedTypeMetadata: 注解的信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO 是否为WINDOW系统
//能获取到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的方法。

  1. @Bean: [导入第三方的类或包的组件],比如Person为第三方的类, 需要在我们的IOC容器中使用
  2. 包扫描+组件的标注注解(@ComponentScan: @Controller, @Service @Repository,@Component),一般是针对我们自己写的类。
  3. @Import:快速给容器导入一个组件
  • a, @Import(要导入到容器中的组件):容器会自动注册这个组件,bean的id为全类名
  • b, ImportSelector:是一个接口,返回需要导入到容器的组件的全类名数组。
  • c, ImportBeanDefinitionRegistrar:可以手动添加组件到IOC容器, 所有Bean的注册可以使用BeanDifinitionRegistry,只需要实现。ImportBeanDefinitionRegistrar接口即可
  1. 使用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引入了QualifierPrimary来解决这个问题。

假定有两个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。

扩展:

  1. @Resource

@Resource和Autowired一样可以装配bean

@Resource缺点: 不能支持@Primary功能,不能支持@Autowired(required = false)的功能

  1. @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精品面试课程

Spring MVC系列-(3) Bean的装配的更多相关文章

  1. Spring MVC系列-(2) Bean的装配

    2. Bean的装配 Spring容器负责创建应用程序中的bean,并通过DI来协调对象之间的关系.Spring提供了三种主要的装配机制: XML显式配置: Java配置类进行显式配置: 隐式的bea ...

  2. 【Spring MVC系列】--(4)返回JSON

    [Spring MVC系列]--(4)返回JSON 摘要:本文主要介绍如何在控制器中将数据生成JSON格式并返回 1.导入包 (1)spring mvc 3.0不需要任何其他配置,添加一个jackso ...

  3. Spring mvc系列一之 Spring mvc简单配置

    Spring mvc系列一之 Spring mvc简单配置-引用 Spring MVC做为SpringFrameWork的后续产品,Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块 ...

  4. 《Spring实战》系列之Bean的装配-Days02

    2.1 回顾 对于我第一天在bean的装配中写的,是一些基本的语法或者是Spring本身的一些规定,但是我没有对此进行深究.接下来就让我们仔细的讨论一下细节问题.和传统的类的定义和方法的调用做一些比较 ...

  5. 《Spring实战》系列之Bean的装配-Days01

    1 自动化装配bean Spring通过两个方面实现对bean的自动装配 1 ) 组件扫描(component scaning):Spring会自动发现Spring上下文中的bean 2 ) 自动装配 ...

  6. Spring学习记录(三)---bean自动装配autowire

    Spring IoC容器可以自动装配(autowire)相互协作bean之间的关联关系,少写几个ref autowire: no ---默认情况,不自动装配,通过ref手动引用 byName---根据 ...

  7. Spring学习日志之Bean的装配

    Spring容器负责创建应用程序中的bean并通过依赖注入来协调这些对象之间的关系.但是,作为开发人员,要告诉Spring需要创建哪些bean并且如何将其装配在一起.当描述bean如何装配时,Spri ...

  8. Spring框架系列(三)--Bean的作用域和生命周期

    Bean的作用域 Spring应用中,对象实例都是在Container中,负责创建.装配.配置和管理生命周期(new到finalize()) Spring Container分为两种: 1.BeanF ...

  9. 我看Spring MVC系列(一)

    1.Spring MVC是什么: Spring MVC:Spring框架提供了构建Web应用程序的全功能MVC模块. 2.Spring helloWorld应用(基于Spring 4.2) 1.添加S ...

随机推荐

  1. 主成分分析(PCA)模型概述

    数据降维 降维是对数据高维度特征的一种预处理方法.降维是将高维度的数据保留下最重要的一些特征,去除噪声和不重要的特征,从而实现提升数据处理速度的目的.在实际的生产和应用中,降维在一定信息损失范围内,可 ...

  2. Header函数和PHP_AUTH_USER做用户验证(转载)

    php Header PHP_AUTH_USER PHP_AUTH_PW 用户验证 在php中,可以使用Header函数做一些有趣的事情,用户验证就是其中一个很有意思的功能.具体用法: Header( ...

  3. 后端开发中,可以在Cache-Control设置的常用指令

    max-age 该指令指定从当前请求开始,允许获取的响应被重用的最长时间(单位为秒.例如:Cache-Control:max-age=60表示响应可以再缓存和重用 60 秒.需要注意的是,在max-a ...

  4. jQuery2.0.0版本以后不再支持ie8的原因

    在引用jQuery时,引用高版本的Jq会在IE8下报错,在网上查了一下,jq在2.0+的版本就已经放弃对ie8的支持了.之前没有仔细研究过jq版本,借此机会去看了一下jq版本的知识.一.如何查看jq的 ...

  5. [PyTorch入门]之从示例中学习PyTorch

    Learning PyTorch with examples 来自这里. 本教程通过自包含的示例来介绍PyTorch的基本概念. PyTorch的核心是两个主要功能: 可在GPU上运行的,类似于num ...

  6. Promethues配置

    # my global config global: scrape_interval: 10s # Set the scrape interval to every 15 seconds. Defau ...

  7. 添砖加瓦:Linux /proc目录简介

    Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构.改变内核设置的机制.proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间.它以文件系统的方式为访问系 ...

  8. K8S实战-构建Django项目-03-使用共享存储

    上篇博文,发布之后,正好跟着双十一,不知道大家剁手了没~~.好啦,言归正传先声明一下,每周1,3,5更新教程,大家如果想要了解更多的教程可以重温一下之前的教程或者,关注崔格拉斯 公众号,大家想要源码的 ...

  9. Leetcode 142题 环形链表 II(Linked List Cycle II) Java语言求解

    题目描述: 给定一个链表,返回链表开始入环的第一个节点. 如果链表无环,则返回 null. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始). 如果 p ...

  10. C++扬帆远航——11(斐波那契数列)

    /* * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:Feibo.cpp * 作者:常轩 * 微信公众号:Worldh ...