草庐IT

Spring依赖注入的三种方式

厚积薄发ض 2023-04-04 原文

参考 :

面试突击77:Spring 依赖注入有几种?各有什么优缺点? - 掘金

目录

更加简单地从Spring中取出Bean对象(超级重要)

属性注入

属性注入的优点和缺点

setter注入

Setter注入的优缺点

构造方法注入

注意事项

构造方法注入的优缺点

官方建议


更加简单地从Spring中取出Bean对象(超级重要)

将Bean对象更加简单地从Spring中取出来,有三种方式,分别为 属性注入, setter注入,构造方法注入

我们可以回忆一下之前是怎么拿到一个对象的.

最初我们在没有学Spring之前就是直接new一个对象

学了Spring之后呢... 我们先要创建一个Spring上下文对象(context),通过context.getBean()方法指定id,类型来从Spring中取出Bean对象..

到现在我们就可以使用更加简单地方式来从Spring中取出Bean对象了.

我们分别来讲一下属性注入,Setter注入,构造方法注入 并分析出三种方式的优缺点,以及Spring官方推荐我们使用哪种呢 ?

  • 为什么要使用依赖注入
    • 传统的代码,每个对象负责管理与自己需要依赖的对象,导致如果需要切换依赖对象的实现类时,需要修改多处地方。同时,过度耦合也使得对象难以进行单元测试。
    • 依赖注入把对象的创造交给外部去管理,很好的解决了代码紧耦合(tight couple)的问题,是一种让代码实现松耦合(loose couple)的机制。
    • 松耦合让代码更具灵活性,能更好地应对需求变动,以及方便单元测试

  • 为什么要使用Spring
    • 使用Spring框架主要是为了简化Java开发(大多数框架都是为了简化开发),它帮我们封装好了很多完善的功能,而且Spring的生态圈也非常庞大。
    • 基于XML的配置是Spring提供的最原始的依赖注入配置方式,从Spring诞生之时就有了,功能也是最完善的.

属性注入

首先我来代码演示一下怎么才是属性注入,代码层次上是怎么写的呢..

我们这里代码演示是 我们在UserController类里面要使用UserService类....

//和前端进行交互的第一层(主要验证前端传递过来的参数-->进行校验和验证==>相当于安检的作用)
@Controller
public class UserController {

    //我们这里假设要在UserController类里面使用UserService,前提是UserService已经存放到Spring中了(加了类注解了)

    @Autowired //使用一个AutoWired这样的一个注解---->自动装配的意思
    private UserService userService;//UserService成员属性

    public void sayHello(){
        System.out.println("do UserController!!!");
        userService.doUserService();//调用UserService的方法
    }
}

我们看能不能成功呢 ?

看来是没有问题的..

注解真神奇..我们来见一下@AutoWired

AutoWired这个注解就是自动装配的意思,AutoWired来自于Spring.

由于我们的属性是在Spring中的(通过类注解),当在属性上面这加了AutoWired注解(就相当于功能的声明,要将存到spring中的这个属性,从spring中读取出来然后赋值给当前这个属性(自动装配))之后,Spring框架就会自动的将这个属性动态的注入到当前的类中.这就是属性注入

现在还有一个问题,就是我们已经知道使用注解@AutoWired就可以从Spring中读取Bean,那为啥启动类中获取Bean的方式还是使用getBean呢,为啥呢?

可以再启动类通过属性注入的方式,得到Bean对象可以么 ?

Non-static field 'userController' cannot be referenced from a static context

非静态字段“userController”不能从静态上下文引用

这也就说明了原因,由于启动类中的main方法->是static的,所以不能在静态方法中使用

再具体的原因就是因为静态方法执行的时机比Spring注入的时机要早,也就是说我在启动类使用这个属性的时候,spring框架还没有把这个属性注入到当前类中

如果你还不行的话,那把UserController加上static,随然不报错了,但由于spring执行注入的时机比较晚,所以会发生NullPointerException 空指针异常

属性名可以随便起

属性注入的这个属性由于是在Spring中的,所以是可以在其他类里面重复对这个属性进行属性注入的.

这个属性名字是可以随便起的(AutoWired是先去找类型,再去找名称->这个后面讲解)

属性注入的优点和缺点

优点 :

  • 属性注入只需要在属性上加一个@AutoWired注解,写法简便,可读性高,易维护.

缺点 :

  • 1.不能注入final修饰的属性.

为啥呢 ?

  • 因为我们知道JDK是我们最底层的框架,spring是上层,使用spring也要依赖java的,所以要遵循Java的规范,Java固定被final修饰的属性1.在定义的时候就进行赋值 2.在构造方法内部进行赋值.

所以,是因为不满足Java的规范

  • 2.属性注入只能在IOC容器才能使用(类与IOC容器高度耦合),使用其他容器/框架的时候不能使用属性注入(通用性不好)

  • 3.更容易违背单一职责原则

啥是单一职责原则呢 ?

单一职责原则的核心思想 : 一个类最好只做一件事,只有一个引起它变化的原因

换句话说,类只有单一功能,不要为类实现过多的功能点,以保证实体只有一个引起职责变化的原因

一个类只做一件事,要么做A事情,要么做B事情.只做一件事情

为什么说属性注入更容易违背单一职责原则呢 ?

  • 由于属性注入使用起来简便,这样就更容易使得开发者在类中注入多个对象,就可能会导致滥用的概率大大提高,所以违背单一职责原则的概率就大大提高-->更容易违背 不是一定违背,

setter注入

啥是setter注入呢?

setter注入就是使用我们Java的setter方法配合注解将Bean注入到当前类中.

我们来演示一下,怎么才算是属性注入.

Setter注入的优缺点

优点 :

  • setter注入满足单一设计/职责原则.

因为setter方法的特性,一个setter方法只对应一个对象,不会有注入多个对象的可能性,所以满足单一设计/职责原则

缺点 :

  • 不能注入final修饰的对象

还是与属性注入的解释是一样的,原因就是JDK是我们最底层的框架,Spring作为上层,要基于JDK/Java的,所以要满足Java的规范,java规定,被final修饰的属性1.在定义的时候进行赋值2.在构造方法内部进行赋值

  • 注入的对象可能会被修改

由于setter方法是可以被多次调用的,有修改的风险,所以注入的对象就可能被修改.

构造方法注入

构造方法注入就是利用构造方法将对象/Bean注入到当前类中

我来演示一下 :

对于构造方法注入也是官方最推荐我们使用的.(但悲哀的是我们依然会使用属性注入->因为写法简单,即使官方推荐用构造方法注入,但是官方自己的原码也是在使用属性注入.)

如果使用了构造方法注入,在类加载的时候,看到有在构造方法上加个注解,就会从Spring中取出该Bean,注入到当前类中.

注意事项

构造方法注入在有一个构造方法的时候,可以不加注解

为啥呢 ?

原因就是在Spring设计的时候,当使用构造方法注入的时候并且只有一个构造方法,Spring就会将对象注入到当前类中,给这个属性进行赋值.就可以省略注解AutoWired-->但必须是只有一个构造方法,才会符合

存在多个构造方法的时候,还可以省略注解么?

如果存在多个构造方法,他就不知道给哪一个构造方法注入了,并不知道到底执行哪个构造方法,这就不能注入,所以下面在调用的时候就会触发空指针异常.

这就好比,你班级有一个张三,老师在点名的时候,只有它会站起来回答问题,但是当你班级有两个名字为张三的时候,老师叫张三回答为题,两个张三都懵了,你到底叫谁呢??

所以当存在多个构造方法的时候必须要对要加注解.这样Spring就知道你到底执行哪个构造方法,在哪一个构造方法中注入Bean.

构造方法中一次可以注入多个对象么?

我们可以演示一下

构造方法注入是可以一次注入多个对象的

  • 当有多个构造方法的时候,加了AutoWired注解的构造方法才会执行,并且构造方法中的参数必须都要存在Spring容器中,否则就会报错

  • 在Spring中,一个类中的构造方法可以有多个但是只能有一个构造方法上添加AutoWired注解,否则会报错

构造方法注入的优缺点

优点 :

构造方法最牛,上面的缺点,都是俺构造方法的优点.

就比如,属性注入和setter注入都不能解决注入final修饰的属性问题,那对于构造方法注入就能够解决

  • 能够注入final修饰的属性

为什么构造方法可以注入final修饰的属性呢?

原因还是一样的,因为满足Java的规范,被final修饰的属性 一个是定义的时候就进行赋值,一个是在构造方法内部进行赋值.满足第二条,所以可以注入final修饰的属性.

  • 注入的对象不会被修改.

构造方法注入 注入的对象不会被修改,因为构造方法只会执行一次.

  • 构造方法注入是完全初始化的.

因为依赖注入是在构造方法内部执行的,而构造方法又是在类起初创建的时候就执行的,所以会被完全初始化

  • 它的通用性会更好

构造方法注入因为基于java的,JDK是最底层框架,所以无论在哪一个容器/框架都可以适用

缺点 :

  • 构造方法可以注入多个对象,也就违背了单一设计原则
  • 写法不简便

官方建议

在Spring4.2之前推荐的注入用法就是setter注入,因为setter注入更加符合单一设计/职责原则

在Spring4.2之后官方就推荐使用构造方法注入的方式(因为它的优点).如果要传入太多参数就需要考虑单一设计原则问题了.

但是我们在开发的时候依然会使用属性注入的方式,因为写法很简便.

有关Spring依赖注入的三种方式的更多相关文章

  1. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  4. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

    我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

  5. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  6. ruby-on-rails - 正确的 Rails 2.1 做事方式 - 2

    question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. spring.profiles.active和spring.profiles.include的使用及区别说明 - 2

    转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev

  9. ruby - 鸭子输入字符串、符号和数组的优雅方式? - 2

    这是针对我无法破坏的现有公共(public)API,但我确实希望对其进行扩展。目前,该方法采用字符串或符号或任何其他在作为第一个参数传递给send时有意义的内容我想添加发送字符串、符号等列表的功能。我可以只使用is_a吗?数组,但还有其他发送列表的方法,这不是很像ruby​​。我将调用列表中的map,所以第一个倾向是使用respond_to?:map。但是字符串也会响应:map,所以这行不通。 最佳答案 如何将它们全部视为数组?String的行为与仅包含String的Array相同:deffoo(obj,arg)[*arg].eac

  10. ruby - 这个 ruby​​ 注入(inject)魔术是如何工作的? - 2

    我今天看到了一个ruby​​代码片段。[1,2,3,4,5,6,7].inject(:+)=>28[1,2,3,4,5,6,7].inject(:*)=>5040这里的注入(inject)和之前看到的完全不一样,比如[1,2,3,4,5,6,7].inject{|sum,x|sum+x}请解释一下它是如何工作的? 最佳答案 没有魔法,符号(方法)只是可能的参数之一。这是来自文档:#enum.inject(initial,sym)=>obj#enum.inject(sym)=>obj#enum.inject(initial){|mem

随机推荐