Spring实战——装配bean

前言:

  此系列文章为学习“Spring三剑客”之《Spring实战》的笔记,思维以及自己的感想和拓展,如需转载,请注明出处如有问题,请指教,谢谢!

一、Spring配置的可选方案:

1、Spring装配Bean的意义

  Spring容器负责创建应用程序中的Bean并通过DI(依赖注入)来协调各个Bean之间的合作,所以,作为开发人员,应该告诉Spring需要创建哪些Bean,并且创建各个Bean之间的关联关系,所以此过程我们成为Bean的装配;

2、Spring装配Bean的三种方法:

  1. 在XML中进行显式配置;
  2. 使用Java进行显式配置;
  3. 使用Spring容器进行隐式配置(使用Spring的自动装配和Bean的发现机制);

3、对于Spring配置的选择方案:

  在上述的三种方法中,选择哪种方法进行SpringBean的配置都是可以的,甚至在应用程序的开发的过程中此三种方法可以混合使用,所以选取哪种,完全基于开发者的喜好,但是在装配方法的选择上有一个规范,那就是:

装配方式的优先级:

                                                         自动装配 ——>  Java显示配置 ——> XML显示配置

                                                         (  显式  )  ——> (                      隐式                          )

二、自动化装配Bean:

1、创建可被发现的Bean

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

  1. 组件扫描(Component scanning):Spring会自动发现应用上下文中所创建的Bean(在使用注解的情况下);
  2. 自动装配(autowiring):Spring自动满足Bean之间的相互依赖;

 为了更好的展示Bean装配,我们使用了CD和CD播放机的示例去进行解释,首先,我们需要定义一个CD的接口,并为其提供了一个播放的方法:

package com.robin.soundSystem;

/**
 * @descrition CD类的接口
 * @author Robin
 * @date 2019-01-09
**/
public interface CompactDisc {
    
    void play();
}

  CompactDisc的具体内容并不重要,重要的是这个接口定义了CD的重要操作,那就是进行播放歌曲,它将CD播放器的任何实现与CD本身的耦合降至最低程度;有了CD的接口,那么我们下一步需要创建一个CompactDisc的具体实现,为了方便与现实进行关联,我们使用ColdPlay乐队的《Yellow》作为实现类的名称:

package com.robin.soundSystem;

/**
 * @descrition CD类的实现类,以ColdPlay乐队的歌曲Yellow为例子
 * @author Robin
 * @date 2019-01-09
**/
import org.springframtwork.steretype.Component;

@Component
public class YellowCD implement ComponentDisc{

    private String title = "Yellow";
    private String artist = "ColdPlay";
    
    public void play() {
        System.out.println("Playing " + this.title + " by " + this.artist);
    }
}

      和上述的ComponentDisc接口类一样,我们不需要关注YellowCD类具体实现了什么,在上述YellowCD类的定义中,我们可以看到使用了@Component注解,该注解的意义在于告诉Spring,该类(YellowCD.class)需要创建Bean,并且作为组件类投入到应用程序的使用当中,所以此处相对于基于Java和xml的显式配置来说,此基于Spring的自动配置很方便、很快捷的发挥了更大的作用;

      备注:我想具体查找一下@Component注解的详细介绍,但是网上大多数都是此回答:@component ——把普通pojo实例化到spring容器中,相当于配置文件中的 <bean id="" class=""/>,泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。(所以对于为啥此篇文章我们所用的注解不使用@Controller、@Service等,就做了一个良好的解释)

      但是仅仅配置了@Component注解并不能让Spring进行自动化配置,因此我们还缺一个Spring扫描需要进行配置的东西,称为Spring的组件扫描,因此我们需要定义一个CD播放器的类,并且在CD播放器这个类上面去开启自动扫描;

package com.robin.soundSystem;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @descrition CD播放器的类
 * @author Robin
 * @date 2019-01-09
**/
@Configuration
@ComponentScan
public class CDPlay() {

}

      看了CDPlay.class的类定义,一定会有人会疑惑,为什么@ComponentScan需要在CDPlay.class上进行标注,那么,这就引申出一点,@ComponentScan的作用范围:

@ComponentScan的作用范围:在Spring中启用组件扫描时,在默认的情况下(没有对该注解进行其他配置的情况下)扫描标注了该注解的类所在的包以及该包的所有子包下的所有带自动配置标注的注解;

      到现在为止,我们没有为@ComponentSacn设置任何属性,就像刚才所属的其作用范围一样,如果按照默认的,他仅仅只会根据当前配置类的所属的包来作为基础包(base-package)来扫描组件,但是,如果想扫描其他包或者扫描多个基础包呢???,那么就引申除了我们下一个需要说的一点:关于@ComponentScan的base-package的设定,所以,我总结了三点关于@ComponentScan的basePackage的配置:

@ComponentScan的三种basePackage的配置

  1. @ComponentScan——默认,表示当前包下和当前包下的所有子包中的注解组件;
  2. @ComponentScan(basePackage="XXX")或@ComponentScan(basePackage={"XXX1", "XXX2"})——表示配置单个或多个基础包的包名;
  3. @ComponentScan(basePackageClass=XXX.class)或@ComponentScan(basePackageClass={XXX1.class, XXX2.class})——表示配置单个或多个类所在的包为基础包;

      那么上述中所讲的三种基础包的配置方法中我们可以看出,@ComponentScan注解不仅支持通过包名来进行基础包的配置,也支持通过某类所在的包进行配置,那么问题来了,通过包名已经很好的能够去配置基础包了,那么为啥还要多一个通过类所在的包名进行基础包的配置呢,原因是因为上面可以看出通过包名进行配置的话包名的书写是通过"String"类型的数据去配置的,难免会出现包名书写错误的情况,相反通过类名去进行配置的话首先你的代码编写工具,例如IDEA会进行一个检验,检验你所书写的类名是否是项目中所拥有的类名,其次Java程序在编译的过程中JVM也会进行校验,所以我想估计是想减少代码的出错吧;

@ComponentScan是基于Java的一个配置,可能有些人在项目中很少看到使用@ComponentScan注解,相反看到最多的是基于XML的一个配置,如下代码:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="ioc.xsd">

<context:component-scan base-package="com.robin.soundSystem"/>

</beans>

且使用<context:component-scan>的元素和@ComponentScan注解的属性对应!

2、为组件扫描的Bean命名

Spring应用上下文中需要创建的所有Bean都会为其创建一个专属的ID,即使我们没有手动通过SetPeppers进行手动创建Bean的ID,Spring为所有Bean所创建的ID为将首字母小写的类名,那么我们在有需要手动为某个Bean创建特定ID的时候,可以进行如下操作,我们还是以CompactDisc的实现类YellowCD.class为例:

package com.robin.soundSystem;

import org.springframtwork.steretype.Component;

//注意此处,默认情况下Spring为YellowCD.class创建的ID名称为"yellowCD",此处我们手动将其改
//为"yellow"
@Component("yellow")
public class YellowCD implement ComponentDisc{
    ...
}

此外,还有另一种为Bean命名的方式,这种方式不适用@Component注解,而是使用Java依赖注入规范中所提供的@Named注解来为Bean设置ID,同理,我们以YellowCD.class为例子,并且将其ID设置为"yellow":

package com.robin.soundSystem;

import javax.inject.Named;

@Named("yellow")
public class YellowCD implement ComponentDisc{
    ...
}

Spring支持将@Named作为@Component注解的替代方案,两者之间有一些细微的差异,但是在大多数的场景中他们是可以相互替换的,但是估计在我们所见的项目中,使用@Component的很多,使用@Named基本上没有看到过(我是这样,不知道各位看官怎么说),我想其主要原因是@Named这个名字不容易让别人联想到是作为标定Bean的注解的。。。

3、通过为Bean添加注解实现自动装配

      上述我们讲到,通过@Component去标明一个组件,并且通过@ComponentScan去自动扫描需要创建的组件,然而装配的意思,从字面上讲,是指将零件按规定的技术要求组装起来,并经过调试、检验使之成为合格产品的过程,装配始于装配图纸的设计。(出自百度百科),通俗的使用我们上述CD与CDPlay的例子来讲,装配可以看做: 将已使用@Component标注并创建的YellowCD.class装配到已使用@Component标注并创建的CDPlay.class中,因此,我们将上面CDPlay.class代码重新进行编写,不再在该类上使用@ComponentScan,请看代码:

package com.robin.soundSystem;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;


@Component
public class CDPlay() {
    
    private CompactDisc cd;

    //编写在构造函数上
    @Autowired
    public CDPlay(CompactDisc cd) {
        this.cd = cd;
    }

    public void play() {
        cd.play();
    }
}

     上述就是所说的@Autowired,至于详细的原理类的东西在这本《Spring实战》中没有进行详细解答,我想也许是因为此书主要是注重实战的吧,至于@Autowired的详细源码解析,我这边推荐一篇博客(https://blog.csdn.net/sun576643307/article/details/78235311),大家可以详细去看看

      接下来我们回到正题,对于需要进行装配的类,加上上述的一种编写方式外,一共有四种编写方式,我们分别列出来并给予代码的解释:

@Autowired的四种编写方式:

  1. 编写在构造函数上;
  2. 编写在成员变量的定义上;
  3. 编写在Set方法上;
  4. 编写在普通方法上;

编写在成员变量的定义上:

package com.robin.soundSystem;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;


@Component
public class CDPlay() {
    
    //编写在成员变量上
    @Autowired
    private CompactDisc cd;

    ...
}

编写在Set方法上:

package com.robin.soundSystem;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;


@Component
public class CDPlay() {
    
    private CompactDisc cd;

    //编写在Set方法上
    @Autowired
    public void setCd(CompactDisc cd) {
        this.cd = cd;
    }

    ...
}

编写在普通方法上:

package com.robin.soundSystem;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;


@Component
public class CDPlay() {
    
    private CompactDisc cd;

    //编写在普通方法上
    @Autowired
    public void initCompactDisc(CompactDisc cd) {
        this.cd = cd;
    }

    ...
}

      值得注意的一点是,@Autowired的查询机制为:

@Autowired的查询机制

  • 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
  • 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
  • 如果查询的结果为空,那么会抛出异常。解决方法时,使用required=false;

         值得注意的是最后一点中所提及的required属性,将required设置为false时,Spring会尝试着为@Autowired进行Bean的查询与自动装配,如果没有查询到,那么Spring会将该Bean处于一个未装配的状态,所以说当我们把required设置为false的时候,一定做好null的判断,否则很容易出现NullPointerException;

    上述我们说到,@Autowired是属于Spring的注解,那么和该注解有着类似的作用的基于Java的两个注解分别是:@Resource和@Inject,那么我们就来大概说一下两者的一个区别比较:

@Resource注解:

     @Resource的作用和@Autowired类似,其主要区别在于@Autowired默认按照类型(type)进行自动注入,而@Resource默认按照名称(name)进行自动注入,@Resource有两个重要的属性,为name和type,按照字面上的意思而言也就是Bean的名称和Bean的类型,如果@Resource使用了name属性,那么它会使用按照名称自动注入的策略进行自动装配,同理,使用了type则是按照类型,如果两者都不指定,就通过反射机制使用名称进行自动装配;

@Resource的装配顺序

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常;
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常;
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常;
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;

@Inj

未完,每晚九点更新。。。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值