基于spring DI的三种注入方式分析

一.前言: IOC(控制反转)与DI(依赖注入)

Spring框架对Java开发的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反转)和AOP,平时使用最多的就是其中的IOC,我们通过将组件交由Spring的IOC容器管理,将对象的依赖关系由Spring控制,避免硬编码所造成的过度程序耦合。

在讲依赖注入之前,我觉得有必要了解一下IOC(控制反转)与DI(依赖注入)的关系,在这篇文章中有详细的介绍:spring IOC 与 DI。

二.DI的三种常见注入方式

DI的三种常见注入方式为:setter注入、构造器注入和基于注解的注入(也叫field注入),下面来分别讲讲他们的特点。

2.1 基于注解注入

首先来看一下它的实现:

@RestController
@RequestMapping("/annotation")
public class AnnotationController {
    @Autowired
    private DiService diService;

    @GetMapping("/test001")
    public String test001() {
        return diService.test001("annotation");
    }
}

这种方式应该是目前最常见的注入方式了,原因很简单:

1、注入方式非常简单:加上@Autowired注解,加入要注入的字段,即可完成。

2、使得整体代码简洁明了,看起来美观大方。

在介绍注解注入的方式前,先简单了解bean的一个属性autowire,autowire主要有三个属性值:constructor,byName,byType。

  • constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
  • byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
  • byType:查找所有的set方法,将符合符合参数类型的bean注入。

下面进入正题:

注解方式注册bean:

在以前的开发中,我们主要使用四种注解注册bean,每种注解可以任意使用,只是语义上有所差异:

  1. @Component:可以用于注册所有bean
  2. @Repository:主要用于注册dao层的bean
  3. @Controller:主要用于注册控制层的bean
  4. @Service:主要用于注册服务层的bean

随着springboot的流行,@Bean注解也逐渐的被我们使用起来。Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。

注解方式注入依赖(主要有两种):

  • @Resource :java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
  • @Autowired :spring注解,默认是以byType的方式去匹配类型相同的bean,可以结合@Qualifier 注解根据byName方式匹配。

关于他们的具体用法与区别,因为内容比较多,所以写在另一篇博客中,请见:@Autowired 和 @Resource 详解

2.2 构造器注入

老规矩,先上代码示例:

@RestController
@RequestMapping("/constructor")
public class ConstructorController {
    private final DiService diService;
    private final String result;
    public ConstructorController(DiService diService) {
        this.diService = diService;
        this.result = diService.test001("constructor");
    }

    @GetMapping("/test001")
    public String test001() {
        return diService.test001(this.result);
    }
}

这里有一个问题,如果只有一个有参数的构造方法并且参数类型与注入的bean的类型匹配,那就会注入到该构造方法中。如果有多个有参数的构造方法并且每个构造方法的参数列表里面都有要注入的属性,那userDaoJdbc会注入到哪里呢?

在Spring4.x版本中推荐的注入方式就是这种,相较于上面的field注入方式而言,就显得有点难看,特别是当注入的依赖很多(5个以上)的时候,就会明显的发现代码显得很臃肿。对于从field注入转过来+有强迫症的同学来说,简直可以说是石乐志 ,但是为啥spring官方还会这么推荐呢?

官方文档里是这么说的:

The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.

翻译一下就是:Spring团队通常提倡构造函数注入,因为它允许将应用程序组件实现为不可变的对象,并确保所需的依赖不为空。此外,注入构造函数的组件总是以完全初始化的状态返回给客户机(调用)代码。

简单解释一下:

  • 不可变的对象:其实说的就是final关键字,这里不再多解释了。
  • 依赖不为空:省去了我们对其检查。当要实例化ConstructorController的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。这样就可以保证不会为空。
  • 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法,这里不详细展开。)。所以返回来的都是初始化之后的状态。

与注解方式注入相比,构造器注入可复用性高,如果使用field注入,缺点显而易见,对于IOC容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。而且将一直是个潜在的隐患,因为你不调用将一直无法发现NPE的存在。

相对于注解注入,构造器注入可以防止循环依赖的问题,若如下代码:

public class A {
    @Autowired
    private B b;
}

public class B {
    @Autowired
    private A a;
}

如果使用构造器注入,在spring项目启动的时候,就会抛出:

BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?

从而提醒你避免循环依赖,如果是注解注入的话,启动的时候不会报错,在使用那个bean的时候才会报错。

2.3 setter注入

这是在spring3.x出来的时候,官方推荐的注入方式,但是在spring4.x以后就没有见它推荐了,而且在实际开发中已经很少能见到这种注入方式了。

下面来看一下它的实现:

@RestController
@RequestMapping("/setter")
public class SetterController {
    private DiService diService;
    @Autowired
    public void setDiService(DiService diService) {
        this.diService = diService;
    }

    @GetMapping("/test001")
    public String test001() {
        return diService.test001("setter");
    }
}

试想一下,一旦需要注入的组件很多,那我们会累死的,所以大家都不喜欢用它也是情理之中的事情。

这里有一点需要注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。

总结

这么多的依赖注入方式,我们应该怎么选择呢?那种方式最好呢?

其实,有句古话说的很对,合适自己的才是最好的,我们需要看情况来选择使用哪种注入方式。

使用构造器注入的好处:

  1. 保证依赖不可变(final关键字)
  2. 保证依赖不为空(省去了我们对其检查)
  3. 保证返回客户端(调用)的代码的时候是完全初始化的状态
  4. 避免了循环依赖
  5. 提升了代码的可复用性

另外,当有一个依赖有多个实现的使用,推荐使用注解方式注入的方式来指定注入的类型或name,使用setter注入指定类型。这是spring官方博客对setter注入方式和构造器注入的比较

谢谢大家看完了,以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如果有描述不对的地方欢迎指正,与大家共同进步!

(0)

相关推荐

  • Spring:bean注入--Set方法注入

    目录 Set 方法注入 总结 Set 方法注入 1.新建一个空的 maven项目. 2.导入依赖 properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!--这里是java 版本号--> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target

  • 浅谈SpringBoot @Autowired的两种注入方式

    Autowired有两种注入方式 by type by name 默认使用的是byType的方式向Bean里面注入相应的Bean.例如: @Autowired private UserService userService; 这段代码会在初始化的时候,在spring容器中寻找一个类型为UserService的bean实体注入,关联到userService的引入上. 但是如果UserService这个接口存在多个实现类的时候,就会在spring注入的时候报错,例如: public class Us

  • 使用springMVC通过Filter实现防止xss注入

    springMVC Filter防止xss注入 跨站脚本工具(cross 斯特scripting),为不和层叠样式表(cascading style sheets,CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS. 恶意攻击者往web页面里插入恶意scriptScript代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的. 防止XSS攻击简单的预防就是对Request请求中的一些参数去掉一些比较敏感的脚本命令. 原本是打算通过springMVC的H

  • SpringBoot使用@Value实现给静态变量注入值

    SpringBoot中使用@Value()只能给普通变量注入值,不能直接给静态变量赋值 例如 application-dev.properties 配置文件有如下配置: 给普通变量赋值时,直接在变量声明之上添加@Value()注解即可,如下所示: 当要给静态变量注入值的时候,若是在静态变量声明之上直接添加@Value()注解是无效的,例如: 虽然没有编译和运行上的报错,经调试可知这种注解方式mailUsername.mailPassword.mailHost的值都是null,也就是说直接给静态变

  • Spring对静态变量无法注入的解决方案

    Spring对静态变量无法注入 问题 今天在学习的过程中想写一个连接和线程绑定的JDBCUtils工具类,但测试时发现一直报空指针异常,上网查了之后Spring并不支持对静态成员变量注入,所以光试用@Autowired肯定是不行的. 可是我们编写工具类时肯定是要使用静态变量和方法的,我总结一下我用过可以实现对静态成员变量注入的方法. @Component public class JDBCUtils { @Autowired private static ComboPooledDataSourc

  • 基于spring DI的三种注入方式分析

    一.前言: IOC(控制反转)与DI(依赖注入) Spring框架对Java开发的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反转)和AOP,平时使用最多的就是其中的IOC,我们通过将组件交由Spring的IOC容器管理,将对象的依赖关系由Spring控制,避免硬编码所造成的过度程序耦合. 在讲依赖注入之前,我觉得有必要了解一下IOC(控制反转)与DI(依赖注入)的关系,在这篇文章中有详细的介绍:spring IOC 与 DI. 二.DI的三种常见注入方

  • Spring Bean三种注入方式详解

    在Spring容器中为一个bean配置依赖注入有三种方式: 使用属性的setter方法注入  这是最常用的方式: 使用构造器注入: 使用Filed注入(用于注解方式). Field注入是最常见的一种方式,可以采用 @Autowired 对Bean类的接口进行初始化,代码如下 @ContextConfiguration({"/META-INF/spring/amazing-base.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public

  • Java spring的三种注入方式详解流程

    目录 设置Spring的作用域 自动注入 @Primary Qualifier @ComponentScan不同的配置对性能的影响 懒加载 三种注入方式 字段注入(IDEA 会提示不推荐) 字段注入的bean类外部不可见 循环依赖问题 构造器注入(官方推荐) set方法注入 设置Spring的作用域 或者使用枚举值设置 单例和多里使用场景 自动注入 @Primary 一个接口有多个实现被spring管理吗,在依赖注入式,spring会不知道注入哪个实现类就会抛出NoUniqueBeanDefin

  • spring与mybatis三种整合方法

    1.采用MapperScannerConfigurer,它将会查找类路径下的映射器并自动将它们创建成MapperFactoryBean. spring-mybatis.xml: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3

  • spring IOC中三种依赖注入方式

    一.Spring IOC(依赖注入的三种方式): 1.Setter方法注入. 2.构造方法注入. 使用构造方法,注入bean值. 关键代码: public UserServiceImpl(UserDao dao) { this.dao=dao; } <bean id="service" class="service.impl.UserServiceImpl"> <constructor-arg><ref bean="dao&q

  • 详解Spring中bean的几种注入方式

    首先,要学习Spring中的Bean的注入方式,就要先了解什么是依赖注入.依赖注入是指:让调用类对某一接口的实现类的实现类的依赖关系由第三方注入,以此来消除调用类对某一接口实现类的依赖. Spring容器中支持的依赖注入方式主要有属性注入.构造函数注入.工厂方法注入.接下来将为大家详细介绍这三种依赖注入的方式以及它们的具体配置方法. 1.属性注入 属性注入即通过setXXX( )方法注入bean的属性值或依赖对象.由于属性注入方式具有可选择性和灵活性高的特点,因此它也是实际开发中最常用的注入方式

  • Spring AOP拦截-三种方式实现自动代理详解

    这里的自动代理,我讲的是自动代理bean对象,其实就是在xml中让我们不用配置代理工厂,也就是不用配置class为org.springframework.aop.framework.ProxyFactoryBean的bean. 总结了一下自己目前所学的知识. 发现有三种方式实现自动代理 用Spring一个自动代理类DefaultAdvisorAutoProxyCreator: <bean class="org.springframework.aop.framework.autoproxy.

  • Spring中Bean的三种实例化方式详解

    目录 一.环境准备 二.构造方法实例化 三.分析Spring的错误信息 四.静态工厂实例化 4.1 工厂方式创建bean 4.2 静态工厂实例化 五.实例工厂与FactoryBean 5.1 环境准备 5.2 实例工厂实例化 5.3 FactoryBean的使用 六.bean实例化小结 一.环境准备 准备开发环境 创建一个Maven项目 pom.xml添加依赖 resources下添加spring的配置文件applicationContext.xml 最终项目的结构如下: 二.构造方法实例化 在

  • 基于maven的三种packaging方式

    maven的三种packaging方式 pom是maven依赖文件 jar是java普通项目打包 war是java web项目打包 pom:打出来可以作为其他项目的maven依赖,在工程A中添加工程B的pom,A就可以使用B中的类.用在父级工程或聚合工程中.用来做jar包的版本控制. jar包:通常是开发时要引用通用类,打成jar包便于存放管理.当你使用某些功能时就需要这些jar包的支持,需要导入jar包. war包:是做好一个web网站后,打成war包部署到服务器.目的是节省资源,提供效率.

  • 猜你不知道Spring Boot的几种部署方式(小结)

    引言 本文主要讲的是spring boot的五种部署方式,里面是否有你不知道的呢,如果有欢迎评论留言哦,一起交流探讨哦!!! 可以使用各种方法将Spring Boot应用程序部署到生产系统中.在本文中,我们将通过以下5种方法逐步部署Spring Boot应用程序: 在Java Archive(JAR)中作为独立应用程序进行部署, 将Web应用程序存档(WAR)部署到servlet容器中, 在Docker Container中部署, 在NGINX Web服务器后面部署 - 直接设置, 部署在NGI

随机推荐