Java Spring IoC&DI :探索Java Spring中控制反转和依赖注入的威力,增强灵活性和可维护性

简介: Java Spring IoC&DI :探索Java Spring中控制反转和依赖注入的威力,增强灵活性和可维护性

前提小知识:高内聚低耦合

我们一下要学习的内容都是为了实现⾼内聚低耦合来进行的

软件设计原则:⾼内聚低耦合.

⾼内聚指的是:⼀个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联

系程度越⾼,则内聚性越⾼,即"⾼内聚"。

低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。修改⼀处代码,其他模块的代码

改动越少越好.

⾼内聚低耦合⽭盾吗?

不⽭盾,⾼内聚指的是⼀个模块中各个元素之间的联系的紧密程度,低耦合指的是各个模块之间的紧

密程度

这就好⽐⼀个企业,包含很多部⻔,各个部⻔之间的关联关系要尽可能的⼩,⼀个部⻔发⽣问题,要尽

可能对降低对其他部⻔的影响,就是耦合.但是部⻔内部员⼯关系要尽量紧密,遇到问题⼀起解决,克

服.这叫做内聚.

⽐如邻⾥邻居,楼上漏⽔,楼下遭殃,就是耦合.家庭⼀个成员⽣病,其他成员帮忙照顾,就叫内聚.

⼀个家庭内部的关系越紧密越好,⼀个家庭尽可能少的影响另⼀个家庭,就是低耦合.

简单来说:就是在未来的工作中写出的代码尽量集中在一个路径中,但是每个程序与其它程序的关联度很低,以至于一个文件出错了并不影响其它程序的正常运行.


一. IOC

1.1 什么是IOC?

IoC是Spring的核⼼思想,也是常⻅的⾯试题,那什么是IoC呢?

IoC:InversionofControl(控制反转),也就是说Spring是⼀个"控制反转"的容器.

什么是控制反转呢?也就是控制权反转.什么的控制权发⽣了反转?获得依赖对象的过程被反转了

也就是说,当需要某个对象时,传统开发模式中需要⾃⼰通过new创建对象,现在不需要再进⾏创

建,把创建对象的任务交给容器,程序中只需要依赖注⼊(DependencyInjection,DI)就可以了.

我们⽤⼀句更具体的话来概括Spring,那就是:Spring是包含了众多⼯具⽅法的IoC容器

简单点来说:就是通过IOC的内置注解将被注解的内容放到Spring的大池子里,方便后续的调用(这样子应该很好理解)

1.2 IOC的实现

举个栗子:比如传统的造车过程是一级级的调用,如下:

以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要修改.程序的耦合度⾮常⾼(修改⼀处代码,影响其他处的代码修改)

经过IOC实现后的流程图如下:

而IOC的目标就是为了降低耦合度,对于上面的例子来说就是将汽车每个部件的生产都交给第三方来处理,不让他们之间有联系,也就降低了耦合度

我们发现了⼀个规律,通⽤程序的实现代码,类的创建顺序是反的,传统代码是Car控制并创建了

Framework,Framework创建并创建了Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再

是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由

当前类控制了.

这样的话,即使依赖类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是IoC的

实现思想。

1.3 IOC容器的优点

资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集

中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合

度。

  1. 资源集中管理:IoC容器会帮我们管理⼀些资源(对象等),我们需要使⽤时,只需要从IoC容器中去取
    就可以了
  2. 我们在创建实例的时候不需要了解其中的细节,降低了使⽤资源双⽅的依赖程度,也就是耦合度.
    Spring就是⼀种IoC容器,帮助我们来做了这些资源管理.

1.4 IOC的存储

在之前的⼊⻔案例中,要把某个对象交给IOC容器管理,需要在类上添加⼀个注解: @Component

⽽Spring框架为了更好的服务web应⽤程序,提供了更丰富的注解.

共有两类注解类型可以实现:

  1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
  2. ⽅法注解:@Bean.
    接下来我们分别来看
@Controller(控制器存储)

使⽤@Controller存储bean的代码如下所⽰:

@Controller // 将对象存储到 Spring 中
  public class UserController {
    public void sayHi(){
      System.out.println("hi,UserController...");
    }
  }
@Service(服务存储)

使⽤@Service存储bean的代码如下所⽰:

@Service// 将对象存储到 Spring 中
  public class UserService {
    public void sayHi(String name) {
      System.out.println("Hi," + name);
    }
  }
@Repository(仓库存储)

使⽤ @Repository 存储bean的代码如下所⽰:

@Repository
  public class UserRepository {
    public void sayHi() {
      System.out.println("Hi, UserRepository~");
    }
  }
@Component(组件存储)

使⽤@Component存储bean的代码如下所⽰:

@Component
  public class UserComponent {
    public void sayHi() {
      System.out.println("Hi, UserComponent~");
    }
  }
@Configuration(配置存储)

使⽤@Configuration存储bean的代码如下所⽰:

@Configuration
  public class UserConfiguration {
    public void sayHi() {
      System.out.println("Hi,UserConfiguration~");
    }
  }
@Bean (方法注解)

类注解是添加到某个类上的同时⽅法注解要配合类注解使⽤,但是存在两个问题:

  1. 使⽤外部包⾥的类,没办法添加类注解
  2. ⼀个类,需要多个对象,⽐如多个数据源
    这种场景,我们就需要使⽤⽅法注解 @Bean
    我们先来看看⽅法注解如何使⽤:
    ⽅法注解要配合类注解使⽤
@Component
public class BeanConfig {
  @Bean
  public User user(){
    User user = new User();
    user.setName("zhangsan");
    user.setAge(18);
    return user;
  }
}

1.5 IOC注解总结概括

  • @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应.
  • @Servie:业务逻辑层,处理具体的业务逻辑.
  • @Repository:数据访问层,也称为持久层.负责数据访问操作
  • @Configuration:配置层.处理项⽬中的⼀些配置信息.
  • @Component:其它4大类注解的父类
  • @Bean:方法注解,将一个方法交给Spring管理,同时要与类注解一起搭配使用

其实这些注解⾥⾯都有⼀个注解 @Component ,说明它们本⾝就是属于 @Component 的"⼦类".@Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service ,

@Repository 等.这些注解被称为 @Component 的衍⽣注解.

@Controller , @Service 和 @Repository ⽤于更具体的⽤例(分别在控制层,业务逻辑层,持

久化层),在开发过程中,如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更

好的选择


二. DI

2.1 什么是DI?

DI:DependencyInjection(依赖注⼊)

容器在运⾏期间,动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。

程序运⾏时需要某个资源,此时容器就为其提供这个资源.

从这点来看,依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过

引⼊IoC容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。

上述代码中,是通过构造函数的⽅式,把依赖对象注⼊到需要使⽤的对象中的

可以说,DI是IoC的⼀种实现

2.2 DI的方法使用

依赖注⼊是⼀个过程,是指IoC容器在创建Bean时,去提供运⾏时所依赖的资源,⽽资源指的就是对象.

在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作.

简单来说,就是把对象取出来放到某个类的属性中.

在⼀些⽂章中,依赖注⼊也被称之为"对象注⼊",“属性装配”,具体含义需要结合⽂章的上下⽂来理解

关于依赖注⼊,Spring也给我们提供了三种⽅式:

  1. 属性注⼊(FieldInjection)
  2. 构造⽅法注⼊(ConstructorInjection)
  3. Setter注⼊(SetterInjection)
属性注入

属性注⼊是使⽤ @Autowired 实现的,将Service类注⼊到Controller类中.

Service类的实现代码如下:

import org.springframework.stereotype.Service;
@Service
public class UserService {
    public void sayHi() {
      System.out.println("Hi,UserService");
    }
}
构造方法注入

构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所⽰:

@Controller
public class UserController2 {
  //注⼊⽅法2: 构造⽅法
  private UserService userService;
  @Autowired
  
  public UserController2(UserService userService) {
    this.userService = userService;
  }
  public void sayHi(){
    System.out.println("hi,UserController2...");
    userService.sayHi();
  }
}

注意事项:如果类只有⼀个构造⽅法,那么@Autowired注解可以省略;如果类中有多个构造⽅法,

那么需要添加上@Autowired来明确指定到底使⽤哪个构造⽅法。

Setter注入

Setter注⼊和属性的Setter⽅法实现类似,只不过在设置set⽅法的时候需要加上@Autowired注

解,如下代码所⽰:

@Controller
public class UserController3 {
  //注⼊⽅法3: Setter⽅法注⼊
  private UserService userService;
  @Autowired
  public void setUserService(UserService userService) {
    this.userService = userService;
  }
  
  
  public void sayHi(){
    System.out.println("hi,UserController3...");
    userService.sayHi();
  }
}
三种注入优缺点分析
  1. 属性注⼊

优点:简洁,使⽤⽅便;

缺点:

  • 只能⽤于IoC容器,如果是⾮IoC容器不可⽤,并且只有在使⽤的时候才会出现NPE(空指针异常)
  • 不能注⼊⼀个Final修饰的属性
  1. 构造函数注⼊

优点:

  • 可以注⼊final修饰的属性
  • 注⼊的对象不会被修改
  • 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法 是在类加载阶段就会执⾏的⽅法.
  • 通⽤性好,构造⽅法是JDK⽀持的,所以更换任何框架,他都是适⽤的

缺点:

注⼊多个对象时,代码会⽐较繁琐

  1. Setter注⼊

优点:⽅便在类实例之后,重新对该对象进⾏配置或者注⼊

缺点:

  • 不能注⼊⼀个Final修饰的属性
  • 注⼊对象可能会被改变,因为setter⽅法可能会被多次调⽤,就有被修改的⻛险.

2.3 @Autowired存在的问题

当同⼀类型存在多个bean时,使⽤@Autowired会存在问题

报错的原因是,⾮唯⼀的Bean对象。

如何解决上述问题呢?Spring提供了以下⼏种解决⽅案:

• @Primary

• @Qualifier

• @Resource

@Primary

使⽤@Primary注解:当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现.

@Component
public class BeanConfig {
  @Primary //指定该bean为默认bean的实现
  @Bean("u1")
  public User user1(){
    User user = new User();
    user.setName("zhangsan");
    user.setAge(18);
    return user;
  }
  
  @Bean
  public User user2() {
    User user = new User();
    user.setName("lisi");
    user.setAge(19);
    return user;
  }
}
@Qualifier

使⽤@Qualifier注解:指定当前要注⼊的bean对象。在@Qualifier的value属性中,指定注⼊的bean

的名称。

• @Qualifier注解不能单独使⽤,必须配合@Autowired使⽤

@Controller
public class UserController {
  @Qualifier("user2") //指定bean名称
  @Autowired
  private User user;
  
  public void sayHi(){
    System.out.println("hi,UserController...");
    System.out.println(user);
  }
}
@Resource

使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。

@Controller
public class UserController {
  @Resource(name = "user2")
  private User user;
  
  public void sayHi(){
    System.out.println("hi,UserController...");
    System.out.println(user);
  }
}

2.4 @Autowird的注入流程图

常见⾯试题:

@Autowird与@Resource的区别

• @Autowired是spring框架提供的注解,⽽@Resource是JDK提供的注解

• @Autowired默认是按照类型注⼊,⽽@Resource是按照名称注⼊.相⽐于@Autowired来说,

@Resource⽀持更多的参数设置,例如name设置,根据名称获取Bean。


三. 总结

Java Spring框架中的IoC(Inversion of Control,控制反转)和DI(Dependency Injection,依赖注入)是框架的核心概念之一,它们旨在降低组件之间的耦合度,提高代码的灵活性和可维护性。

  • 控制反转(IoC):在传统的程序设计中,应用程序控制程序的流程,即应用程序负责实例化和管理对象之间的依赖关系。而在IoC容器中,控制权被转移到容器,容器负责实例化对象并管理它们之间的依赖关系。这种控制权的转移使得组件之间的耦合度降低,提高了代码的灵活性和可测试性。
  • 依赖注入(DI):依赖注入是IoC的一种实现方式,通过依赖注入,容器会将一个对象所依赖的其他对象注入到该对象中,而不是由对象自己去创建或查找依赖的对象。依赖注入可以通过构造函数、属性或者方法进行注入,使得组件之间的依赖关系更加清晰、可控,同时也方便进行单元测试和替换依赖。

总的来说,IoC和DI通过将控制权交给容器,实现了组件之间的解耦和松耦合,提高了代码的可维护性、可测试性和可扩展性,是Spring框架的核心特性之一。

相关文章
|
5天前
|
XML Java 测试技术
Spring Boot中的依赖注入和控制反转
Spring Boot中的依赖注入和控制反转
|
11天前
|
XML Java 数据库连接
Spring6(二):IoC容器(2)
Spring6(二):IoC容器
17 2
|
11天前
|
XML Java 数据格式
Spring6(二):IoC容器(3)
Spring6(二):IoC容器(3)
14 1
|
11天前
|
XML Java 数据格式
Spring6(二):IoC容器(1)
Spring6(二):IoC容器(1)
21 1
|
5天前
|
XML Java 数据格式
深入理解Spring中的依赖注入原理
深入理解Spring中的依赖注入原理
|
6天前
|
Java Spring 容器
深入理解Spring Boot中的容器与依赖注入
深入理解Spring Boot中的容器与依赖注入
|
11天前
|
Java Spring 容器
Spring6(二):IoC容器(4)
Spring6(二):IoC容器(4)
9 0
|
3天前
|
存储 安全 Java
Java面试题:请解释Java内存模型(JMM)是什么,它如何保证线程安全?
Java面试题:请解释Java内存模型(JMM)是什么,它如何保证线程安全?
34 13
|
2天前
|
缓存 安全 Java
Java中线程池如何管理?
【7月更文挑战第11天】Java中线程池如何管理?
8 2
|
2天前
|
安全 算法 Java
Java中线程安全怎么做?
【7月更文挑战第11天】Java中线程安全怎么做?
9 2