Spring-IoC中装配Bean

在Spring-IoC简介中简单介绍了什么是IoC以及一些相关的概念,这里将详细介绍如何通过IoC容器提供的方法进行注入和如何进行注入配置。

依赖注入

IoC提供的依赖注入方法主要有属性注入、构造函数注入和工厂方法注入,这里依次介绍如何通过各个方法注入Bean

属性注入

属性注入要求Bean提供一个默认的构造器,并为需要注入的属性提供对应的setter方法。Spring会检查Bean中是否有setter方法,但不会检查是否有对应的属性成员。
Bean的代码如下。

public class Car{
    private int maxSpeed;
    private String brand;
    //如果要通过属性注入则必须实现setter方法
    public void setXX(<T> XX){
        this.XX=XX;
    }
}

我们这里通过XML文件对Bean进行设置,后面会详细介绍所有的配置方式和配置内容。

<bean id="car" class="Car">
    <property name="maxSpeed"><value>200</value></property>
    <property name="brand"><value>红旗CA72</value></property>
</bean>

这样设置完成后,当Spring启动后,就会自动生成idcarclassCar,属性分别为200红旗CA72Bean
对于属性的命名,要求变量的前两个字母要么全部大写,要么全部小写iCCardiDCode是非法的。如果取非法的变量名,在试图启动Spring时,将会失败。因此命名的建议是像QQ、MSN、ID等正常以大写字母出现的专业术语,在Java中一律使用小写形式

构造函数注入

当想要通过构造函数注入时,需要在Bean中实现相应的构造函数,并以想要注入的属性为参数。
Bean的代码如下。

public class Car{
    private int maxSpeed;
    private String brand;
    public Car(int maxSpeed,String brand){...};
}

然后我们在XML文件中进行配置。

<bean id="car" class="Car">
  <constructor-arg type="java.lang.String">
        <value>红旗</value>
  </constructor-arg>
  <constructor-arg type="double">
        <value>200</value>
  </constructor-arg>
</bean>

在通过构造器注入时,配置文件中arg的顺序与构造器中的参数顺序无关,当只有一个构造器的情况下上述配置文件才会生效。因此建议使用索引匹配入参,即<constructor-arg index="0" value="红旗">
当两个构造器仅有intdouble的区别(如下),则此时还需要在配置文件中加上参数类型以作区分,即<constructor-arg index="0" type="int" value="200"/>

public class Car{
    private int maxSpeed;
    private double averageSpeed;
    private String brand;
    public Car(int maxSpeed,String brand){...};
    public Car(double averageSpeed,String brand){...};
}

如果构造函数的参数的类型是可以辨别的(非基础数据类型且入参类型各异),则可以不提供类型和索引的信息。
构造器注入可能会出现循环依赖的问题,即A类需要注入B类,B类需要注入A类,在这种情况下,使用属性注入就可以解决了。

工厂方法注入

在使用框架的过程中很少会使用到工厂方法注入,以下仅举2个例子。

  1. 非静态工厂类
public class CarFactory{
    public Car createHongQiCar(){}
}
<bean id="carFactory" class="CarFactory"/>

<bean id="car" factory-bean="carFactory" factory-method="createHongQiCar"/>

由于工厂类不是静态的,因此需要先定义工厂类的bean。

  1. 静态工厂类
public class CarFactory{
    public static Car createHongQiCar(){}
}
<bean id="car" class="CarFactory" factory-method="createCar"/>

Bean的配置方式

配置Bean可以通过XML、注解、Java类和Groovy DSL这四种方式,下面来详细介绍各个配置方法的细节。

基于XML的配置

对于基于XML的配置,Spring2.0以后采用Schema格式,使得配置文件更具扩展性,但文件头声明会复杂一些,下面是一个例子。

<?xml version="1.0" encoding="utf-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"

xsi:schemaLocation=
  "http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"
>
    <!-- 默认命名空间的配置 -->
    <bean id="foo" class="com.smart.Foo"/>

    <!-- aop命名空间的配置 -->
    <aop:config>
        <aop:advisor pointcut="execution(* *..PetStoreFacade.*(..))" advice-ref="txAdvice"/>
    </aop:config>
</beans>

在上面的代码中定义了3个命名空间:

  1. xmlns="http://www.springframework.org/schema/beans",默认命名空间:没有空间名,用于Spring Bean的定义。
  2. xsi标准命名空间:这个命名空间用于指定自定义命名空间的Schema样式文件,是W3C定义的标准命名空间。
  3. xmlns:aop="http://www.springframework.org/schema/aop",aop命名空间:这个命名空间是Spring配置aop的命名空间,即一种自定义的命名空间。

命名空间的定义分为两个步骤:

  1. 第一步指定命名空间的名称,如xmlns:aop="http://www.springframework.org/schema/aop"aop为命名空间的别名,而"http://www.springframework.org/schema/aop"是命名空间的全限定名,习惯上用文档发布机构的官方网站和相关网站目录作为全限定名。如果命名空间的别名为空,则表示该命名空间为文档默认的命名空间。
  2. 第二步指定命名空间的Schema文档格式文件的位置,用空格或回车换行进行分隔。定义的语法如下<命名空间1> <命名空间1Schema文件> <命名空间2> <命名空间2Schema文件>(注意之间的分隔符可以为空格或换行符)。Schema地址有2个用途:一是XML解析器可以获取Schema文件并对文档进行格式合法性验证;二是IDE可以引用Schema文件对文档编辑提供自动补全功能。

以上就是一个XML配置文件的结构介绍,接下来将介绍如何对Bean进行定制。

  • 字面值
    对于基本类型或其封装类,我们在XML中可以直接进行设置。对于字符串来说,如果其中包含了&、<、>、"、'这5种特殊字符,可以通过下面的![CDATA[]]进行包装,使得XML解析器将其中的内容当成纯文本;或者可以通过转义序列来表示&amp、&lt、&gt、&quot、&apos。
    <bean id="car" class="Car">
     <property name="maxSpeed">
         <value>200</value>
     </property>
     <property name="brand">
         <value><![CDATA[红旗&CA72]]></value>
     </property>
    </bean>
    
  • 引用其他bean
    可以通过以下3种标签对其他的bean进行引用。
    1. bean:可以引用同一容器或父容器总的Bean,这是最常见的形式。
    2. local:只引用同一配置文件中的Bean。
    3. parent:只引用父容器中的Bean。
    <!--通过bean-->
    <bean id="car" class="Car"/>
    <bean id="boss" class="Boss">
      <property name="car">
        <ref bean="car"/>
      </property>
    </bean>
    <!--通过local与通过bean的方式类似-->
    <!--通过parent-->
    <!--在父容器中定义car-->
    <bean id="car" class="Car"/>
    <!--在子容器中定义另一个car-->
    <bean id="car" class="Car"/>
    <bean id="boss" class="Boss">
      <ref parent="car"><!--将引用父容器中的car-->
    </bean>
    
  • 内部bean
    我们也可以通过隐藏类那样定义bean。
    <bean id="boss" class="Boss">
      <property name="car">
        <bean class="Car">
          <property name="brand" value="红旗"/>
        </bean>
      </property>
    </bean>
    
  • null值
    如果想要注入null值,需要显示的将值设为<null/>,即<property name="brand"><value><null/></value></property>
  • 级联属性
    <bean id="boss" class="Boss">
      <property name="car.brand" value="红旗"/>
    </bean>
    
    如果想要通过这种方式直接对boss中的car的属性进行设置,则需要在Boss类中声明一个初始化对象并实现getter()方法。
    public class Boss{
      private Car car=new Car();
      public Car getCar(){return car;}
    }
    
  • 集合类
    1. List
    <bean id="boss" class="Boss">
      <property name="favorites">
        <list>
          <value>看报</value>
          <value>滑雪</value>
        </list>
      </property>
    </bean>
    
    如果List的属性类型可以通过字符串字面值进行配置,那么就可以使用这种方式,如String[]int[]等。
    此外,List还可以通过<ref>注入容器中其他的Bean。
    1. Set
    <bean id="boss" class="Boss">
      <property name="favorites">
        <set>
          <value>看报</value>
          <value>滑雪</value>
        </set>
      </property>
    </bean>
    
    1. Map
    <bean id="boss" class="Boss">
      <property name="jobs">
        <map>
          <entry>
            <key><value>AM</value><key>
            <value>会见客户</value>
          </entry>
          <entry>
            <key><value>PM</value><key>
            <value>开会</value>
          </entry>
          <!--如果键值对都是对象-->
          <entry>
            <key><ref bean="keyBean"/><key>
            <ref bean="valueBean"/>
          </entry>
        </map>
      </property>
    </bean>
    
  • Properties
    Properties属性可以看成键值都是字符串的Map类型。
    <bean id="boss" class="Boss">
      <property name="jobs">
        <props>
          <prop key="mail">123@gmail.com</prop>
        </props>
      </property>
    </bean>
    
  • 强类型集合
    Spring配置强类型和非强类型集合相同,会将值自动转换为目标类型。
    public class Car{
      private Map<Integer,String> passengers=new HashMap<Integer,String>();
    }
    
  • 集合合并
    <bean id="boss1" class="Boss">
     <property name="favorites">
       <set>
         <value>看报</value>
       </set>
     </property>
    </bean>
    <bean id="boss2" class="Boss" parent="boss1">
     <property name="favorites">
       <set merge="true"> <!--合并父类中的同名set-->
         <value>看报</value>
       </set>
     </property>
    </bean>
    
  • 配置集合类型的Bean
    如果想要配置集合类型的Bean,而不是属性类型的集合,可以通过在Spring配置文件中引入util命名空间的声明,然后进行配置。
    <beans
         xmlns:util="http://www.springframework.org/schema/util">
         xsi:shcemaLocation="http://www.springframework.org/schema/util
                                            http://www.springframework.org/schema/util/spring-util-4.0.xsd"
     <util:list id="favorite" list-class="java.util.LinkedList">
       <value>喝茶</value>
       <value>看报</value>
     </util:list>
    </beans>
    
简化XML配置方式
  1. 字面值属性
类型 简化前 简化后
字面值属性 <property name="maxSpeed"><value>200</value></property> <property name="maxSpeed" value="200"/>
构造函数参数 <constructor-arg index="0"><value>200</value></constructor-arg> <constructor-arg index="0" value="200"/>
集合元素 <map>
<entry>
<key><value>AM</value></key>
<value>见客户</value>
</entry>
</map>
<map>
<entry key="AM" value="见客户"/>
</map>
  1. 引用对象属性
类型 简化前 简化后
字面属性值 <property name="car"> <ref-bean="car"/></property> <property name="car" ref="car">
构造函数 <constructor-arg> <ref-bean="car"></property> <constroctor-arg ref="car"/>
集合元素 <entry>
<key><ref bean="keyBean"/></key>
<ref bean="valueBean">
</entry>
<entry key-ref="keyBean" value-ref="valueBean"/>

<ref>的简化形式对应于<ref bean="xxx">,而<ref local>和<ref parent>没有对应的简化形式。

  1. 使用p命名空间
<beans xmlns:p="http://www.springframework.org/schema/p"><!--声明p命名空间-->
  <bean id="car" class="Car"
    p:brand="红旗"
    p:maxSpeed="200"/>
  <bean id="boss" class="Boss"
    p:car-ref="car"/>
</beans>

基于注解的配置

采用基于注解的配置文件,则Bean定义信息通过在Bean实现类上标注注解实现。

//这里定义了一个Dao的Bean
/*
  与@Component功能相似的还有:
    @Repository:用于对DAO实现类进行标注
    @Service:用于对Service实现类进行标注
    @Controller:用于对Controller实现类进行标注
*/
@Component("carDao")    
public class CarDao{
  //..
}

对于通过注解定义的bean需要在Spring配置文件中进行额外设置。

<beans
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:"http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd"
>
  <!--扫描类包以应用注解的bean,可以通过resource-pattern进行过滤 -->
  <!-- 其余过滤的方法还有include-filter和exclude-filter -->
  <context:component-scan base-package="package" resource-pattern="./*.class">
</beans>

Bean完成配置后,还需要通过注解进行自动注入

@Service
public class LoginService{
  //注入LogDao的bean
  //对required=false设置,当Spring启动时如果未找到对应的bean则不会报错
  //通过Qualifier可以指定对应bean的名称
  @Autowired(required=false)
  @Qualifier("logDao")
  private LogDao logDao;
  
  //对类方法进行标注
  @Autowired
  @Qualifier("logDao")
  public void setLogDao(LogDao logDao){}
  //或者通过这种形式
  public void setLogDao(@Qualifier("logDao")LogDao logDao){}

  //如果对类中集合类的变量或方法入参进行标注,那么Spring会将容器中类型匹配的所有Bean都注入
  @Autowired
  private List<Plugin> plugins;
  //Spring如果发现变量是一个list和一个map,会将容器中匹配集合元素类型的所有bean都注入
  //这里将会把plugins的bean注入map集合,key是bean的名字,value是所有实现了plugin的bean。
  @Autowired
  private Map<String,Plugin> pluginMaps;
  //如果Plugin有多个实现类,那么可以在不同的实现类前用@Order(value=1)对注入顺序进行设置,越小越先加载
}

基于Java类的配置、基于Groovy DSL的配置

一般来说,使用XML和标注的方式就能解决几乎所有的任务。建议使用XML配置DataSource、SessionFactory等资源bean,在XML中利用aop、context命名空间进行相关主题的配置,其余所有项目中开发的Bean都通过基于注解配置的方式进行配置。

Bean基本配置

Bean的命名

Bean的id属性命名与Java变量的命名要求相同,而name属性命名没有任何字符上的限制。可以通过不设置id属性命名来实例化匿名Bean

<bean class="Car"/>
<bean class="Car"/>
<bean class="Car"/>

这样就实例化了3个匿名Bean,第一个Bean通过getBean("Car")获得,第二个通过getBean("Car#1")获得。

依赖注入

Spring支持属性注入和构造函数注入,除此之外还支持工厂法注入方式。

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

推荐阅读更多精彩内容