Java中的依赖注入

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

1,依赖注入的三种方式

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

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

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

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

打印机类:

package com.example.di.model;

/**
 * 打印机类
 */
public class Printer {

   /**
    * 打印机的打印方法
    *
    * @param content 打印内容
    */
   public void print(String content) {
      System.out.println("打印了:" + content);
   }

}
复制代码

电脑类:

package com.example.di.model;

/**
 * 电脑类
 */
public class Computer {

   /**
    * 电脑的打印机
    */
   private Printer printer;
   
   // 省略getter和setter

}
复制代码

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

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

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

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

package com.example.di.model;

/**
 * 电脑类
 */
public class Computer {

   /**
    * 电脑的打印机
    */
   private Printer printer = new Printer();

}
复制代码

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

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

(1) 构造器注入

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

/**
 * 电脑类带参构造器
 * @param printer 传入打印机实例
 */
public Computer(Printer printer) {
   // 构造器注入
   this.printer = printer;
}
复制代码

然后实例化电脑类,并注入打印机实例试试:

Printer printer = new Printer();
// 实例化电脑类,通过构造器注入了打印机实例(依赖)
Computer computer = new Computer(printer);
computer.getPrinter().print("构造器注入");
复制代码

结果:

image.png

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

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

(2) setter注入

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

public void setPrinter(Printer printer) {
   this.printer = printer;
}
复制代码

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

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

我们再来实例化一下试试:

Printer printer = new Printer();
// 实例化电脑类
Computer computer = new Computer();
// 通过setter方法注入了打印机实例(依赖)
computer.setPrinter(printer);
computer.getPrinter().print("setter注入");
复制代码

image.png

2,Spring中的自动装配

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

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

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

DAO:

package com.example.firstssm.dao;

import com.example.firstssm.dataobject.Character;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface CharacterDAO {

   List<Character> getAll();

}
复制代码

Mapper XML:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.firstssm.dao.CharacterDAO">
   <resultMap id="characterResultMap" type="com.example.firstssm.dataobject.Character">
      <id column="id" property="id"/>
      <result column="name" property="name"/>
      <result column="nickname" property="nickname"/>
      <result column="type" property="type"/>
      <result column="guild" property="guild"/>
      <result column="gmt_created" property="gmtCreated"/>
      <result column="gmt_modified" property="gmtModified"/>
   </resultMap>

   <select id="getAll" resultMap="characterResultMap">
      select *
      from `character`
   </select>
</mapper>
复制代码

服务类接口:

package com.example.firstssm.service;

import com.example.firstssm.dataobject.Character;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public interface CharacterService {

   /**
    * 查询全部角色
    *
    * @return 全部角色列表
    */
   List<Character> queryAll();

}
复制代码

实现类:

package com.example.firstssm.service.impl;

import com.example.firstssm.dao.CharacterDAO;
import com.example.firstssm.dataobject.Character;
import com.example.firstssm.service.CharacterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class CharacterServiceImpl implements CharacterService {

   @Autowired
   private CharacterDAO characterDAO;

   @Override
   public List<Character> queryAll() {
      return characterDAO.getAll();
   }

}
复制代码

角色实体类:

package com.example.firstssm.dataobject;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;
import java.time.LocalDateTime;

@Getter
@Setter
@NoArgsConstructor
public class Character implements Serializable {

   /**
    * 主键id
    */
   private int id;

   /**
    * 角色名
    */
   private String name;

   /**
    * 外号
    */
   private String nickname;

   /**
    * 角色定位
    */
   private String type;

   /**
    * 公会
    */
   private String guild;

   /**
    * 创建时间
    */
   private LocalDateTime gmtCreated;

   /**
    * 修改时间
    */
   private LocalDateTime gmtModified;

   @Override
   public String toString() {
      return "主键id:" + id + " 角色名:" + name + " 角色外号:" + nickname + " 角色定位:" + type + " 角色公会:" + guild;
   }

}
复制代码

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

(1) 字段注入

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

package com.example.firstssm;

import com.example.firstssm.dataobject.Character;
import com.example.firstssm.service.CharacterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;

@Component
public class Test {

   // 字段注入
   @Autowired
   private CharacterService characterService;

   @PostConstruct
   public void initTest() {
      // 调用一下试试
      List<Character> characterList = characterService.queryAll();
      for (Character character : characterList) {
         System.out.println(character);
      }
   }

}
复制代码

结果:

image.png

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

(2) 构造器注入

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

package com.example.firstssm;

import com.example.firstssm.dataobject.Character;
import com.example.firstssm.service.CharacterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;

@Component
public class Test {

   private CharacterService characterService;

   // 标注在构造函数上
   @Autowired
   public Test(CharacterService characterService) {
      this.characterService = characterService;
   }

   @PostConstruct
   public void initTest() {
      // 调用一下试试
      List<Character> characterList = characterService.queryAll();
      for (Character character : characterList) {
         System.out.println(character);
      }
   }

}
复制代码

还可以:

package com.example.firstssm;

import com.example.firstssm.dataobject.Character;
import com.example.firstssm.service.CharacterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;

@Component
public class Test {

   private CharacterService characterService;
   
   // 标注在构造函数参数上
   public Test(@Autowired CharacterService characterService) {
      this.characterService = characterService;
   }

   @PostConstruct
   public void initTest() {
      // 调用一下试试
      List<Character> characterList = characterService.queryAll();
      for (Character character : characterList) {
         System.out.println(character);
      }
   }

}
复制代码

运行,效果一样。

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

(3) setter注入

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

package com.example.firstssm;

import com.example.firstssm.dataobject.Character;
import com.example.firstssm.service.CharacterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;

@Component
public class Test {

   private CharacterService characterService;

   // 方法注入
   @Autowired
   public void setCharacterService(CharacterService characterService) {
      this.characterService = characterService;
   }

   @PostConstruct
   public void initTest() {
      // 调用一下试试
      List<Character> characterList = characterService.queryAll();
      for (Character character : characterList) {
         System.out.println(character);
      }
   }

}
复制代码

3,总结

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

Guess you like

Origin juejin.im/post/7039355027986579464