Ioc容器和bean的装配

Spring IoC 概述

正转:若要使用某个对象,需要自己去负责对象的创建。
反转:若要使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程,也就是把创建对象的控制权反转给了Spring框架。

Spring IoC 容器的设计

Spring IoC 容器的设计主要是基于以下两个接口:

  • BeanFactory
  • ApplicationContext

其中 ApplicationContext 是 BeanFactory 的子接口之一,换句话说:BeanFactory 是 Spring IoC 容器所定义的最底层接口,而 ApplicationContext 是其最高级接口之一,并对 BeanFactory 功能做了许多的扩展,所以在绝大部分的工作场景下,都会使用 ApplicationContext 作为 Spring IoC 容器。

BeanFactory接口
// bean的获取
bean = (Bean) factory.getBean("beanName");  //按照 bean的名字拿bean。这种方法不太安全,IDE 不会检查其安全性(关联性)
bean = (Bean) factory.getBean(Bean.class);  //按照类型拿bean。要求在 Spring 中只配置了一个这种类型的实例,否则报错
bean = (Bean) factory.getBean("beanName", Bean.class); //按照名字和类型拿 bean(推荐)

// bean的检查
boolean = factory.containsBean("beanName"); //容器是否包含这个bean
boolean = factory.isTypeMatch("beanName", Bean.class) // bean类型是否匹配
boolean = factory.isSingleton("beanName"); //bean是否单例模式
boolean = factory.isPrototype("beanName"); //bean是否prototype模式:
/**
 *1、prototype作用域的Bean会导致在每次对该Bean请求(将其注入到另一个Bean中,或者以程序的方式调用容器的getBean方法)时都会创建一个新的Bean实例;
 *2、对于具有prototype作用域的Bean,有一点很重要,即Spring不能对该Bean的整个生命周期负责。具有prototype作用域的Bean创建后交由调用者负责销毁对象回收资源。
 **/
ApplicationContext接口

ApplicationContext 接口继承BeanFactory接口,但扩展了许许多多的接口,因此它的功能十分强大,在实际应用中常常会使用到的是 ApplicationContext 接口,ApplicationContext 的方法和功能较多。

  • 常见实现类
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext ac = new FileSystemXmlApplicationContext("c:/applicationContext.xml");

/**
 *XmlWebApplicationContext:需要在Web的环境下才可以运行,所以运行起来比较特殊
 **/
XmlWebApplicationContext ac = new XmlWebApplicationContext(); // 这时并没有初始化容器
ac.setServletContext(servletContext); // 需要指定ServletContext对象
ac.setConfigLocation("/WEB-INF/applicationContext.xml"); // 指定配置文件路径,开头的斜线表示Web应用的根目录
ac.refresh(); // 初始化容器
  • 使用示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 通过 xml 方式装配 bean -->
    <bean name="source" class="pojo.Source">
        <property name="fruit" value="橙子"/>
        <property name="sugar" value="多糖"/>
        <property name="size" value="超大杯"/>
    </bean>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Source source = (Source) context.getBean("source", Source.class);

System.out.println(source.getFruit());
System.out.println(source.getSugar());
System.out.println(source.getSize());

=========================此处内容分隔符=========================
=========================此处内容分隔符=========================
=========================此处内容分隔符=========================

Bean的定义和初始化

  • Resource 定位
    Spring IoC 容器先根据开发者的配置,进行资源的定位,在 Spring 的开发中,通过 XML 或者注解都是十分常见的方式,定位的内容是由开发者提供的。
  • BeanDefinition 的载入
    这个时候只是将 Resource 定位到的信息,保存到 Bean 定义(BeanDefinition)中,此时并不会创建 Bean 的实例
  • BeanDefinition 的注册
    这个过程就是将 BeanDefinition 的信息发布到 Spring IoC 容器中。注意:此时仍然没有对应的 Bean 的实例。
  • Bean的初始化
    做完了以上 3 步,Bean 就在 Spring IoC 容器中被定义了,而没有被初始化,更没有完成依赖注入,也就是没有注入其配置的资源给 Bean,那么它还不能完全使用。
    对于初始化和依赖注入,Spring Bean 还有一个配置选项——【lazy-init】,其含义就是是否初始化 Spring Bean。在没有任何配置的情况下,它的默认值为 default,实际值为 false,也就是 Spring IoC 默认会自动初始化 Bean。如果将其设置为 true,那么只有当我们使用 Spring IoC 容器的 getBean 方法获取它时,它才会进行 Bean 的初始化,完成依赖注入。

Bean的生成

bean的默认id号或者名字
  • 组件扫描(Component)的情况:默认的id号或者bean的name是类名的首字母小写。
  • 显示配置bean(Configuration-Bean)时:这个时候bean默认的名字是与方法名相同
  • 在配置文件xml中的配置:<bean class="com.qls.Hello"/>这个bean的id号默认是com.qls.Hello#0即(包名.类名#自然数)
组件扫描生成bean
  • xml标签
    配置完<context:component-scan>这个标签后,spring就会去自动扫描base-package对应的路径或者该路径的子包下面的java文件,如果扫描到文件中带有@Component,@Service,@Repository,@Controller等这些注解的类,则把这些类注册为bean。
/**
 * base-package属性告诉spring要扫描的包
 * use-default-filters=”false”表示不要使用默认的过滤器,默认过滤器会扫描包含@Service,@Component,@Repository,@Controller注解修饰的类
 * <context:excluce-filter> 来按照规则排除某些包的扫描
 * <context:incluce-filter> 来按照规则只包含某些包的扫描
 **/
context:component-scan base-package="com.sparta.trans.controller"  use-default-filters="false">
    <context:include-filter type="regex" expression="com\.sparta\.trans\.[^.]+(Controller|Service)"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan> 

filter标签的type和表达式说明如下


image.png
  • @ComponentScan注解
    @ComponentScan 的 useDefaultFilters 属性,该属性默认值为 true,也就是说 spring 默认会自动发现被 @Component、@Repository、@Service 和 @Controller 标注的类,并注册进容器中。要达到只包含某些包的扫描效果,就必须将这个默认行为给禁用掉(在 @ComponentScan 中将 useDefaultFilters 设为 false 即可)
/**
 * 创建一个配置类,在配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类
 * 配置类包括@Component、@Repository、@Service 和 @Controller 
 **/
@ComponentScan
public class BeanConfig {}

/**
 * value指定了要扫描的包,默认扫描包括@Component、@Repository、@Service 和 @Controller注解的类
 * 使用 excludeFilters 来按照规则排除某些包的扫描
 * 使用 includeFilters 来按照规则只包含某些包的扫描
 **/
@ComponentScan(value = "io.mieux",
        excludeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Controller.class})})
        includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})},
        useDefaultFilters = false
public class BeanConfig {}

/**
 * 如果使用的 jdk8,则可以直接添加多个 @ComponentScan 来添加多个扫描规则,但是在配置类中要加上 @Configuration 注解,否则无效
 * 也可以使用 @ComponentScans 来添加多个 @ComponentScan,从而实现添加多个扫描规则。同样,也需要加上 @Configuration 注解,否则无效
 **/
@ComponentScan(value = "io.mieux.controller")
@ComponentScan(value = "io.mieux.service")
@ComponentScans(value = {@ComponentScan(value = "io.mieux.controller"), @ComponentScan(value = "io.mieux.service")})
@Configuration
public class BeanConfig {}
使用@Bean生成bean
  • 问题
    以上都是通过 @Component 注解来装配 Bean ,并且只能注解在类上,当你需要引用第三方包的(jar 文件),而且往往并没有这些包的源码,这时候将无法为这些包的类加入 @Component 注解,让它们变成开发环境中的 Bean 资源。
  • 解决方案
    1.自己创建一个新的类来扩展包里的类,然后再新类上使用 @Component 注解,但这样很 low
    2.使用 @Bean 注解,注解到方法之上,使其成为 Spring 中返回对象为 Spring 的 Bean 资源。

Bean的装配(注入)

byType的注入是指通过类的包名+类名来确定bean的类型,如果一个类型有多个bean则会报错。
byName就是通过Bean的id或者name来注入的。例如:代码autowire="byName"意思是通过id="userDao"来查找Bean中的userDao对象

  • xml配置方式
<-- 基于构造函数的依赖注入 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
    <constructor-arg ref="spellChecker"/>
</bean>

<-- 基于设置函数setter的依赖注入 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
    <property name="spellChecker" ref="spellChecker"/>
</bean>

<-- 基于设置函数setter的内部依赖注入 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
    <property name="spellChecker">
        <bean id="spellChecker" class="com.tutorialspoint.SpellChecker"/>
    </property>
</bean>

<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
</bean>
<-- 基于构造函数的自动装配(实质是byType自动装配) -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor" autowire="constructor">
  <constructor-arg value="Generic Text Editor"/>
</bean>

<-- 基于设置函数setter的byName自动装配 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor" autowire="byName">
    <property name="name" value="Generic Text Editor" />
</bean>

<-- 基于设置函数setter的byType自动装配 -->
<bean id="textEditor" class="com.tutorialspoint.TextEditor" autowire="byType">
    <property name="name" value="Generic Text Editor" />
</bean>

<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
</bean>
  • 注解方式
@Required  注释应用于bean属性的setter方法上,表明在注入bean时这个bean必须存在。

@Resource  此非spring的注解,应用在field和bean属性的setter方法上,如果找不到会报错。
           如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
           如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
           如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
           如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为按照byType方式进行装配。

@Autowired 注解应用在constructor、bean、setter方法上,是按照类型(byType)装配依赖对象,
           默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。
           如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容