Spring Boot依赖注入[DI]和相关注解 [Annotations]

这篇文章,让我们讨论一下Spring boot中的依赖注入(Dependency Injection)和相关注解(Annotations)的使用。

演示例子源代码 演示案例

概念

Spring Boot的一个重要概念和实现--依赖注入。我列举了几个和依赖注入相关的概念,面向接口编程、IOC和依赖倒转。这些都是Spring Boot 依赖注入机制实现的理论基础。更多的了解这些概念将有助于深入理解Spring Boot 的依赖注入。

由于讲述所有这些概念是另一个更大的关于设计模式的话题,以后有机会抽时间再写一下相关文章。有兴趣的朋友也可以给我留言,大家共同讨论。

相关概念解释:

  • 面对接口编程
    IOP: Interface Oriented Programming
    系统应该依赖接口(Interface)或抽象类进行编程,而不是具体的实现类(Class),即将抽象和具体分离,以便增强系统的可扩展性。
  • 依赖倒转
    IOC: Inverse of Control
    面向对象的一种设计思想,它提出在设计中对象交由一个容器控制在运行时动态创建和管理,而不是在设计时由程序代码直接管理对象。
  • 依赖注入:
    DI: Dependency Injection
    在IOC的设计思想基础上,DI提供对象管理容器并在运行时提供相关查找、定位、初始化、装配对象的过程。也就是,你在写代码的时候只需要关心接口,不需要关心对象如何被装配的。

依赖注入

解决的问题

在一个应用系统中,通常会存在分层、分模块的情况,通常一个系统都有由很多不同模块共同合作构建而成。比如,经典的三层模式,它包括服务层、业务层、数据层,层次之间存在大量的对象引用(依赖)。这导致:

  1. 存在大量的对象强依赖(引用),不利于维护、变更和扩展。
  2. 设计时,需要关注大量对象的创建,这是一件繁杂且需要耐心的事情。
  3. 系统维护随着系统变大变得更加艰难。
  4. 如果不使用依赖注入很难做到面向接口编程,包和类的引用管理都需要人为或程序进行管理。
    依赖注入就是解决上述问题的。通过依赖注入机制,程序员在编程时(设计时)只需要面向接口进行编程,而无需关心具体实现对象如何被初始化和加载的(装配过程)。

原理

“没有对比就没有伤害。”,我们来对比下面两种编程模式,以便知道为什么需要依赖注入。

传统编程
面向接口编程
  • 第一种模式中,上层的调用方(Controller)需要明确知道下层的实际实现类(OrderService)以及明确所在的包。这个方案的最大问题就是耦合度太高,一旦程序需要更改,调用方和被调用方都需修改代码,并且存在对包的版本管理。

  • 第二种模式中,类的实现和接口的定义被分割到不同的包中。在设计时,上层的调用方(Controller)仅需要知道接口(OrderService)的存在,至于需要加载哪个包,使用哪个具体的实现类是不需要关心的。具体的实现类的引用、初始化被“延迟”到程序运行时。程序运行时可利用Factory模式或者依赖注入机制选择具体的实现类并加载(延迟确定)。由于在运行时动态确定和装配,使得程序灵活度更高,通过配置就可以更改和替换逻辑和规则,实现内聚合、外开放

示意图

接下来,看看依赖注入机制都做了什么。

依赖注入机制

以上图例中:

  1. OrderServiceImpl类实现了OrderService接口。
  2. OrderControler作为上层模块依赖OrderService接口。
  3. 1、2的信息在设计时通过多种可能的途径被配置,通常依赖注入机制会提供相应的配置机制。
  4. 依赖注入机制在程序启动 运行时(runtime),读取相关配置信息并分析、定位和装配相关对象。
  5. 最后,当OrderController被调用的时候,如期得到来之OrderServiceImpl的支持。

在上述过程中,借助依赖注入机制,运行时程序也只关注于接口定义,依赖注入容器会在运行时完成自动装配过程。

  • 设计时,OrderController作为上层调用者只需要知道OrderService接口的存在,并做相关“依赖配置”。
  • 运行时(runtime),依赖注入机制根据配置信息,自动 装配 所需对象。

原理对比

为更加清楚的说明,我们将依赖注入机制和自行车的制造过程做一个类比。下图描述了自行车从设计到生产的大概过程。

依赖注入对比
  1. 自行车的设计(设计时)类似我们编写代码的时候定义的接口、逻辑、以及配置文件等等。
  2. 自行车厂家做完自行车设计后,将零件设计标准提供给零配件厂家,厂家按照设计标准进行生产。当然,在符合标准的情况厂家也可以做一些调整,比如,颜色。这个过程类似我们将符合接口的实现类分解到不同的包中,或者请“外部团队”根据接口要求编写和构建包。
  3. 装配车辆的过程(运行时)就是将零配件按照设计进行实际装配。这类似Spring Boot的依赖注入机制,它根据接口的定义、实现类(制作好的零件)以及相关的配置文件在程序运行时进行动态装配。

上面生产自行车的过程如果用代码编写出来的话,我们只需要定义好做好设计标准并生产出相应的零件就好,自行车的装配工作就交给依赖注入机制完成就好了。

演示例子源代码 演示案例

Spring boot 依赖注入

看完前面的描述,spring boot 依赖注入机制就显得很简单。它提供了一个容器( spring container)管理一堆的bean对象。spring container通过“配置信息“知道如何将一个普通对象变成一个bean对象并放入到容器中。同时,spring container也知道如何装配一个对象到另一个对象中(可能是字段、构造函数或属性)

一个bean对象,记录了被注入的对象的类型、如何初始化以及这个对象依赖其他的哪些对象(严格意义上来说应该是哪些bean对象)。

容器如何知道哪些类型需要被转变成bean呢?这就是下面我们需要讲的部分。

spring boot只在应用程序启动的时刻注入所有的bean对象以及进行装配。

注解(Annotation)

Spring container依赖三种配置方式收集哪些类型需要被转化成bean对象并注入到spring container中进行管理。

注解方式,是spring boot 依赖注入中三种静态bean配置其中的一种。下面列举依赖注入的三种配置方式。

配置bean(注入bean)的方式

  • 通过注解进行配置 这章节讲述的
  • 通过编码方式,使用@Configuraiton的方式进行配置,它可以取代下面基于xml的配置方式。
  • 通过bean xml配置文件形式配置 (现代应用程序中很少使用这样方式)

相关的注解

使用注解方式配置依赖注入bean配置一般涉及到以下几类注解。

用于把对象配置为bean对象的标签

  1. @Component
    • 它由Spring boot框架,是元注解,可以用于标注其他注解(元注解)
    • 它是@Service, @Controller, @Respository的注解标签
    • 被@Component的注解的对象以及被它注解的注解,如@Service, @Controller, @Respository都将被组件扫描(Component scanning)过程收集注册为bean对象。
  2. @Controller, @Service, @Respository
    • 它们在经典三层模式中带有特殊含义,分别代API接口组件、服务组件和持久化组件。
  • 其用途和@Component相当。
  1. @Primary
    • 在一个接口有多个实现的情况下,通过@Primary指定默认实现此接口的实现类作为主要的bean。
  2. @Order
    • 控制bean的加载次序,有时候一个对象需要另一个对象被加载后在能被加载。
  3. Conditional 【单独描述和举例】
    • Spring 4.0引入的条件注解,满足某个特定条件下创建特定的Bean。而我们实现的方式就是实现Condition接口。

用于指示需要装配bean对象的标签

  1. @Autowired 和 @Qualifier
    • 上面两各标签由Spring boot框架
    • @Autowired通过类型查找bean并进行装配,如果同一个类型有多个bean,将引发错误。
    • 如果@Autowired和@Qualifier一起使用将使用名字的方式查找bean并装配,如果没有找到,将报错。
  2. @Resource
    • 属于JDK提供的标签,位于javax.annotation.Resource, 最早实现的DI是作为JDK的一部分。
    • 默认用名字来查找bean并进行装配,如果查找失败,将回退到@Autowired通过类型查找并装配。

第一个例子

这个例子中将基于典型三层,使用 @Autowired、@Compoent、@Service, @Controller, @Respository的注解进行bean对象的配置和装配。

演示例子源代码 演示案例

例子讲解

  • 项目结构
    从图中可以看出来,这是一个典型的三层结构的 rest api项目。


    截屏2020-02-1800.16.59.png
  • 先从定义 bean开始

UserService接口

package com.juqimiao.di.demos.d01.daos;

public interface UserDao {
    String select(String name);
}

定义UserDaoImpl实现类,它实现了UserDao接口,并且将这个类打上@ Repository注解。它将被作为bean注入到spring container 中。

@Repository
public class UserDaoImpl implements UserDao {

    @Override
    public String select(String name) {
        return "User's information " + name;
    }
}

接下来,我们定义UserService接口。

package com.juqimiao.di.demos.d01.services;

public interface UserService {
    String getInfo(String name);
}

定义UserServiceImpl实现类,它实现了UserService接口,并且在这个类上面大上了@Service注解。它将被作为bean注入到spring container 中。

package com.juqimiao.di.demos.d01.services.Impl;

import com.juqimiao.di.demos.d01.daos.UserDao;
import com.juqimiao.di.demos.d01.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    /**
     * 演示构造函数注入方式。
     * @param userDao {@link UserDao}
     */
    @Autowired
    public UserServiceImpl(UserDao userDao){
        this.userDao = userDao;
    }

    @Override
    public String getInfo(String name) {
        return userDao.select(name);
    }
}

你看到了代码中,类的构造函数上被打上了@Autowired注解,构造函数中有一个参数 “UserDao userDao”。这意味着,当程序运行的时候,UserDaoImpl的一个实例会被自动装配给这个参数。

最后,我们创建UserController,这是典型的rest api的Controller。

package com.juqimiao.di.demos.d01.controllers;

import com.juqimiao.di.demos.d01.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;
    @GetMapping("/info")
    public String getInfo(@RequestParam String name){
        return userService.getInfo(name);
    }
}

这里,你可以看到@Autowired注解被打在了userService 字段上面,意味着UserServiceImpl对象将被装配到这个字段上。

同时,我们也在项目中通过,CustomerController/CustomService和CustomerDao演示了@Component是如何工作的。

例子小结

  1. @Service、@ Repository和@Component都用于声明一个对象作为bean对象的注解。
  2. @Autowired告诉spring container它所需要依赖的bean并要求container自动查找并装配。
  3. bean可以通过 构造函数、字段、属性进行装配,但装配的bean对象并不能保证一定是不为null的。
  4. UserServiceImpl和UserController中相应引用的是UserDao和UserServic接口,而不是具体实现类。因为,如果直接指向具体实现类,依赖注入本身其实就失去了意义,毕竟它是为面向接口编程而生的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,270评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,489评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,630评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,906评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,928评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,718评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,442评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,345评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,802评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,984评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,117评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,810评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,462评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,011评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,139评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,377评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,060评论 2 355

推荐阅读更多精彩内容