Sprign In Action<二>

来源:互联网 发布:unity php 编辑:程序博客网 时间:2024/06/16 14:48

二、DI-依赖注入(IOC-控制反转)

       创建应用对象之间协作关系的行为叫做装配,这就是依赖注入的本质。

1.Spring的三种装配机制:

       1.XML中显式装配;2.在java中显式装配;3.隐式的bean发现机制和自动装配(通过注解的方式)。

      《Spring In Action》建议尽可能的使用自动装配,显式的越少越好,但是当你必须要用到显式装配的时候,尽量使用类型安全且比XML更强大的JavaConfig。只有想使用便利的XML命名空间和JavaConfig没有和XML相同的实现时才会使用XML显式装配。

       1).自动装配Bean

CD接口

package action.spring.study.second;public interface CompactDisc {    void play();}

CD接口的实现类

package action.spring.study.second;import org.springframework.stereotype.Component;@Componentpublic class SgtPeppers implements CompactDisc {private String title="SgtPepper";private String artist="Play SgtPepper Song";@Overridepublic void play() {         System.out.println("Playing"+title+"contain"+artist);}}
@Component会表名SgtPeppers是组件类并提醒Spring创建Bean,但是Spring的组件扫描默认是不打开的,所以需要我们显式配置一下。下列代码定义了Spring的装配规则,@Configuration的使用暂不考虑,@ComponentScan这个注解能够在Spring中启动组件扫描。如果没有其他设置的话会默认扫描与配置类相同的包,查找@Component注解的类,发现SgtPeppers类被@Component注解Spring会为其自动创建bean。
package action.spring.study.second;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;@Configuration@ComponentScanpublic class CDPlayerConfig {}


如果钟爱XML配置方式可以在XML配置文件中添加Spring context命名空间

<context:component-scan base-package="action.spring.study.second">

测试@ComponentScan扫描后创建bean成功并且不为空,下面是一个简单的JUnit测试代码:

package action.spring.study.second;import static org.junit.Assert.*;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes=CDPlayerConfig.class)public class CDPlayerTest {   @Autowired   private CompactDisc cd;   @Test   public void cdShouldNotBeNull(){   assertNotNull(cd);   }}

下面我们将深入讨论@Component和@ComponentScan的详细使用和作用:

       Spring应用上下文会给每一个bean一个ID,以上例子中我们没有明确给SgtPeppers指定一个ID,Spring应用上下文默认取名类sgtPeppers(类名第一个字母小写作为ID)。如果想为SgtPeppers设定不同的ID这么写:@Component("sgtPeppersNewName"),还可以用@Named替代@Component。写法和@Component类似:@Named("sgtPeppersNewName")。两者之间存在细微的差异,但是大部分场景可以替换。

       @ComponentScan如何不设置其他属性它默认是扫描配置类所在的包,如果为了设置不同的基础包或者有时候我们会将配置类

单独放到一个包中我们需要指定扫描基础包。@ComponentScan("base-package")或者@ComponentScan(basePackages={"base-package1,"base-package2"}),但是这种方式不安全,如果重构代码指定的扫描包可能发生错误,所以经常@ComponentScan(base-package={基础包下的类名1.class,基础包下的类名2.class})。一般的处理方式我们会在基础包中创建空标记接口(就是接口里什么也没有)用来扫描。

         在应用程序中如果对象独立的,彼此没有依赖,只需要配置组件扫描既可以了。但是很多对象依赖其他对象完成任务,我们需要一种方法将扫描得到的bean和他们的依赖绑定到一起。

       借助@Autowired Spring应用上下文可以寻找bean需要的其他bean并实现自动装配。具体代码如下:

package action.spring.study.second;import org.springframework.beans.factory.annotation.Autowired;public class CDPlayer {    private CompactDisc cd;        public CompactDisc getCd() {return cd;    }    @Autowired    public void setCd(CompactDisc cd) {this.cd = cd;    }    @Autowired    public CDPlayer(CompactDisc cd ){    this.cd=cd;    }    public void paly(){    cd.play();    }}
在构造方法上使用@Autowired和在setter方法上使用@Autowired的效果是一样的。不光如此我们还可以在CDPlayer这个类的其他方法上添加@Autowired,这三种方法都可以实现自动装配,没有任何区别。在启动Spring应用上下文时如果没有对应的Bean装配到CDPlayer的bean中,Spring会抛出相应的异常。所以可以这样写@Autowired(required=false),这样Spring就会在启动Spring应用上下文的时候就会让这个bean处于未装配状态,如果代码没有对其进行null判断就会产生:NullPointerException。如果装配的时候有多个bean满足要求,比如:CompactDisc接口的实现类有好几个,Spring在启动Spring应用上下文的时候就会报异常,具体的解决办法后面会学到,暂且搁置。@Inject是java依赖注入规范,该规范定义了@Named注解。@Inject和@AutoWired注解存在细微的差异,但是大部分场合和替换使用。

验证自动装配:

package action.spring.study.second;import static org.junit.Assert.*;import org.junit.Rule;import org.junit.Test;import org.junit.contrib.java.lang.system.StandardOutputStreamLog;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes=CDPlayerConfig.class)public class CDPlayerTest2 {   @Rule   public final StandardOutputStreamLog log = new StandardOutputStreamLog();   @Autowired   private MediaPlayer player;   @Autowired   private CompactDisc cd;   @Test   public void cdShouldNotBeNull(){   assertNotNull(cd);   }   @Test   public void paly(){   player.play();   assertEquals("PlayingSgtPeppercontainPlay SgtPepper Song",log.getLog());   }}


        2).通过Java代码装配Bean

        虽然通过组件扫描和自动装配实现Spring的自动化配置是极为推荐的方式,但是有时候这种方式行不通:比如将第三方库的组件装配到你的应用.....而JavaConfig相对于XML来说更强大、类型安全并且重构友好,毕竟它本身就是java代码。虽然JavaConfig本身就是java代码,但是毕竟它是配置代码,它与一般的业务逻辑代码不同,所以通常都是将它单独放在一个包内。

        在之前的实例中我们配置了CDPlayerConfig的java代码,创建JavaConfig文件的关键在于@Configuration主键,这个注解表名这个类配置类。但是我们去除@ComponentScan这个注解后我们再运行CDPlayerTest就会报BeanCreationException异常。下面我们就来解决这个问题:

package action.spring.config;import org.springframework.context.annotation.Bean;import action.spring.study.second.CompactDisc;import action.spring.study.second.SgtPeppers;@Configurationpublic class JavaConfig {    @Bean(name="sgtPeppersName")public CompactDisc sgtPeppers(){return new SgtPeppers();}@Bean(name="sgtPeppersNewName")public CompactDisc sgtPeppersNewName(){return new SgtPeppers();}}
通过@Bean(name="Bean的ID名")来实现JavaConfig的显示装配Bean。CDPlayer依赖CompactDisc类,最简单的方法就是调用JavaConfig配置类中生成CompactDisc类的方法,代码如下:

@Bean(name="cDPlayer")public MediaPlayer  cDPlayer(){    return new CDPlayer(sgtPeppers());}
乍一看好像是直接调用的sgtPeppers方法生成的CompactDISC的Bean,实际上因为@Bean的存在所以Spring拦截所以对这个方法的调用,并且确保直接返回该方法创建的bean。

@Bean(name="sgtPeppersName")public CompactDisc sgtPeppers(){    return new SgtPeppers();}@Bean(name="cDPlayer")public MediaPlayer  cDPlayer(){    return new CDPlayer(sgtPeppers());}@Bean(name="cDPlayer2")public MediaPlayer  cDPlayer2(){    return new CDPlayer(sgtPeppers());}

在实际生活里一个CD是不能同时放到两个播放器里的,这也是正常java方法调用的时候的实际意义。但是JavaConfig配置类中默认情况下Spring中的bean都是单例的,所以在这两个CDPlayer bean创建完全相同的SgtPeppers实例。

        @Bean(name="cDPlayer")public CDPlayer  cDPlayer(){return new CDPlayer(sgtPeppers());}@Bean(name="cDPlayer2")public CDPlayer  cDPlayer2(CompactDisc cd){return new CDPlayer(cd);}@Bean(name="cDPlayer3")public CDPlayer  cDPlayer3(CompactDisc cd){CDPlayer cdPlayer = new CDPlayer(cd);cdPlayer.setCd(cd);return cdPlayer;}
这三种方法都可以为CDPlayer装配所依赖的CompactDisc的bean。

       3).通过XML装配bean

        俗话说“工欲善其事,必先利其器”,在学习之前我们现在eclipse上安装一个插件:spring-tool-suite。

         (1)登陆网站:https://spring.io/tools/sts/all

         (2)查看自己的eclipse版本:Help-->About Eclipse-->Installation Details-->Eclipse Platform

         (3)将下图中的与eclipse版本对应的spring-tool-suite的网址复制到eclipse的Help-->Install New Software-->work with中并回车,勾选与Spring相关的插件,同时Contact  all update。。。这个选项不要勾选。

          

       (4)一路next即可。

       使用XML显式装配Bean需要创建一个XML文件,在这个文件的顶部声明多个XML模式文件,这些模式文件定了配置Spring的XML标签。<beans>是配置文件的根元素,我们可以简单的声明一个简单的bean:<bean class="包名.类名"></bean>。如果要定义它的ID:<bean id="ID编号" class="包名.类名"></bean>,如果不定义id的话,默认就是包名.类名。以上面的例子为例使用XML配置文件如何将被依赖对象装配到需要它的bean中:1)使用构造器注入:<bean id="" class=""><constructor-arg ref="被依赖对象的ID名"></bean>在Spring3.0之后可以通过c-命名空间来实现,c:为前缀,-ref为后缀,cd是构造器参数的名字,两者存在微小的差别,前者繁琐但是功能相对多些后者相反。   

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:c="http://www.springframework.org/schema/c"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="       http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd       http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context-4.0.xsd       http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"><bean id="cDPlayer" c:cd-ref="compactDisc" class="action.spring.study.second.CDPlayer"></bean><bean id="cDPlayer"  class="action.spring.study.second.CDPlayer">      <constructor-arg ref="compactDisc"></constructor-arg></bean><bean id="compactDisc" c:title="判断输了" c:artist="输了" class="action.spring.study.second.BlankDisc"></bean></beans>
如果默认要将构造器参数设置为空可以这么写<constructor-arg ><null/></constructor-arg>,如果是剩余的一个构造器参数是一个集合可以这么写:

<constructor-arg>     <list>           <value></value>           <value></value>           <ref bean="bean的id名字"/>        </list></constructor-arg>
如果是剩余的一个构造器参数是一个Set可以这么写:

<constructor-arg>     <set>           <value></value>           <value></value>           <ref bean="bean的id名字"/>        </set></constructor-arg>
上面事例中我们讨论的是xml显式装配bean中通过构造器注入,下面我们通过setter注入(也叫属性注入):

<bean id="cDPlayer" class="action.spring.study.second.CDPlayer">   <property name="cd"  ref="compactDisc"/></bean>
什么时候使用构造器注入?什么时候使用setter注入呢?对于强依赖使用构造器注入,对于可选性依赖使用属性注入。强依赖我的理解就是如果这个bean的某个方法依赖于另外一个bean,那么这个就是强依赖,我们使用构造器注入,反之使用属性注入。对于属性注入我们可以使用p-命名空间,代码如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:p="http://www.springframework.org/schema/p"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="       http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd       http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context-4.0.xsd       http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"><bean id="cDPlayer" class="action.spring.study.second.CDPlayer" p:cd-ref="compactDisc" p:title="String类型的属性值"></bean></beans>
但是这种方式我们是没法来装配集合的,所以xml提供了util-命名空间,代码如下:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:c="http://www.springframework.org/schema/c"xmlns:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="       http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd       http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context-4.0.xsd       http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"><bean id="compactDisc" c:title="判断输了" c:artist="输了" class="action.spring.study.second.BlankDisc"></bean><bean id="compactDisc2" p:title="title value" p:listValue-ref="firstList" class="action.spring.study.second.BlankDisc"></bean><util:list id="firstList">     <value>one</value>     <value>two</value></util:list></beans>
       4)导入和混合配置
       一、在JavaConfig中引入XML配置
       假设我们的JavaConfig太笨重,里面定义的bean颇多。我们上面的例子中JavaConfig中只有两个bean,我们幻想已经很多的情况下,一种处理方式是将其中一个bean比如BlankDisc从CDPlayerConfig中分离开定义到自己的JavaConfig中-CDConfig。一种方法就是在CDPlayerConfig中使用@Import注解导入CDConfig。具体使用方法:

     @Import(CDConfig.class)     public  class   CDPlayerConfig{     }
      或者在新的JavaConfig中使用@Import将两个config类导入进来:

     @Import({CDConfig.class,CDPlayerConfig.class})     public  class   NewConfig{     }
        二、在XML配置中引入JavaConfig
        同上,如果我们要拆分XML配置文件我们只需要加入如下标签:<import resource="cd-spring.xml"/>
       
        如果想引入JavaConfig添加如下标签:<bean class="JavaConfig全名"/>

综述:不管使用XML还是JavaConfig通常会创建一个根配置,这样就可以将所有的JavaConfig和XML组合起来。     

如果BlankDisc配置在cd-spring.xml中呢?我们可以使用@ImportResource("cd-spring.xml")

1 0
原创粉丝点击