依赖构造器注入、注入属性装配 bean

2016-03-24 09:00:46   最后更新: 2016-05-04 16:01:26   访问数量:638




在实际的应用中,往往会有很多类之间相互协作完成特定的业务逻辑,每个对象负责管理与自己相互协作的对象的引用,这就造成高度的耦合和难以测试的代码

考虑下面的例子:

public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight() { quest = new RescueDamselQuest(); // 与 RescueDamselQuest 紧耦合 } public void embarkOnQuest() throws QuestException { quest.embark(); } }

 

 

这里我们创建了一个骑士类,这个骑士紧密的与 RescueDamselQuest 耦合在一起,结果是这个骑士只能去拯救少女,而不能去杀掉一条恶龙,不能去参加一场决斗

同时,当你需要测试这个骑士的对象时,必须保证 embarkOnQuest 方法调用的同时 RescueDamselQuest 的 embark 方法也被调用

 

通过依赖注入,可以将这样的耦合松散化

public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest) { this.quest = quest; } public void embarkOnQuest() throws QuestException { quest.embark(); } }

 

 

这样,勇敢的骑士不再与 RescueDamselQuest 耦合,他可以通过传入的 Quest 对象做任何事,这样的方式就是“构造器注入”

依赖注入的代码可以很容易的注入你自己的对象,mock 注入的类来测试

理解了依赖注入的概念,我们就可以看看 Spring 是怎么做的了

 

创建应用组件之间协作的行为通常被称为“装配”,这也是依赖注入的本质

我们基于上一篇日志中的代码进行修改,关于如何建立一个 spring 项目,参看:

Spring MVC 简介及 Hello World 项目创建

 

现在,我们要在家里举办一场表演:

package com.techlog.test.controller; import com.techlog.test.constants.PerformanceException; import com.techlog.test.service.Performer; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; /** * Created by techlog on 15/11/5. */ @Controller public class HomeController { private Performer performer; public HomeController(Performer performer) { this.performer = performer; } @RequestMapping("/home") public ModelAndView home() throws PerformanceException { ModelAndView mv = new ModelAndView(); mv.setViewName("home"); mv.addObject("perform", performer.perform()); return mv; } }

 

这里调用了 Performer 接口的 perform 方法,所以我们要创建一个接口,规定一下比赛的形式:

package com.techlog.test.service; import com.techlog.test.constants.PerformanceException; /** * Created by liuzeyu on 16/3/22. **/ public interface Performer { String perform() throws PerformanceException, PerformanceException; }

 

以及一个实现:

package com.techlog.test.service.implement; import com.techlog.test.constants.PerformanceException; import com.techlog.test.service.Performer; /** * Created by techlog on 16/3/23. */ public class Juggling implements Performer { private int beanBags = 3; public Juggling() { } public Juggling(int beanBags) { this.beanBags = beanBags; } @Override public String perform() throws PerformanceException { return "Juggling " + beanBags + " BeanBags"; } }

 

 

当然了,我们的表演者可不止 Juggling 一个,所以我们的 HomeContrller 里不能依赖于 Juggling 这个表演者,需要依赖注入的方法来注入每个表演者

在 servlet-context.xml 中创建我们的表演者 bean 和 homeController 对应的 bean:

<bean id="juggling" class="com.techlog.test.service.implement.Juggling"/> <bean id="homeController" class="com.techlog.test.controller.HomeController"> <constructor-arg ref="juggling"/> </bean>

 

这里,通过 id 将 juggling bean 装配到了 homeController bean 中,实现了构造器注入

 

于此相同,我们可以直接通过内部 bean 指定需要使用的 bean 实例:

<bean id="homeController" class="com.techlog.test.controller.HomeController"> <constructor-arg> <bean class="com.techlog.test.service.implement.Juggling"> <constructor-arg value="15"/> </bean> </constructor-arg> </bean>

 

内部 bean 没有 id,不能被其他 bean 复用,因此并不推荐使用

 

通过为 juggling bean 添加 constructor-arg 标签,可以指定调用不同的构造器,而不是调用默认的构造器:

<bean id="performer" class="com.techlog.test.service.implement.Juggling"> <constructor-arg value="15"/> </bean>

 

 

从上面可以看到,对于基本类型的参数,使用 value 可以为 bean 注入该参数(调用相应的构造函数),对于类对象,需要使用 ref 将对象 bean 注入

 

上面我们用构造方法创建了 bean,但是对于有的情况,类对象并不使用构造器创建

最典型的情况是单例模式下

 

还是我们刚刚的例子,我们需要为表演者们搭建一个舞台,但是与表演者不同,我们只能有一个舞台供所有表演者使用:

package com.techlog.test.service; /** * Created by techlog on 16/3/23. */ public class Stage { private Stage() { } private static class StageSingletonHolder { static Stage instance = new Stage(); // 延迟加载实例 } public static Stage getInstance() { return StageSingletonHolder.instance; } }

 

 

由于每次要用到舞台时,实际上用的都是同一个舞台,所以需要使用单例的方式创建,这里使用了 static 内部类封装了一个 static 实例来实现单例模式,这使得只有需要他的时候他才会被初始化,而不会在 Stage 类加载时初始化

bean 提供了 factory-method 属性来初始化这样的 bean,他允许我们调用一个静态方法生成实例:

<bean id="stage" class="com.techlog.test.service.Stage" factory-method="getInstance"/>

 

同样可以使用 constructor-arg 来传递参数

 

装配基本类型和 bean 引用

除了构造器注入,我们还可以选择注入属性:

package com.techlog.test.controller; import com.techlog.test.constants.PerformanceException; import com.techlog.test.service.Performer; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; /** * Created by techlog on 15/11/5. */ @Controller public class HomeController { private Performer performer; @RequestMapping("/home") public ModelAndView home() throws PerformanceException { ModelAndView mv = new ModelAndView(); mv.setViewName("home"); mv.addObject("perform", performer.perform()); return mv; } public Performer getPerformer() { return performer; } public void setPerformer(Performer performer) { this.performer = performer; } }

 

 

还是刚刚的例子,现在我们没有了构造器,取而代之的是 perfofmer 对象的 get、set 方法,通过 property 属性可以将所需的值或对象注入进去:

<bean id="homeController" class="com.techlog.test.controller.HomeController"> <property name="performer" ref="juggling"/> </bean>

 

 

装配集合

上面的介绍中,无论是构造器装配还是装配属性,都可以使用 value 或 ref 指定注入的值或其他 Bean,但是有时我们需要为某个集合类型的对象注入 bean 实例

Spring 提供了四种集合配置元素:

spring 提供的集合配置元素
集合元素用途
list装配 list 类型,允许重复(可以装配 java.util.Collection 任意实现的属性或数组类型)
set装配 set 类型,不允许重复
map装配 map 类型(java.util.Map)
props装配 properties 类型,键、值都必须为 String 类型(java.util.Properties)

 

装配 List、Set、Array

对于 Collection 类型的对象或数组类型时,使用 <list> 或 <set> 装配属性时,可以使用 value 标签装配基本类型,或使用 ref 标签装配其他 bean 的引用:

<property name="instruments"> <list> <ref bean="guitar" /> <ref bean="cymbal" /> <ref bean="harmonica" /> </list> </property> <property name="personnames"> <list> <value>Lisa</value> <value>Lily</value> <value>Tom</value> </list> </property>

 

 

装配 Map

对于 Map 类型的集合,可以使用 <props> 或 <map> 标签进行装配,他们的区别是 <props> 只能用来装配 Properties 类型的集合,也就是说他的 key、value 必须为 String

对于基本类型,可以使用 key、value 标签进行装配,对于引用其他 bean 的装配可以使用 key-ref、value-ref:

<property name="instruments3"> <map> <entry key="GUITAR" value-ref="guitar" /> <entry key="CYMBAL" value-ref="cymbal" /> <entry key="HARMONICA" value-ref="harmonica" /> </map> </property> <property name="instruments4"> <props> <prop key="GUITAR">STRUM STRUM STRUM</prop> <prop key="CYMBAL">CRASH CRASH CRASH</prop> <prop key="HARMONICA">HUM HUM HUM</prop> </props> </property>

 

 

装配空值

同时,Spring 提供了 <null/> 元素,为属性装配空值 null:

<property name="instruments"><null/></property>

 

 

表达式装配

上面讲解了如何在 xml 中定义静态的 bean 并装配,Spring3 引入了表达式语言 SpEL,他可以通过运行期执行的表达式将值装配到 Bean 属性或构造器中:

<property name="count" value="#{5}" /> <!-- 常量 --> <property name="count" value="#{saxophone}" /> <!-- 相当于 ref="saxophone" --> <property name="count" value="#{kenny.song}" /> <!-- bean的属性,相对于对相应对象执行 getSong 方法 --> <property name="count" value="#{songSelector.selectSong().toUpperCase()}" /> <!-- bean的函数 --> <property name="count" value="#{songSelector.selectSong()?.toUpperCase()}" /> <!-- 第一个函数调用结果如果是null则不会调用第二个,防止抛出NullPointerException --> <property name="count" value="#{T(java.lang.Math).random()}" /> <!-- T()调用类,用于调用常量或静态函数 --> <property name="count" value="#{T(java.lang.Math).random() + 20 * 5 - 6 % 3 / 2}" /> <!-- 运算 --> <property name="count" value="#{T(java.lang.Math).random() le 10000}" /> <!-- 比较,eq、lt、le、gt、ge --> <property name="count" value="#{shape.kind eq 'circle' and shape.perimeter gt 10000}" /> <!-- 逻辑运算,and、or、not或!、条件表达式 ?: --> <property name="count" value="#{admin.email matches '[a-z0-9]+\\.com'}" /> <!-- 正则,matches -->

 

 






技术帖      web      xml      龙潭书斋      java      framework      spring      ioc      依赖注入      bean     


京ICP备15018585号