zoukankan      html  css  js  c++  java
  • (二)《Spring实战》——Spring核心

    第二章:装配Bean

    在Spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组件。

    创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。

    1. Spring配置的可选方案

      Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。但是,作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起。当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制

    • 在XML中进行显式配置。
    • 在Java中进行显式配置。
    • 隐式的bean发现机制和自动装配。

    Spring有多种可选方案来配置bean,这是非常棒的,但有时候你必须要在其中做出选择。

      这方面,并没有唯一的正确答案。你所做出的选择必须要适合你和你的项目。而且,谁说我们只能选择其中的一种方案呢?Spring的配置风格是可以互相搭配的,所以你可以选择使用XML装配一些bean,使用Spring基于Java的配置(JavaConfig)来装配另一些bean,而将剩余的bean让Spring去自动发现。

      即便如此,我的建议是尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使XML。

    2. 自动化装配bean

      尽管显式装配技术(借助Java和XML来进行Spring装配)非常有用,但是在便利性方面,最强大的还是Spring的自动化配置。

    Spring从两个角度来实现自动化装配:

    • 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
    • 自动装配(autowiring):Spring自动满足bean之间的依赖。

      为了阐述组件扫描和装配,我们需要创建几个bean,它们代表了一个音响系统中的组件。首先,要创建CompactDisc 类,Spring会发现它并将其创建为一个bean。然后,会创建一个CDPlayer 类,让Spring发现它,并将CompactDisc bean注入进来。

      2.1 创建可被发现的bean

    CompactDisc接口在Java中定义了CD的概念

    package soundsystem;
    
    public interface CompactDisc {
      void play();
    }

      CompactDisc 的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。

    带有@Component注解的CompactDisc实现类SgtPeppers

    package soundsystem;
    import org.springframework.stereotype.Component;
    
    @Component
    public class SgtPeppers implements CompactDisc {
    
      private String title = "Sgt. Pepper's Lonely Hearts Club Band";  
      private String artist = "The Beatles";
      
      public void play() {
        System.out.println("Playing " + title + " by " + artist);
      }
    }

      和CompactDisc 接口一样,SgtPeppers 的具体内容并不重要。你需要注意的就是SgtPeppers 类上使用了@Component 注解。这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。没有必要显式配置SgtPeppers bean,因为这个类使用了@Component 注解,所以Spring会为你把事情处理妥当。

      不过,组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component 注解的类,并为其创建bean。

    @ComponentScan注解启用了组件扫描

    package soundsystem;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan
    public class CDPlayerConfig { 
    }

      如果没有其他配置的话,@ComponentScan 默认会扫描与配置类相同的包。因为CDPlayerConfig 类位于soundsystem 包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component 注解的类。这样的话,就能发现CompactDisc ,并且会在Spring中自动为其创建一个bean。

      如果你更倾向于使用XML来启用组件扫描的话,那么可以使用Spring context 命名空间的<context:component-scan> 元素。

    通过XML启用组件扫描

    <?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:context="http://www.springframework.org/schema/context"
      xmlns:c="http://www.springframework.org/schema/c"
      xmlns:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
      <context:component-scan base-package="soundsystem" />
    
    </beans>

    测试组件扫描能够发现CompactDisc

    package soundsystem;
    
    import static org.junit.Assert.*;
    
    import org.junit.Rule;
    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);
      }
    
    }

      CDPlayerTest 使用了Spring的SpringJUnit4ClassRunner ,以便在测试开始的时候自动创建Spring的应用上下文。注解@ContextConfiguration 会告诉它需要在CDPlayerConfig 中加载配置。因为CDPlayerConfig 类中包含了@ComponentScan ,因此最终的应用上下文中应该包含CompactDisc bean。

      测试代码中有一个CompactDisc 类型的属性,并且这个属性带有@Autowired 注解,以便于将CompactDisc bean注入到测试代码之中。

    2.2 为组件扫描的bean命名

      Spring应用上下文中所有的bean都会给定一个ID。在前面的例子中,尽管我们没有明确地为SgtPeppers bean设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers ,也就是将类名的第一个字母变为小写。

      如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传递给@Component 注解。

      还有一种方式是使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named 注解来为bean设置ID。(不常用)

     

    2.3  设置组件扫描的基础包

      为了指定不同的基础包,你所需要做的就是在@ComponentScan 的value属性中指明包的名称:

      要表明你所设置的是基础包,那么你可以通过basePackages属性进行配置:

     

      要设置多个基础包的话,只需要将basePackages 属性设置为要扫描包的一个数组即可:

      除了将包设置为简单的String类型(缺点类型不安全【not type-safe】)之外,@ComponentScan 还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:

      可以看到,basePackages 属性被替换成了basePackageClasses 。同时,我们不是再使用String 类型的名称来指定包,为basePackageClasses 属性所设置的数组中包含了类。这些类所在的包将会作为组件扫描的基础包。

    2.4 通过为bean添加注解实现自动装配

      简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired 注解。

      如下,构造器上添加了@Autowired 注解,这表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc 类型的bean。

    package soundsystem;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CDPlayer implements MediaPlayer {
      private CompactDisc cd;
    
      @Autowired
      public CDPlayer(CompactDisc cd) {
        this.cd = cd;
      }
    
      public void play() {
        cd.play();
      }
    }

      @Autowired 注解不仅能够用在构造器上,还能用在属性的Setter方法上。

    @Autowired
    public setCompactDisc(CompactDisc cd) {
        this.cd = cd;
    }

      @Autowired 注解可以用在类的任何方法上。

    @Autowired
    public insertCompactDisc(CompactDisc cd) {
        this.cd = cd;
    }

      不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。

      如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowiredrequired 属性设置为false

    @Autowired(required=false)
    public CDPlayer(CompactDisc cd) {
        this.cd = cd;
    }

      将required 属性设置为false 时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但是,把required 属性设置为false 时,你需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现NullPointerException

      @AutowiredSpring特有的注解。如果你不愿意在代码中到处使用Spring的特定注解来完成自动装配任务的话,那么你可以考虑将其替换为@Inject

      @Inject 注解来源于Java依赖注入规范,该规范同时还为我们定义了@Named 注解。在自动装配中,Spring同时支持@Inject@Autowired 。尽管@Inject@Autowired 之间有着一些细微的差别,但是在大多数场景下,它们都是可以互相替换的。

     2.5 验证自动装配

    package soundsystem;
    
    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 CDPlayerTest {
    
      //测试代码使用StandardOutputStreamLog 基于控制台的输出编写断言
      @Rule
      public final StandardOutputStreamLog log = new StandardOutputStreamLog();
    
      //将CDPlayer bean注入到测试代码的player 成员变量之中(它是更为通用的MediaPlayer 类型)
      @Autowired
      private MediaPlayer player;
      
      @Autowired
      private CompactDisc cd;
      
      @Test
      public void cdShouldNotBeNull() {
        assertNotNull(cd);
      }
    
      @Test
      public void play() {
        player.play();
        assertEquals(
            "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles
    ", 
            log.getLog());
      }
    
    }

    3. 通过Java代码装配bean

      在进行显式配置时,JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。

      同时,JavaConfig与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。

    3.1 创建配置类

    package soundsystem;import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class CDPlayerConfig {
    }

      创建JavaConfig类的关键在于为其添加@Configuration 注解,@Configuration 注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。

    3.2 声明简单的bean

      要在JavaConfig中声明bean ,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean 注解。

     @Bean
      public CompactDisc compactDisc() {
        return new SgtPeppers();
      }

      默认情况下,bean的ID与带有@Bean 注解的方法名是一样的。当然也可通过name属性自定义。

     @Bean(name="lonelyHeartsClubBand")
      public CompactDisc compactDisc() {
        return new SgtPeppers();
      }

    3.3 借助JavaConfig实现注入

      可以使用上面的代码,通过调用方法来引用bean,但最常用的方法是下面这种:

    @Bean
      public CDPlayer cdPlayer(CompactDisc compactDisc) {
        return new CDPlayer(compactDisc);
      }

      在这里,cdPlayer() 方法请求一个CompactDisc 作为参数。当Spring调用cdPlayer() 创建CDPlayer bean的时候,它会自动装配一个CompactDisc 到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer() 方法也能够将CompactDisc 注入到CDPlayer 的构造器中,而且不用明确引用CompactDisc@Bean 方法。

    4. 通过xml装配bean

    4.1 创建xml配置规范

      创建新的配置规范。在使用JavaConfig的时候,这意味着要创建一个带有@Configuration 注解的类,而在XML配置中,这意味着要创建一个XML文件,并且要以<beans> 元素为根。

    <?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:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">
    
    </beans>

    4.2 声明一个简单的bean

    <bean id="compactDisc"  class="soundsystem.SgtPeppers" />

    4.3 借助构造器注入初始化bean

    构造器注入,有两种基本的配置方案可供选择:

    • <constructor-arg> 元素
    • 使用Spring 3.0所引入的c-命名空间

    4.3.1 构造器注入bean引用

    <constructor-arg >使用ref 来引入其他bean

           

    c-命名空间

           

    4.3.2 将字面量注入到构造器中

    <constructor-arg>使用value 属性表明给定的值要以字面量的形式注入到构造器之中。

    c-命名空间

      ①引用构造器参数的名字

       

      ②通过参数索引装配相同的字面量值

       

    4.3.3 装配集合

     

    4.4 设置属性

    引用bean

    <property >通过ref引用bean

     

    p-命名空间

     

    将字面量注入到属性中

     <property> 使用value 属性设置属性值

     

    p-命名空间

     

    不能使用p-命名空间来装配集合,装配集合可使用<util:list>

     

     现在,我们能够像使用其他的bean那样,将磁道列表bean注入到BlankDisc bean的tracks 属性中

     

    5. 导入和混合配置

      在自动装配时,它并不在意要装配的bean来自哪里。自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。

     5.1 在JavaConfig中引用XML配置

      假设BlankDisc 定义在名为cd-config.xml 的文件中,该文件位于根类路径下,那么可以修改SoundSystemConfig ,让它使用@ImportResource 注解,如下所示:

      

      两个bean——配置在JavaConfig中的CDPlayer 以及配置在XML中BlankDisc ——都会被加载到Spring容器之中。因为CDPlayer 中带有@Bean 注解的方法接受一个CompactDisc 作为参数,因此BlankDisc 将会装配进来,此时与它是通过XML配置的没有任何关系。

    5.2 在XML配置中引用JavaConfig

      在XML中,我们可以使用<import >元素来拆分XML配置。

      

  • 相关阅读:
    〖Python〗-- Tornado自定义session组件
    〖Python〗-- Tornado异步非阻塞
    〖Python〗-- Tornado基础
    〖Python〗-- Flask
    〖Python〗-- 设计模式
    〖Python〗-- Celery分布式任务队列
    〖Demo〗-- CosPlayPermission
    05——wepy框架中的一些细节
    04——wepy框架搭建
    03——微信小程序官方demo讲解——page部分
  • 原文地址:https://www.cnblogs.com/zjfjava/p/8448963.html
Copyright © 2011-2022 走看看