Spring IoCDI

在这里插入图片描述

文章目录

  • 前言
  • 什么是Spring
    • 1. 什么是 IoC 容器
      • 1.1 什么是容器
      • 1.2 什么是 IoC
    • 2. 什么是DI
  • IoC & DI 的使用
  • IoC详解
    • Bean的存储
      • @Controller注解
      • 如何获取Bean
        • 1. 根据Bean的名称获取Bean
        • 2. 根据Bean类型获取Bean
        • 3. 根据Bean名和Bean类型获取Bean
      • @Service注解
      • @Repository注解
      • @Component注解
      • @Configuration注解
      • 为什么会有这么多类注解
      • 方法注解
      • 重命名Bean
      • 扫描路径
  • DI 详解
    • 1. 属性注入
    • 构造方法注入
    • Setter 注入
    • 三种注入的优缺点
    • Autowired 存在的问题

前言

前面我们大概知道了什么是 Spring,以及 Spring 家族中 Spring Boot 和 Spring MVC的开发,但是 Spring 到底是什么呢?

什么是Spring

前面我为大家简单介绍了什么是 Spring 【Spring】什么是Spring,不过前面的介绍较为简单,要想知道Spring 的原理,这些知识不不足以帮助我们了解 Spring 的,所以这篇文章我将详细为大家介绍什么是 Spring。

通过前面的学习,我们知道了 Spring 是一个开源的框架,它让我们的开发变得更加简单,它支持广泛的应用场景,有着活跃而庞大的社区,这也是 Spring 能够经久不衰的原因。

但是这个概念对于我们来说,还是太抽象了,用一句话概括:Spring 是包含了众多工具的 IoC 容器。那么什么是 IoC 容器呢?

1. 什么是 IoC 容器

1.1 什么是容器

容器是指能够容纳某种物品的装置。在生活中,储物箱、垃圾桶、冰箱等这些都属于容器,而在计算机中,我们前面学习的List/map就是数据存储的容器,Tomcat就是Web容器。

1.2 什么是 IoC

IoC 是 Spring 的核心思想。

IoC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IoC这个概念。对于面向对象设计及编程的基本思想,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。

在传统的程序设计中,对象的创建和管理都是由代码直接完成的。而在IoC中,对象的创建和管理权交给了IoC Service Provider(IoC思想的具体实现),我们只需要告诉它需要什么对象,它就会为我们准备好。这种机制的引入,使得应用程序的各个部分之间的依赖关系变得非常清晰,并且可以将各个部分解耦,提高代码的可重用性和可维护性。

给大家举个例子,传统的汽车开发过程是这样的:

在这里插入图片描述
用代码体现就是这样的:

public class NewCarExample {public static void main(String[] args) {Car car = new Car();car.run();}/*** 汽车对象*/static class Car {private Framework framework;public Car() {framework = new Framework();System.out.println("Car init...");}public void run() {System.out.println("Car run...");}}/*** 车身类*/static class Framework {private Bottom bottom;public Framework() {bottom = new Bottom();System.out.println("Framework init...");}}/*** 底盘类*/static class Bottom {private Tire tire;public Bottom() {tire = new Tire();System.out.println("Bottom init...");}}/*** 轮胎类*/static class Tire {private int size;public Tire() {this.size = 17;System.out.println("轮胎尺寸:" + size);}}
}

如果我们在造车的时候,需要造车的一方指定轮胎大小的话,那么这个生产车的代码进行较大的改动。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,当需要造车方指定轮胎的大小的时候,基本上所有的零件的代码都需要做出更改,这就叫做 高耦合

什么叫做高内聚、低耦合呢?

相比大家经常会听到高内聚、低耦合这句话吧,那么它们到底代表的什么意思呢?

“高内聚、低耦合”是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低。

  • 高内聚:内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。一个模块内各个元素彼此结合的紧密程度高,则内聚性高。所谓高内聚就是一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。
  • 低耦合:耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。一个程序结构中各模块的内聚程度越高,模块间的耦合程度就越低。

上面我们设计的代码的耦合程度就比较高,那么应该如何降低耦合度呢?

我们可以将各个零件之间的依赖关系给改变一下。

在这里插入图片描述
我们先根据需要,创造出指定大小的轮胎,然后将造好的轮胎给底盘创造厂,然后再造好底盘,将造好的底盘交给车身制造厂,制造出车身,最后将造好的车身交给汽车制造厂,最终制造出来一个汽车。

public class NewCarExample {public static void main(String[] args) {Tire tire = new Tire(20);Bottom bottom = new Bottom(tire);Framework framework = new Framework(bottom);Car car = new Car(framework);car.run();}/*** 汽车对象*/static class Car {private Framework framework;public Car(Framework framework) {this.framework = framework;System.out.println("Car init...");}public void run() {System.out.println("Car run...");}}/*** 车身类*/static class Framework {private Bottom bottom;public Framework(Bottom bottom) {this.bottom = bottom;System.out.println("Framework init...");}}/*** 底盘类*/static class Bottom {private Tire tire;public Bottom(Tire tire) {this.tire = tire;System.out.println("Bottom init...");}}/*** 轮胎类*/static class Tire {private int size;public Tire(int size) {this.size = size;System.out.println("轮胎尺寸:" + size);}}
}

通过更改各个类之间的依赖关系,那么就算底层轮胎如何变化,也不会影响整个产业链,这样就实现了代码之间的解耦,从而实现了更加灵活、通用的程序设计了。

通过上面的优化,我们发现:类的创建顺序是相反的,之前是 Car 控制并创建了 Framework,Framework 创建并控制创建了 Bottom,Bottom 创建并控制创建了 Tire,改进之后的控制权发生了反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入到当前对象中,依赖对象的控制权不再由当前类控制了。

这样,即使依赖对象发生任何变化,当前类都是不受影响的,这就是典型的控制反转,也就是是 IoC 的实现思想。

知道了什么是容器以及什么是 IoC 之后我们就知道了什么叫做 IoC 容器了。

在这里插入图片描述
IoC 容器的优点:

通过上面的案例我们可以看出来,使用 IoC 容器,资源不再由使用资源的双方管理,而是由不使用资源的第三方进行管理,这样可以带来以下好处:1. 实现资源的集中统一管理;2. 降低了使用资源的双方的依赖程度,也就是耦合程度。

  1. 资源集中管理:IoC容器会帮我们管理一些资源(对象)等,我们在使用的时候只需要从IoC中去取就可以了。
  2. 我们在创建实例的时候不需要了解其中的具体细节,降低了使用资源的双方的依赖程度(耦合程度)。

2. 什么是DI

DI(Dependency Injection)即依赖注入,是面向对象编程中的一种设计模式,用来减少代码之间的耦合度。

具体来说,依赖注入将对象的创建和管理权从代码中转移到了外部容器,通过外部容器来创建对象并注入需要的依赖。这种方式可以降低代码的耦合度,提高代码的可重用性和可维护性。

在Java中,Spring框架是使用依赖注入最广泛的开源框架之一。通过使用依赖注入,Spring可以将应用程序中的各个组件解耦,使得它们之间的依赖关系变得更加清晰和易于管理。

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

从这点来看,依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。

在造汽车的过程中,将 Tire 这个依赖注入到 Bottom 中造出 Bottom,然后将造好的 Bottom 依赖注入到 Framework 中造出 Framework,最后将造好的 Framework 依赖注入到 Car 中,最终创建出 Car。

IoC 是⼀种思想,也是"⽬标",⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就属于具体的实现。所以也可以说, DI 是 IoC 的⼀种实现。

IoC & DI 的使用

Spring 既然是一个 IoC 容器,那么他肯定具有两个基本的功能:存和取。

Spring 容器管理的主要是对象,这些对象我们称之为“Bean”,这个跟我们前面学习的 Bean 不一样。我们把这些 Bean 交给 Spring 进行管理,由 Spring 来负责对象的创建和销毁,我们在写 Spring 代码的时候只需要告诉 Spring,哪些对象是我们要交给 Spring 管理,我们又要取出哪些对象进行使用。

那么在 Spring 中,如何存储和取出 Bean 呢?

  1. 将类存储进 Spring IoC 容器中需要使用 @Component 注解,其实还有很多注解,这里我们先为大家介绍这个注解,本文后面再为大家介绍另外几种存储 Bean 的注解
  2. 取出依赖对象使用注解 @Autowired
package com.example.springiocdi20231209;import org.springframework.stereotype.Component;@Component
public class UserComponent {public void sayHi() {System.out.println("hello spring");}
}
package com.example.springiocdi20231209;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/component")
public class GetMessage {@Autowiredprivate UserComponent userComponent;@RequestMapping("/get")public void get() {userComponent.sayHi();}
}

在这里插入图片描述
在这里插入图片描述
这里显示出了我们想要的结果,就说明我们使用 @Componnet 注解和 @Autowired 注解对 Bean 实现了存储和取出。

需要注意的是:当我们在使用 @Autowired 注解的时候,需要保证这个类有 Controller 或者 RestController 注解,因为我们既然要想使用 Spring 的 IoC 容器肯定要保证这个类是被 Spring 管理的。

IoC详解

上面为大家展示了 IoC 和 DI 的基本使用,接下来将为大家详细的讲解一下 IoC。

Bean的存储

上面我们存储 Bean 使用的是 @Component 注解,而 Spring 框架为了更好的服务 Web 应用程序,提供了更丰富的注解。

  1. 类注解:@Controller、@Service、@Repository、@Componet、@Configuration。
  2. 方法注解:@Bean

@Controller注解

package com.example.springiocdi20231209.Controller;import org.springframework.stereotype.Controller;@Controller
public class UserController {public void sayHi() {System.out.println("hi, spring");}
}

使用 @Controller 就将这个 Bean 给存储到 IoC 容器中了,那么我们如何获取这个 Bean 呢?

如何获取Bean

获取 Bean 的方法有很多种,我们只要介绍下面的第1、2、4种。
在这里插入图片描述

1. 根据Bean的名称获取Bean

我们可以根据 Bean 的名字来获取到指定的 Bean,但是某个 Bean 的名称是什么,我们该怎么知道呢?我们来看看官方的解释。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
简单来讲就是当类名中前两个字母中大写字母小于2的时候,那么该类交给 IoC 后就会以第一个字母小写的小驼峰形式命名,当类名的前两个字母都为大写字母的时候,那么该 Bean 名就是原类名。

所以要以 Bean 名获取到 UserController 这个 Bean 的话,就需要将 userController 作为参数。

package com.example.springiocdi20231209;import com.example.springiocdi20231209.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class SpringIoCDi20231209Application {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);UserController userController = (UserController) context.getBean("userController");}
}

我们这个代码是在 项目名称+Application 这个类中写的,准确来说是在有 @SpringBootApplication 这个注解的类中写的,并且 SpringApplication.run() 方法是可以有返回值也可以没有返回值的,我们可以根据需要使用变量来接收这个方法的返回值。要想获取到 IoC 容器中的 Bean,需要依靠 ApplicationContext 这个类,所以我们就用这个类的变量来接收 run 方法的返回值。

在这里插入图片描述

启动项目的时候,就会发现我们预想中的结果出现在了控制台中,并且这个不需要我们发送什么 Http 请求,而是启动项目就会自动执行这个类当中的代码。并且通过 Bean 名获取到的 Bean 名返回的是一个 Object 类型,所以在拿变量进行接收的时候就需要进行类型的转换。

2. 根据Bean类型获取Bean

可以通过 Bean 类型来获取到 Bean。

package com.example.springiocdi20231209;import com.example.springiocdi20231209.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class SpringIoCDi20231209Application {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);//1. 根据Bean名字来获取Bean//UserController userController = (UserController) context.getBean("userController");//2. 根据Bean类型来获取BeanUserController userController = context.getBean(UserController.class);userController.sayHi();}
}

在这里插入图片描述

3. 根据Bean名和Bean类型获取Bean

通过 Bean 名获取 Bean 需要进行类型的转换,可以在传递参数的时候就指定返回值的类型。

package com.example.springiocdi20231209;import com.example.springiocdi20231209.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class SpringIoCDi20231209Application {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);//1. 根据Bean名字来获取Bean//UserController userController = (UserController) context.getBean("userController");//2. 根据Bean类型来获取Bean//UserController userController = context.getBean(UserController.class);//3. 根据Bean名和Bean类型获取BeanUserController userController = context.getBean("userController", UserController.class);userController.sayHi();}
}

在这里插入图片描述

@Service注解

通过这个注解,也可以将类交给 Spring 进行管理。

package com.example.springiocdi20231209.Controller;import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;@Service
public class UserController {public void sayHi() {System.out.println("hi, spring");}
}

在这里插入图片描述

@Repository注解

package com.example.springiocdi20231209.Controller;import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;@Repository
public class UserController {public void sayHi() {System.out.println("hi, spring");}
}

在这里插入图片描述

@Component注解

package com.example.springiocdi20231209.Controller;import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;@Component
public class UserController {public void sayHi() {System.out.println("hi, spring");}
}

在这里插入图片描述

@Configuration注解

package com.example.springiocdi20231209.Controller;import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;@Configuration
public class UserController {public void sayHi() {System.out.println("hi, spring");}
}

在这里插入图片描述

为什么会有这么多类注解

这个也是和咱们前⾯讲的应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途。

  • @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应
  • @Servie:业务逻辑层,处理具体的业务逻辑
  • @Repository:数据访问层,也称为持久层.负责数据访问操作
  • @Configuration:配置层.处理项⽬中的⼀些配置信息

在这里插入图片描述

并且通过观察这五个注解的源码我们可以发现一些问题。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:

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

@Controller @Service 和 @Repository ⽤于更具体的⽤例(分别在控制层,业务逻辑层,持久化层),在开发过程中,如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更好的选择。

方法注解

类注解是写在我们项目代码中的类上的,但是存在两个问题:

  1. 使用外部包里的类,没办法添加类注解
  2. 一个类,需要多个对象,比如多个数据源

上面两个问题是无法使用类注解来解决的。所以也就出现了方法注解 @Bean

假设我们这里的 User 类是一个外部包里的类,那么我们就无法在这个类中添加类注解,这是就需要使用到方法注解。

package com.example.springiocdi20231209;import org.springframework.context.annotation.Bean;public class BeanConfig {@Beanpublic User user() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
}

运行 @SpringApplication 注解的代码,看看什么效果。

在这里插入图片描述
这里报错说这个 Bean 没有被定义。其实使用方法注解 @Bean 的时候,需要保证该方法所在的类也有被类注解注释。

package com.example.springiocdi20231209;import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Component
public class BeanConfig {@Beanpublic User user() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
}
package com.example.springiocdi20231209;import com.example.springiocdi20231209.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class SpringIoCDi20231209Application {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);User user = context.getBean(User.class);System.out.println(user);}
}

在这里插入图片描述

同一个类定义多个对象。

package com.example.springiocdi20231209;import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Component
public class BeanConfig {@Beanpublic User user() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}@Beanpublic User user2() {User user = new User();user.setName("liis");user.setAge(20);return user;}
}

当我们使用方法注解,并且一个类有多个相同类型的 Bean 类型的时候,并且我们通过 Bean 类型获取 Bean 的话就会出错。

在这里插入图片描述
所以这里获取 Bean 的话就需要指定 Bean 名称。

User user = context.getBean("user2", User.class);

在这里插入图片描述

@Bean 注解的 Bean,Bean 的,名称就是方法名。

重命名Bean

前面我们呢说了 Bean 的默认名称,但其实我们可以指定 Bean 的名称。那么如何重命名 Bean 呢?

@Bean("beanName")
package com.example.springiocdi20231209;import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Component
public class BeanConfig {@Bean("u1")public User user() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}@Bean("u2")public User user2() {User user = new User();user.setName("liis");user.setAge(20);return user;}
}
ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
User user = context.getBean("u2", User.class);
System.out.println(user);

在这里插入图片描述
可以看到我们通过重命名的名字u2获取到了Bean。

不仅如此,通过观察 @Bean 的源码我们可以发现,这里的name参数是一个字符串数组,也就是说一个 Bean 可以有多个名字。

在这里插入图片描述

@Bean({"u2", "s2"})

在这里插入图片描述

同样的类注解也可以重命名,但是类注解只支持一个名字。

在这里插入图片描述

@Configuration("c1")
public class UserController {public void sayHi() {System.out.println("hi, spring");}
}

在这里插入图片描述

扫描路径

其实并不是项目下的所有文件中的加了注解的类都会被 Spring 进行管理,而是需要看扫描路径在哪。假设我们将 @SpringBootApplication 注解所在的类给换个路径。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

@SpringBootApplication
public class SpringIoCDi20231209Application {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);User user = context.getBean("u1", User.class);System.out.println(user);}
}

在这里插入图片描述
这里就报错说找不到 u1 这个 Bean,说明这个注解没有被扫描到,那么为什么呢?

这其实跟 @ComponentScan 注解配置的扫描路径有关,但是我们 SpringBootApplication 注解的类当中不是没有这个注解吗?其实这个注解继承了 @ComponentScan 注解。

在这里插入图片描述
而如果 @ComponnetScan 没有配置的话,就默认的是当前 @ComponentScan 注解的文件所在的路径。

这里 @SpringBootApplication 注解的类所在的路径是这个 package com.example.springiocdi20231209.springiocdi20231209.Controller;

而我们的 u1 Bean 所在的路径是 package com.example.springiocdi20231209.springiocdi20231209;,所以这个 Bean 是 Component 无法扫描到的。

要想扫描到这个路径,我们可以对 @ComponentScan 注解进行配置。

@ComponentScan({"com.example.springiocdi20231209.springiocdi20231209"})
@SpringBootApplication
public class SpringIoCDi20231209Application {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);User user = context.getBean("u1", User.class);System.out.println(user);}
}

在这里插入图片描述
@ComponentScan 也是可以配置多个扫描路径的。

DI 详解

依赖注⼊是⼀个过程,是指IoC容器在创建Bean时,去提供运⾏时所依赖的资源,⽽资源指的就是对象。在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作。简单来说,就是把对象取出来放到某个类的属性中。

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

关于依赖注入,Spring 为我们提供了三种方法:

  1. 属性注入(Filed Injection)
  2. 构造方法注入(Constructor Injection)
  3. Setter 注入(Setter Injection)

1. 属性注入

属性注⼊是使⽤ @Autowired 实现的。

package com.example.springiocdi2;import org.springframework.stereotype.Service;@Service
public class UserService {public void sayHi() {System.out.println("Hi UserService");}
}
package com.example.springiocdi2;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController {//属性注入@Autowiredprivate UserService userService;public void sayHi() {System.out.println("Hi UserController...");userService.sayHi();}
}
package com.example.springiocdi2;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class SpringIoCDi2Application {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDi2Application.class, args);UserController userController = context.getBean("userController", UserController.class);userController.sayHi();}}

运行结果:

在这里插入图片描述

构造方法注入

构造⽅法注⼊是在类的构造⽅法中实现注⼊。

package com.example.springiocdi2;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController2 {//构造方法注入private UserService userService;@Autowiredpublic UserController2(UserService userService) {this.userService = userService;}public void sayHi() {System.out.println("Hi UserController2");userService.sayHi();}
}

在这里插入图片描述
注意事项:如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法,
那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法。

在这里插入图片描述
在这里插入图片描述

Setter 注入

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

package com.example.springiocdi2;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController3 {//setter方法注入private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public void sayHi() {System.out.println("Hi UserController3");userService.sayHi();}
}

在这里插入图片描述
如果没加@Autowired 注解,就会报错。

在这里插入图片描述

三种注入的优缺点

  1. 属性注入
  • 优点:简洁,使用方便
  • 缺点:
    • 只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)
    • 不能注入一个 Final 修饰的属性
  1. 构造函数注入(Spring 4x推荐)
  • 优点:
    • 可以注入 Final 修饰的属性
    • 注入的对象不会被修改
    • 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法
    • 通用性好,构造方法是JDK支持的,所以更换任何框架,他都是适用的
  • 缺点:
    • 注入多个对象的时候,代码会比较繁琐
  1. Setter注入(Spring 3x推荐)
  • 优点:⽅便在类实例之后,重新对该对象进⾏配置或者注⼊
  • 缺点:
    • 不能注⼊⼀个Final修饰的属性
    • 注⼊对象可能会被改变,因为setter⽅法可能会被多次调⽤,就有被修改的⻛险

属性注入,注入一个final修饰的属性:
在这里插入图片描述
构造方法注入,注入一个final修饰的属性:
在这里插入图片描述
Setter注入,注入一个final修饰的属性:

在这里插入图片描述

为什么有些注入不能注入 final 修饰的属性?

如果一个属性被final关键字修饰,那么这个属性就成为了一个常量,它的值就不能被改变。而依赖注入的属性注入需要动态地修改属性的值,所以不能对被final关键字修饰的属性进行依赖注入。但是,在构造方法中,final属性可以被赋值。这是因为构造方法是在对象创建时执行的,此时final属性还没有被赋值。因此,在构造方法中可以对final属性进行赋值操作。

Autowired 存在的问题

当同一个类类型存在多个 Bean 时,就会出现问题。

package com.example.springiocdi2;import lombok.Data;@Data
public class User {private String name;private Integer age;
}
package com.example.springiocdi2;import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Component
public class BeanConfig {@Bean("u1")public User user1() {User user = new User();user.setName("zhangsan");user.setAge(17);return user;}@Bean("u2")public User user2() {User user = new User();user.setName("lisi");user.setAge(18);return user;}
}
package com.example.springiocdi2;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class UserController4 {@Autowiredprivate User user;public void sayHi() {System.out.println("Hi UserController4");System.out.println(user);}
}

在这里插入图片描述
如何解决这个一个类型有多个 bean 的问题呢?Spring 提供了以下的几种方案:

  • @Primary
  • @Qualifier
  • Resource

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

	@Bean("u1")@Primary  //指定该bean为默认实现public User user1() {User user = new User();user.setName("zhangsan");user.setAge(17);return user;}

在这里插入图片描述

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

  • @Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
	@Qualifier("u2")@Autowiredprivate User user;

在这里插入图片描述
使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。

	@Resource(name = "u2")private User user;

在这里插入图片描述

常见面试题:
@Autowird 与 @Resource的区别:

  • @Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
  • @Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊。相⽐于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/240272.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Docker一键部署Uptime Kuma,并将监控服务映射至公网访问

文章目录 **主要功能**一、前期准备本教程环境为:Centos7,可以跑Docker的系统都可以使用本教程安装。本教程使用Docker部署服务,如何安装Docker详见: 二、Docker部署Uptime Kuma三、实现公网查看网站监控四、使用固定公网地址访问…

go语言初体验1--使用go install

当安装后go语言后。 尝试编写go程序。 当使用 go install 命令,报错。 go: go install requires a version when current directory is not in a moduleTry go install jvmgo\ch01latest to install the latest version通过查找资料。 用命令: go env …

混合精度训练(MAP)

一、介绍 使用精度低于32位浮点数的数字格式有很多好处。首先,它们需要更少的内存,可以训练和部署更大的神经网络。其次,它们需要更少的内存带宽,这加快了数据传输操作。第三,数学运算在降低精度的情况下运行得更快&a…

YOLOv5算法改进(23)— 更换主干网络GhostNet + 添加CA注意力机制 + 引入GhostConv

前言:Hello大家好,我是小哥谈。本节课就让我们结合论文来对YOLOv5进行组合改进(更换主干网络GhostNet + 添加CA注意力机制 + 引入GhostConv),希望同学们学完本节课可以有所启迪,并且后期可以自行进行YOLOv5算法的改进!🌈 前期回顾: YOLOv5算法改进(1)— 如何去…

C++类与对象(中)第一篇

目录 前言: 类的六个默认成员函数 构造函数 析构函数 拷贝构造函数 拷贝场景一:函数参数类型为类类型对象 拷贝场景二:利用已存在的对象创建新对象 拷贝场景三:函数返回值类型为类类型对象 前言: 编译器编译类…

推箱子地图库1-49关

推箱子地图库1-49关 49关 local WALL1--{"墙","墙 "}4 10287 local DEST2--{"目的地",""}1 4001100 10157 local BOX3--{"箱子","¥"} 2 2000801 local PLAYER4--{"玩家","&&a…

influxdb-cluster集群部署

一.部署环境 * InfluxDB集群节点数:mate服务至少3个节点,节点数越多,集群性能越高。 * 操作系统:支持的操作系统包括Linux、Windows和MacOS。 * CPU:至少2核4线程,主频越高越好。 * 内存:至少8…

【已解决】Redis序列化反序列化不一致 - String类型值多了双引号问题

在项目中使用spring 的RedisTemplate从redis中获取数据的时候,发现字符串的value多了双引号。如下图所示: 产生的原因可以分一下几个方面: 一、采用的序列化对象不同 多服务之间调用时候,序列化服务A(向redis中写数据的)和反序…

【翼韵】数据上传沟通、决策、试错

韵达德邦来说说,翼达、翼韵、翼德、翼邦的小记录 翼达同学:沟通成本好大! 翼韵同学:决策成本很大! 翼德同学:试错成本更大! 翼邦同学:你们加起最大! QY成本沟通成本33%决…

Win7如何修改MAC地址

MAC地址,又叫做物理地址、硬件地址,是用来定义网络设备的位置,一般情况下,MAC地址在网卡中是固定的,但不排除有人手动去修改自己的MAC地址。win7如何修改MAC地址?其实修改MAC地址的方法很简单,可以通过硬件…

K8s出现问题时,如何排查解决!

K8s问题的排查 1. POD启动异常、部分节点无法启动pod2. 审视集群状态3. 追踪事件日志4. 聚焦Pod状态5. 检查网络连通性6. 审视存储配置7. 研究容器日志8. K8S集群网络通信9. 问题:Service 是否通过 DNS 工作?10. 总结1、POD启动异常、部分节点无法启动p…

普通Java项目打包可执行Jar

普通Java项目打包 IDEA配置 在项目配置中选择 Artifacts -> JAR -> From modules with dependencies 选择项目模块,程序主类、依赖引入方式、清单文件位置 确认Jar名称和Jar输出目录 通过 Build -> Build Artifact -> Build 打包Jar文件 Java打包可执…

JavaWeb笔记之SVN

一、版本控制 软件开发过程中 变更的管理; 每天的新内容;需要记录一下; 版本分支;整合到一起; 主要的功能对于文件变更的追踪; 多人协同开发的情况下,更好的管理我们的软件。 大型的项目;一个团队来进行开发; 1: 代码的整合 2: 代…

2023-强网杯-【强网先锋-ez_fmt】

文章目录 ez_fmt libc-2.31.so检查main思路exp 参考链接 ez_fmt libc-2.31.so 检查 没有地址随机化 main 简单粗暴的printf格式化字符串漏洞 思路 泄露地址,覆盖返回地址形成ROP链 printf执行时栈上存在__libc_start_main243的指令的地址,可以泄露…

C++哈希表的实现

C哈希表的实现 一.unordered系列容器的介绍二.哈希介绍1.哈希概念2.哈希函数的常见设计3.哈希冲突4.哈希函数的设计原则 三.解决哈希冲突1.闭散列(开放定址法)1.线性探测1.动图演示2.注意事项3.代码的注意事项4.代码实现 2.开散列(哈希桶,拉链法)1.概念2.动图演示3.增容问题1.拉…

MyBatis 架构分析

文章目录 三层架构一、基础支撑层1.1 类型转换模块1.2 日志模块1.3 反射工具模块1.4 Binding 模块1.5 数据源模块1.6 缓存模块1.6 解析器模块1.7 事务管理模块 二、核心处理层2.1 配置解析2.2 SQL 解析与 scripting 模块。2.3 MyBatis 中的 scripting 模块就是负责动态生成 SQL…

SpringCloud Alibaba(itheima)

SpringCloud Alibaba 第一章 微服务介绍1.1系统架构演变1.1.1单体应用架构1.1.2垂直应用架构1.1.3分布式架构1.1.4 SOA架构1.1.5微服务架构 1.2微服务架构介绍1.2.1微服务架构的常见问题1.2.2微服务架构的常见概念1.2.3微服务架构的常见解决方案 1.3 SpringCloud Alibaba介绍1.…

用23种设计模式打造一个cocos creator的游戏框架----(二十二)原型模式

1、模式标准 模式名称:原型模式 模式分类:创建型 模式意图:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象 结构图: 适用于: 1、当一个系统应该独立于它的产品创建、构成和表示时 2、…

BUUCTF-Crypto合集-WP

获取CTF工具可关注CSJH网络安全团队,回复CTF工具 一眼就解密 下面的字符串解密后便能获得flag:ZmxhZ3tUSEVfRkxBR19PRl9USElTX1NUUklOR30 注意:得到的 flag 请包上 flag{} 提交 大小写字母加数字,而且等于号结尾,bas…

实在智能斩获钛媒体2023全球创新评选科技类「 大模型创新应用奖」

近日,历时三天的钛媒体2023 T-EDGE全球创新大会以“新视野新链接”为主题在北京隆重举办。作为科创领域全新高度的年度盛事,大会吸引了AI各产业链近百位海内外创投人、尖端企业家、商业领袖和国际嘉宾齐聚一堂,围绕新一轮AI革命、智慧数字化、…