《Spring实战》-第二章:Bean的装配(3)-自动化装配

慢慢来比较快,虚心学技术

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

Spring提供三种Bean装配机制:

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

一、什么是自动化装配?

很显然,通过前两篇文章的描述,我们可以发现,Spring的配置方式可以很简洁,也可以也很复杂,JavaConfig配置方式使用大量的注解替代了XML中的配置,那么,基于JavaConfig的基础之上,是否可以再次封装或简化配置呢?

Spring实现了纯注解配置的自动化隐式装配,所谓的隐式装配就是不需要像XML和JavaConfig一样去为每一个Bean创建<bean>节点或再配置类中为每个Bean做注解,而是通过一些特殊的注解实现控制

其中包含两个概念:

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

二、如何实现自动化装配

ⅰ.四个基本注解

@Autowired :标记于属性,方法等,自动装配的关键注解,依赖注入的表现,该注解可以自动寻找并从Spring容器中提取使用该注解的bean并注入到对应的属性中去

@Component :标记于类,标明当前类是一个可被扫描的组件

@ComponentScan :标记于配置类,开启组件注解扫描

@Configuration :标记于配置类,标明当前类是一个配置类

ⅱ.基本实现:

①定义基本接口CDPlayer

public interface CDPlayer {

    /**
     * 定义方法播放CD
     *  @param
     *       
     * @return void
     *       
     * @author lai.guanfu 2019/2/27
     * @version 1.0
     **/
    void playCD();

}

②定义基本类CDBean

@Data//Data是lobok的注解,自动添加setter和getter方法等
@Component//将当前类定义为可扫描的组件
public class CDBean {
    /**
     * 定义CD名
     */
    private String title="The World!";

    /**
    * 定义CD作者
    */
    private String author="Mr.D";
}

③定义实现类CDPlayerImpl,并将CDBean作为属性注入

@Component//将当前类定义为可扫描的组件
public class CDPlayerImpl implements CDPlayer {
    /**
     * 从Spring容器注入CDBean-----依赖注入
     */
    @Autowired
    private CDBean cdBean;

    @Override
    public void playCD() {
        System.out.println("正在播放:"+cdBean.getTitle()+" by "+cdBean.getAuthor());
    }
}

④定义配置类CDConfig,并开启注解扫描

@ComponentScan()//开启注解扫描
@Configuration//指定当前类为配置类
public class CDConfig {}

⑤编写测试

此处使用Spring提供的测试类SpringJUnit4ClassRunner帮助测试,以便在测试开始的时候自动创建 Spring 的应用上下文

而@ContextConfiguration可以指定创建上下文的加载方式以及配置的位置等

@RunWith(SpringJUnit4ClassRunner.class)//辅助创建Spring应用上下文
@ContextConfiguration(loader = AnnotationConfigContextLoader.class,classes = {CDConfig.class})//指定创建上下文的加载方式以及配置的位置
public class AppTest 
{
    /**
     * 从Spring容器注入CdPlayer-----依赖注入
     */
    @Autowired
    private CDPlayer cdPlayer;

    /**
     * Rigorous Test :-)
     */
    @Test
    public void play()
    {
        //调用playCD方法测试是否自动装配及依赖注入成功
        this.cdPlayer.playCD();
    }
}

⑥测试结果,自动装配成功,依赖注入成功

正在播放:The World! by Mr.D

ⅲ.关注点:

① 提供另一种测试方式:通过上一篇文章中提及的AnnotationConfigApplicationContext上下文实现进行测试

public static void main(String[] args) {
    /**
     * 注解配置实现,同时指定配置类的位置,否则无法读取配置进行扫描,默认只有Spring自带的组件
     */
     ApplicationContext applicationContext = new AnnotationConfigApplicationContext(CDConfig.class);
     /**
      *获取应用上下文中的所有Bean名称
      */
     String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
     for (String className : beanDefinitionNames){
       System.out.println(className);
     }
     /**
      *从上下文(容器)中获取对应的Bean
      */
    CDPlayer cdPlayer = applicationContext.getBean(CDPlayer.class);
    /**
     *调用具体方法测试是否成功
     */
    cdPlayer.playCD();
}

测试结果:自动装配成功,依赖注入成功

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
CDConfig
app//当前测试类的名字
CDBean
CDPlayerImpl
正在播放:The World! by Mr.D

为装配的Bean命名

Spring 应用上下文中所有的 bean 都会给定一个 ID,默认是类名的首字母小写命名,我们可以为装配的Bean装配一个不一样的ID,在 @Component注解中有一个属性value,该值可指定bean在Spring容器中的ID,其在@Component注解的源码定义如下:

public @interface Component {

   /**
    * The value may indicate a suggestion for a logical component name,
    * to be turned into a Spring bean in case of an autodetected component.
    * @return the suggested component name, if any (or empty String otherwise)
    */
   String value() default "";

}

我们可以给CDPlayerImpl指定一个别的ID:cdPlayer

@Component(value = "cdPlayer")
public class CDPlayerImpl implements CDPlayer {

    @Autowired
    private CDBean cdBean;

    @Override
    public void playCD() {
        System.out.println("正在播放:"+cdBean.getTitle()+" by "+cdBean.getAuthor());
    }
}

此时执行上一测试代码结果中,CDPlayerImpl在容器中的ID类名将变成cdPlayer

另:可以使用@Name代替@Component注解为Bean命名,但是为了避免概念模糊,建议使用@Component注解

设置组件扫描的基础包

上述代码中,配置类CDConfig仅能扫描自身所在包及其子包的组件,能否指定扫描某个或多个包及其子包中的组件呢?

我们看到@ComponentScan注解的源码中定义的几个属性:

public @interface ComponentScan {

   /**
    * Alias for {@link #basePackages}.
    * <p>Allows for more concise annotation declarations if no other attributes
    * are needed &mdash; for example, {@code @ComponentScan("org.my.pkg")}
    * instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
    */
   @AliasFor("basePackages")
   String[] value() default {};

   /**
    * Base packages to scan for annotated components.
    * <p>{@link #value} is an alias for (and mutually exclusive with) this
    * attribute.
    * <p>Use {@link #basePackageClasses} for a type-safe alternative to
    * String-based package names.
    */
    @AliasFor("value")
    String[] basePackages() default {};

    /**
     * Type-safe alternative to {@link #basePackages} for specifying the packages
     * to scan for annotated components. The package of each class specified will be scanned.
     * <p>Consider creating a special no-op marker class or interface in each package
     * that serves no purpose other than being referenced by this attribute.
     */
    Class<?>[] basePackageClasses() default {};
} 

从源码得知,我们可以通过配置basePackages和basePackageClasses属性来指定配置扫描的范围,其中:

basePackages指定组件扫描的包路径(使用{}包含的数组)

@ComponentScan(basePackages = {"com.my.spring","com.my.test"})//指定扫描com.my.spring包和com.my.test包及其子包下的组件
@Configuration
public class CDConfig {}

basePackageClasses:指定以该类数组所在包及其子包为组件扫描范围

@ComponentScan(basePackageClasses = {App.class})//指定扫描App.class所在包及其子包下的组件
@Configuration
public class CDConfig {}

注:通常,为了防止因包路径更改以及业务实体类更改等因耦合而产生的问题,我们通常会使用一个不具备任何业务意义的空接口作为扫描包的类

自动注入注解@Autowired的使用

可以用在属性,构造函数,setter以及任何一个普通方法中

@Component(value = "cdPlayer")
public class CDPlayerImpl implements CDPlayer {
    //1.在属性注入
    @Autowired
    private CDBean cdBean;

    public CDPlayerImpl(){super()};

    //2.在构造函数中注入
    @Autowired
    public CDPlayerImpl(CDBean cdBean){super()};

    //3.在setter方法中注入
     @Autowired
    public void setCDBean(CDBean cdBean){this.cdBean = cdBean};

    //4.在普通方法中使用
    @Autowired
    @Override
    public void playCD(CDBean cdBean) {
        System.out.println("正在播放:"+cdBean.getTitle()+" by "+cdBean.getAuthor());
    }
}

总结

1.自动化装配使用全注解方式替代XML和JavaConfig显式装配方式,方便简洁

2.自动化装配依赖于几个特殊注解:@Autowired,@Component,@ComponentScan和@Configuration

3.@Component注解可以将类定义为装配的组件,同时可以为改组件另起别名(ID)

4.@Configuration将使用该注解的当前类标注为配置类,@ComponentScan开启自动扫描,默认扫描当前配置类所在的包及其子包中含有或使用了@Component注解的类,可通过属性指定扫描范围

5.@Autowired注解可以用在属性,构造函数,setter以及任何一个普通方法中,是Spring依赖注入的核心注解

参考文档

【1】《Spring 实战(第 4 版)》·Craig Walls

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章