Java中的依赖注入

简介: Java中的依赖注入

依赖注入,看起来好像是一个高大上的词语。不过事实上,依赖注入也应用到了我们日常开发中的很多很多地方。可以说,依赖注入(Dependency Injection)是一个很巧妙的思想,今天就来简单地做一个总结。

1,依赖注入的三种方式

在Java中,通常有三种依赖注入的方式:

  • 构造器注入
  • setter注入
  • 接口注入

通常来说,构造器注入和setter注入可以说是最常用的方式,而接口注入是从别的地方注入。先主要来讲解构造器注入setter注入

在此之前,我们先新建一个电脑类和打印机类作为例子讲解,如下:

打印机类:

packagecom.example.di.model;
/*** 打印机类*/publicclassPrinter {
/*** 打印机的打印方法** @param content 打印内容*/publicvoidprint(Stringcontent) {
System.out.println("打印了:"+content);
   }
}

电脑类:

packagecom.example.di.model;
/*** 电脑类*/publicclassComputer {
/*** 电脑的打印机*/privatePrinterprinter;
// 省略getter和setter}

可见,电脑类中有一个打印机类字段,这说明组成电脑类中有打印机类成员,那么很显然:打印机是电脑的依赖

大家一定先要理解依赖这个词。

也很显然,电脑类中的打印机还没有实例化,如果只实例化电脑,其打印机还是用不了的

那有的同学把电脑类改装如下:

packagecom.example.di.model;
/*** 电脑类*/publicclassComputer {
/*** 电脑的打印机*/privatePrinterprinter=newPrinter();
}

这样显然是不行的,基本上好像也没有人这么写。在电脑类中实例化打印机实例,这会导致电脑类和打印机类高度耦合,不利于扩展。譬如说现在打印机只是一个接口或者是抽象类,接口(抽象类)的实现类有彩色打印机类和黑白打印机类,结果不同电脑实例要用不同的打印机,那显然就不能再电脑类里面先实例化打印机了。

因此我们之所以使用依赖注入,是为了减少依赖性,增强可重用性,增加可读性、可扩展性等等。

(1) 构造器注入

在电脑类中写一个带参构造函数,用于给其字段打印机赋值:

/*** 电脑类带参构造器* @param printer 传入打印机实例*/publicComputer(Printerprinter) {
// 构造器注入this.printer=printer;
}

然后我们在主方法中,实例化电脑类,并注入打印机实例试试:

Printerprinter=newPrinter();
// 实例化电脑类,通过构造器注入了打印机实例(依赖)Computercomputer=newComputer(printer);
computer.getPrinter().print("构造器注入");

结果:

网络异常,图片无法展示
|

利用构造器传参,赋值,这就完成了构造器注入,很简单。

可见依赖注入也很好理解:不是让一个对象自己生成或者创造自己的依赖而是从外部注入

(2) setter注入

在讲这个方法之前,请好好看看你的setter方法,相信你已经对它再熟悉不过了:

publicvoidsetPrinter(Printerprinter) {
this.printer=printer;
}

没错,我们平时创建类,离不开gettersetter,那么其实setter就是利用了依赖注入的思想。

利用专门的一个setter方法实现依赖注入,就是setter注入的方式,也可以说是方法注入。

我们再在主方法中,来实例化一下试试:

Printerprinter=newPrinter();
// 实例化电脑类Computercomputer=newComputer();
// 通过setter方法注入了打印机实例(依赖)computer.setPrinter(printer);
computer.getPrinter().print("setter注入");

网络异常,图片无法展示
|

到此,相信大家知道依赖注入到底是什么了。通俗地讲,依赖注入就是在实例化一个类对象的时候不要同时把其中的依赖也给在类的内部实例化了,而是要先实例化这个类,再在外部实例化其依赖,然后把依赖通过构造器或者setter方法传入进去。

2,Spring中的自动装配

相信大家无论是开发Spring还是Spring Boot工程,对@Autowired这个注解已经再熟悉不过了,这其实就是用注解的方式实现了依赖注入。

只不过,利用这个注解,Spring框架就会自动帮你实例化对应的对象赋值帮你完成注入这个过程。例如上面在实例化电脑类时我们还要实例化它的依赖,也就是打印机类,最后再注入。而在Spring中利用@Autowired注解,就不需要我们去手动实例化依赖了,框架会帮我们实例化好。这就是自动装配

还是以一个例子开始,这里定义了一个服务类接口CharacterService及其对应的实现类CharacterServiceImpl,这个服务类可以调用MyBatis DAO查询数据库中所有的游戏角色数据。几个类代码以及Mapper XML如下:

DAO:

packagecom.example.firstssm.dao;
importcom.example.firstssm.dataobject.Character;
importorg.apache.ibatis.annotations.Mapper;
importjava.util.List;
@MapperpublicinterfaceCharacterDAO {
List<Character>getAll();
}

Mapper XML:

<?xmlversion="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.example.firstssm.dao.CharacterDAO"><resultMapid="characterResultMap"type="com.example.firstssm.dataobject.Character"><idcolumn="id"property="id"/><resultcolumn="name"property="name"/><resultcolumn="nickname"property="nickname"/><resultcolumn="type"property="type"/><resultcolumn="guild"property="guild"/><resultcolumn="gmt_created"property="gmtCreated"/><resultcolumn="gmt_modified"property="gmtModified"/></resultMap><selectid="getAll"resultMap="characterResultMap">      select *
      from `character`
</select></mapper>

服务类接口:

packagecom.example.firstssm.service;
importcom.example.firstssm.dataobject.Character;
importorg.springframework.stereotype.Service;
importjava.util.List;
@ServicepublicinterfaceCharacterService {
/*** 查询全部角色** @return 全部角色列表*/List<Character>queryAll();
}

实现类:

packagecom.example.firstssm.service.impl;
importcom.example.firstssm.dao.CharacterDAO;
importcom.example.firstssm.dataobject.Character;
importcom.example.firstssm.service.CharacterService;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Component;
importjava.util.List;
@ComponentpublicclassCharacterServiceImplimplementsCharacterService {
@AutowiredprivateCharacterDAOcharacterDAO;
@OverridepublicList<Character>queryAll() {
returncharacterDAO.getAll();
   }
}

角色实体类:

packagecom.example.firstssm.dataobject;
importlombok.Getter;
importlombok.NoArgsConstructor;
importlombok.Setter;
importjava.io.Serializable;
importjava.time.LocalDateTime;
@Getter@Setter@NoArgsConstructorpublicclassCharacterimplementsSerializable {
/*** 主键id*/privateintid;
/*** 角色名*/privateStringname;
/*** 外号*/privateStringnickname;
/*** 角色定位*/privateStringtype;
/*** 公会*/privateStringguild;
/*** 创建时间*/privateLocalDateTimegmtCreated;
/*** 修改时间*/privateLocalDateTimegmtModified;
@OverridepublicStringtoString() {
return"主键id:"+id+" 角色名:"+name+" 角色外号:"+nickname+" 角色定位:"+type+" 角色公会:"+guild;
   }
}

同样,@Autowired也有如下的注入方式。

(1) 字段注入

这应该是我们实际开发很常见的搞法了,我们创建一个测试类试一试:

packagecom.example.firstssm;
importcom.example.firstssm.dataobject.Character;
importcom.example.firstssm.service.CharacterService;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Component;
importjavax.annotation.PostConstruct;
importjava.util.List;
@ComponentpublicclassTest {
// 字段注入@AutowiredprivateCharacterServicecharacterService;
@PostConstructpublicvoidinitTest() {
// 调用一下试试List<Character>characterList=characterService.queryAll();
for (Charactercharacter : characterList) {
System.out.println(character);
      }
   }
}

结果:

网络异常,图片无法展示
|

可见,我们并没有手动new一个实例赋值给字段characterService,但是我们仍然可以在这里正常使用它,因为我们对这个字段标注了自动装配,那么启动的时候,框架就会去找CharacterService的实现类,并自动实例化一个对应的实例赋值给这个字段,就实现了依赖注入。

(2) 构造器注入

@Autowired还可以标注在构造函数上面或者构造函数中的参数上:

packagecom.example.firstssm;
importcom.example.firstssm.dataobject.Character;
importcom.example.firstssm.service.CharacterService;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Component;
importjavax.annotation.PostConstruct;
importjava.util.List;
@ComponentpublicclassTest {
privateCharacterServicecharacterService;
// 标注在构造函数上@AutowiredpublicTest(CharacterServicecharacterService) {
this.characterService=characterService;
   }
@PostConstructpublicvoidinitTest() {
// 调用一下试试List<Character>characterList=characterService.queryAll();
for (Charactercharacter : characterList) {
System.out.println(character);
      }
   }
}

还可以:

packagecom.example.firstssm;
importcom.example.firstssm.dataobject.Character;
importcom.example.firstssm.service.CharacterService;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Component;
importjavax.annotation.PostConstruct;
importjava.util.List;
@ComponentpublicclassTest {
privateCharacterServicecharacterService;
// 标注在构造函数参数上publicTest(@AutowiredCharacterServicecharacterService) {
this.characterService=characterService;
   }
@PostConstructpublicvoidinitTest() {
// 调用一下试试List<Character>characterList=characterService.queryAll();
for (Charactercharacter : characterList) {
System.out.println(character);
      }
   }
}

运行,效果一样。

这样,就通过构造器完成自动装配,这就是Spring中构造器注入方式。

(3) setter注入

同样,自动装配也可以标注在setter方法上:

packagecom.example.firstssm;
importcom.example.firstssm.dataobject.Character;
importcom.example.firstssm.service.CharacterService;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Component;
importjavax.annotation.PostConstruct;
importjava.util.List;
@ComponentpublicclassTest {
privateCharacterServicecharacterService;
// 方法注入@AutowiredpublicvoidsetCharacterService(CharacterServicecharacterService) {
this.characterService=characterService;
   }
@PostConstructpublicvoidinitTest() {
// 调用一下试试List<Character>characterList=characterService.queryAll();
for (Charactercharacter : characterList) {
System.out.println(character);
      }
   }
}

3,总结

可见依赖注入并不难,依赖注入的思想,也贯穿于我们日常的开发中。不过大家至少还是要熟悉几种常见方式。在Spring框架中,也可见依赖注入更加方便,不需要我们实例化依赖,自动装配就能够搞定。

相关文章
|
5天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
58 23
|
12天前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
81 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
16天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
142 60
【Java并发】【线程池】带你从0-1入门线程池
|
1月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
106 14
|
1月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
57 13
|
1月前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
2月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
2月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
128 17
|
3月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3月前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。