依赖注入(DI)

DI(Dependency Injection),Spring IoC 不是一种技术,而是一种思想,通过这种思想,能够指导我们设计出松耦合的程序代码。而Spring IoC这个思想的作用体现在两个方面,一是如何将Bean装配到容器中去以及如何从容器中获取Bean,二是如何解决Bean之间的依赖关系,换句话说,就是如果由IoC容器来管理依赖关系,当一个Bean需要依赖另外一个Bean时,IoC容器如何实现这样的依赖关系。

解决Spring中Bean之间的依赖的实现方式,在Spring的概念中就被称之为依赖注入(Dependency Injection,DI)。普遍认为的Spring依赖注入的实现方式有三种:构造方法注入、setter方法注入、注解注入。但,就我而言,我认为应该划分为两种形式——基于XML注入和基于注解注入,然后再细分为下面的形式:

基于XML的注入方式是我们最先学习和使用的方式,也是最熟悉的方式,就简单的做个介绍,举个例子。

  • 通过构造方法注入
public class UserServiceImpl implements UserService {    private UserDao userDao;    public UserServiceImpl(UserDao userDao) {        this.userDao = userDao;    }    /**继承自UserService的方法**/}

首先定义一个服务层UserServiceImpl,然后在其内部增加对dao层的引用userDao。

接下来就是添加一个构造方法public UserServiceImpl(UserDao userDao)以待Spring通过这个方法为userDao注入实例。

最后在Spring XML配置文件中注入相应的bean实例。

通过构造方法的注入,必须要注入类中具有对应的构造方法,若没有对应的构造方法,会出现报错。

  • 通过setter方法注入

修改UserServiceImpl.java为:

public class UserServiceImpl implements UserService {    private UserDao userDao;    public void setUserDao(UserDao userDao) {        this.userDao = userDao;    }    /**继承自UserService的方法**/}

再修改XML文件内容为:

这两种方式的区别在于,一、UserServiceImpl.java可以不用添加构造方法,但是必须存在一个无参构造方法(如public UserServiceImpl(),示例里面没写,是因为java默认会提供一个无参构造方法)以供Spring 容器注册生成Bean(如userService)。二、XML文件中,采用构造方法注入时,需要使用这对标签;而在setter方法注入时,使用标签。

在XML注入过程中,除了使用ref=""引用之外,还可以使用value=""设定具体的值,其效果和使用注解@Value差不多。

基于注解的依赖注入

@Autowired

  • 源码
@Target({ElementType.CONSTRUCTOR,         ElementType.METHOD,         ElementType.PARAMETER,         ElementType.FIELD,         ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Autowired {    boolean required() default true;}

@Autowired是基于注解的依赖注入的关键点,它的源码非常简单,只有一个参数request(),这个参数的作用是标识注入Bean是否一定要注入,也就是说,在Spring容器没有找到相应Bean时,如果其值为true,就会报出异常;如果其值为false,就不会出现异常,但在使用过程中,如果容器一直不对Bean进行注入,那么有可能出现空指针异常。

另外一点就是,源码当中的@Target所包含的参数正好就是基于注解的依赖注入的注入方式种类,@Target决定了@Autowired能够标注在哪些类型上面。

  • 通过构造方法注入
@Service("userService")public class UserServiceImpl implements UserService {    private UserDao userDao;    @Autowired    public UserServiceImpl(UserDao userDao) {        this.userDao = userDao;    }    /**继承自UserService的方法**/}

根据开发文档的说法,这种只有一个构造方法的情况,自Spring4.3以后,就不再需要添加@Autowired标注,也可以。但是,如果有多个构造方法时,是必须要对其中一个方法标注@Autowired,不然Spring会报出异常。

  • 通过setter方法注入
@Service("userService")public class UserServiceImpl implements UserService {    private UserDao userDao;    @Autowired    public void setUserDao(UserDao userDao) {        this.userDao = userDao;    }    /**继承自UserService的方法**/}
  • 通过字段注入
@Service("userService")public class UserServiceImpl implements UserService {    @Autowired    private UserDao userDao;    /**继承自UserService的方法**/}
  • 通过方法入参注入

上面三种注入方式,都是比较熟悉的就不再多做阐述了。重点说一下参数注入,其实方法入参注入方式感觉上是和构造方法、setter方法注入形式差不多,相当于将构造方法、setter方法上的注解@Autowired放到入参的位置。说起来可能有些抽象,直接看例子:

@Componentpublic class UserDaoImpl implements UserDao {    //简单返回一个User,模拟数据库查找过程    @Override    public User getUser(Long id, String name){        User user = new User();        user.setId(id);        user.setName(name);        user.setAccount("12345678911");        user.setPassword("******");        user.setOtherInfo("this is a test account");        return user;    }}
//UserService类@Service("userService")public class UserServiceImpl implements UserService {    private UserDao userDao;   public UserServiceImpl(@Autowired UserDao userDao,                          @Autowired User user) {       System.out.println("UserServiceImpl: "+user);       this.userDao = userDao;   }    @Override    public User getUser(Long id, String name){        return userDao.getUser(id,name);    }}
//简单的配置类//作用就是为标有@Componet(@Service也算)注解的类 生成Bean//同时 为@Autowired标识下的Bean(对象) 注入实例@Configuration@ComponentScanpublic class DIConfig {    //用于Service类中入参user的注入    @Bean    public User getUser(){        User u = new User();        u.setName("user inject into service");        return u;    }}
//测试类//注意:使用JUnit4测试时,如果需要使用@Autowired注入那么必须添加//@RunWith    标注使用Spring方式启动(或者SpringBootRunner)//@ContextConfiguration  扫描配置类@RunWith(SpringRunner.class)@ContextConfiguration(classes = DIConfig.class)public class DITest {    //如果不添加测试类上两个注解,会注入失败    @Autowired    private UserService userService;    @Test    public void testAutowired(){ System.out.println(userService.getUser(1L,"name"));    }}

运行测试方法之后就得到以下结果:

public UserServiceImpl(@Autowired UserDao userDao,@Autowired User user)中的输出结果:

public void testAutowired()测试方法中的输出结果:

注意这里public UserServiceImpl(@Autowired UserDao userDao,@Autowired User user)的入参

userDao是UserServiceImpl的字段,但user不是。也就是说,我们可以在构造方法中添加任意参数,只要是我们需要的,不一定要求该参数是类中属性字段。

此外还有需要注意的是,这里所说的方法,不是任意的方法,而是构造方法或setter方法,这种public void initService(@Autowired UserDao userDao)自定义的方法是无法完成注入的。

@Primary 和 @Qualifier

在上面的例子中,我们注入使用到的bean,都只是容器中只有一个Bean实例的情况。那么当容器当中出现多个同类型的Bean时,如何处理呢?

修改配置类代码如下:

@Configuration@ComponentScanpublic class DIConfig {    @Bean    public User getUser(){        User u = new User();        u.setName("this is user");        return u;    }    @Bean    public User getUser2(){        User u = new User();        u.setName("this is user2");        return u;    }}

修改测试类:

@RunWith(SpringRunner.class)@ContextConfiguration(classes = DIConfig.class)public class DITest {    @Autowired    private User user;    @Test    public void testAutowiredPriamry(){        System.out.println(user);    }}

当不做其他处理时,结果为:

因为有两个User Bean(getUser , getUser2 ,@Bean未注明的情况下,默认方法名为Bean Name)的存在,所以Spring无法确定使用那个进行注入。

修改方式:

  • 在@Bean中设置name,如@Bean(name="user"),当名字能够匹配上private User user;时,也能完成注入。
  • 将private User user改写成getUser或getUser2任意一个,也能完成注入。道理和上面一样,Spring首先会按照type进行匹配,如果无法匹配,再按照名字匹配,都匹配不上时,自然抛出异常。

除此之外呢,Spring为我们提供了两个注解来消除依赖注入时的歧义问题。

  • @Primary
@Target({ElementType.TYPE,    // 类、接口、枚举类型         ElementType.METHOD})// 方法@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Primary {}

@Primary是一个设定相同类型Bean优先级的注解,也就是说,一旦在某个类型上添加@Priamry,当注入时,没有明确指定Bean时,就会注入被@Priamry标识的Bean。

@Configuration@ComponentScanpublic class DIConfig {    @Primary    @Bean    public User getUser(){        User u = new User();        u.setName("this is user");        return u;    }    @Bean    public User getUser2(){        User u = new User();        u.setName("this is user2");        return u;    }}

比如上面这样,在getUser()上添加相应注解,测试方法也能正常运行。

但是这种方法的问题就在于@Priamry可以用在很多类上,如果同一类型有多个Bean被标注了@Primary,那么@Priamry就失去了应有的效果。

  • @Qualifier

因此,Spring又提供了@Qualifier这个注解,直接标注在@Autowired注入的Bean上,为其明确指定注入某个Bean。

@Target({ElementType.FIELD,          ElementType.METHOD,          ElementType.PARAMETER,          ElementType.TYPE,          ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Qualifier {    String value() default "";}

@Qualifier可以出现任何@Autowired能够出现的地方,与之配套使用。比如下面这样:

@RunWith(SpringRunner.class)@ContextConfiguration(classes = DIConfig.class)public class DITest {     //直接指定使用getUser2进行注入    @Autowired    @Qualifier("getUser2")    private User user;    @Test    public void testAutowiredPriamry(){        System.out.println(user);    }}

这两种注解都可以消除歧义,推荐使用@Bean(name="xxx")和@Qualifier(value="xxx")组合使用的方式`。但是如果开发环境中没有歧义的存在,自然也就不需要使用这些了。

当然,上面只是对于@Autowired一些常用介绍,如果想要了解更多,可以查看Annotation-based Container Configuration。这个参考文档当中有着更加详细、丰富的介绍。

总结

总得来说,Spring是如何实现IoC的呢?首先,Spring提供了一个获取和管理Bean的IoC容器。然后,再提供了一套依赖注入的机制去帮助IoC容器更好地管理各个Bean之间的依赖关系,从而更好地实现IoC的思想。一个Bean不可能完全脱离其他Bean的依赖关系而独立存在,当一个Bean需要其他Bean的引入才能初始化时,就需要依赖注入这个机制。

举例来说,假如存在一个A类想要去调用B接口的方法或者说需要B接口的一个实例。

传统的程序流程是,使用一个C类实现B接口,然后A类创建一个C类的实例,从而调用其方法。

在Spring的依赖注入过程中就变成了,A类只需要在自己的内部添加一个注入接口(广义上的接口,不是interface这个接口),这个接口可以是构造方法,也可以是setter方法或者说其他形式;同时添加一个对B接口的引用(private B b;)。

当真正需要生成A类的实例时,Spring IoC容器根据A类提供的接口,为其注入相应的Bean,而这个Bean可以是C类(class C implements B{}),也可以D类(class D implements B{})等等;具体是谁,根据Bean的装配策略和IoC容器中的Bean来确定,不再由开发人员管理。

依赖注入的三种方式_Spring IoC是如何进行依赖注入的相关推荐

  1. Spring系列之依赖注入的三种方式

    目录 一.依赖注入方式 1.使用属性的setXXX方法注入 2.构造函数注入 (1)按类型匹配入参type (2)按索引匹配入参index (3)联合使用类型和索引匹配入参[type和index一起使 ...

  2. 05.bean依赖注入的三种方式

    05.bean依赖注入的三种方式 1.概述 依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现. 在编写程序时,通过控制反转,把对象的创建交给 ...

  3. php依赖注入的三种方式

    控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度.其中最常见的方式叫做依赖注入(Dependency Inject ...

  4. spring依赖注入的三种方式以及优缺点

    spring依赖注入的三种方式以及优缺点 一.依赖注入的三种方式 1.通过构造器注入.(spring4.3之后,推荐使用) 2.通过setter注入.(spring4.3之前,推荐使用) 3通过fil ...

  5. Spring属性注入的三种方式(超详细)

    属性注入的三种方式 使用set方法进行注入 使用有参构造函数进行注入 使用p名称空间注入 首先了解下面两个名词的含义: IOC:控制反转(Inversion of Control,缩写为IoC),是面 ...

  6. Spring注入的三种方式

    Spring实例注入的三种方式: 1.属性注入,即使用注解注入. 2.set方法注入. 3.构造方法注入. 1.属性注入 使用@Autowired.@Resource或@Inject注解注入. 1.1 ...

  7. Spring注解依赖注入的三种方式的优缺点以及优先选择

    当我们在使用依赖注入的时候,通常有三种方式: 1.通过构造器来注入: 2.通过setter方法来注入: 3.通过filed变量来注入: 那么他们有什么区别吗?应该选择哪种方式更好? 代码示例: Con ...

  8. Spring依赖注入的三种方式(好的 坏的和丑的)

    关于spring bean三种注入方式的优缺点对比,翻译自Spring DI Patterns: The Good, The Bad, and The Ugly,水平有限,如有错误请指正. Sprin ...

  9. 依赖注入的三种方式_一起学Spring之三种注入方式及集合类型注入

    本文主要讲解Spring开发中三种不同的注入方式,以及集合数据类型的注入,仅供学习分享使用,如有不足之处,还请指正. 概述 Spring的注入方式一共有三种,如下所示: 通过set属性进行注入,即通过 ...

最新文章

  1. BZOJ2275[Coci2010]HRPA——斐波那契博弈
  2. Operations Manager 2012 SP1配置部署系列之(二) SCOM监控SCVMM
  3. XHTML基础问答-给初学者
  4. IDisposable 接口介绍
  5. P2P平台选择网关支付、第三方托管、第三方+银行联合托管有什么区别?
  6. Linkify 添加链接
  7. 咱中国女人太贪钱? 其实真不怨她们
  8. 软件测试和python那个号_软件测试:Java VS Python
  9. phpcms 轮播图 (posid推荐位 )调用 - 代码篇
  10. combo空间禁止手动输入_国产大空间MPV新选择 上汽大通新款G10国六版本上市
  11. 考前一个月,不,只剩29天
  12. PyCharm下解决Unresolved Reference问题
  13. 一款显示游戏计算机占用软件,玩游戏占用电脑内存多怎么办
  14. where条件中等值连接使用双竖杠影响SQL性能
  15. 【缺陷检测】基于matlab形态学液晶显示器表面缺陷检测【含Matlab源码 1304期】
  16. jdk13新特性(jdk18新特性)
  17. 远程 交换机 日志服务器,交换机怎么配置 日志发送日志服务器
  18. Qt qmake LIBS的使用指南
  19. 带有播放列表的网页播放器
  20. 咖啡豆是怎么生产出来的

热门文章

  1. js 跳转到 百度指定地址定位点
  2. 【6.18校内test】T1多项式输出
  3. 畅通工程(自己写的BFS,但后面想了下并查集更好更快)
  4. Windows 0day成功验证之ETERNALBLUE
  5. RHEL5.6更新yum源
  6. 第二轮冲刺-Runner站立会议06
  7. LeetCode - Remove Nth Node From End of List
  8. 当一个GameObject有两个Collider组件时,Physics Material不起作用
  9. 【转载】推荐5款超实用的.NET性能分析工具
  10. 小程序显示服务器开小差,小程序提交一直显示网络错误,网络开小差,请刷新重试,切换网络也上不去,请问是?...