首页 > 其他分享 >控制反转与依赖注入与容器

控制反转与依赖注入与容器

时间:2024-03-20 09:23:21浏览次数:29  
标签:容器 依赖 Resource Spring Bean 反转 注解 public

一、控制反转与依赖注入

1.如何理解控制反转IOC(inversion of control)和依赖注入DI(dependcy Injection)

先说结论:控制反转是一种思想,而依赖注入是其表现形式。好比说IOC是接口,DI是其实现类。IOC就是关于对象的创建。由程序员写程序创建的对象(new一个对象)这种就是正常创建。使用容器创建的对象(一般是DI的形式)就是IOC了。

控制反转(Inversion of Control,IoC)是一种软件设计模式,它通过将控制流程的权利从程序自身转移到外部容器或框架,实现了程序的松耦合。在Spring框架中,IoC是通过依赖注入(Dependency Injection)来实现的,其中最常见的是通过构造函数注入或者Setter方法注入。

比如说这里就是一个对象的创建过程

ModelDao modelDao = new ModelDao();

而下面这种就是IOC的体现了

@Autowired

private ModelDao modelDao;

因为这里的modelDao,并不是我们new出来的对象,而是Spring框架创建出来的对象。

简单总结一下spring框架创建对象的流程:

第一步扫描组件:

在Spring应用程序启动时,Spring容器会扫描指定的包路径(通常是主应用程序类所在的包及其子包)以找到被@Component及其衍生注解(如@Service、@Repository等)标记的类。

第二步创建Bean:

对于扫描到的被标记的类,Spring容器会实例化它们,然后将这些实例化的对象称为Bean

第三步注册Bean:

这些被实例化的Bean会被注册到Spring容器中。在容器中,每个Bean都有一个唯一的标识符(通常是类名的首字母小写形式)。

第四步应用程序运行:

一旦所有Bean都被创建并注册到容器中,Spring容器就会完成初始化过程,应用程序就可以运行了。

第五步依赖注入:

如果在这些Bean中存在被@Autowired等注解标记的字段、构造函数或方法,Spring容器会负责解决这些依赖关系,将相应的Bean注入到对应的地方。

注意,以上的每一步都需要有些需要注意的而部分

2.@Component简单介绍

扫描组件的时候,所有被@Component及其派生的注解都会被扫描到。这里简单看一下@service这个注解

image.png

被箭头指着的这句注解的意思,就类似于Java中的派生,代表这两个注解是等价的。所以@Service也可以被Spring的容器扫描到。其他的几个注解也是一样的。具体的注解后面会详细说。关于这个注解上面的这些元注解,在自定义注解这个篇章会详细说。现在先按下不表。

3.SpringBoot中的容器

Spring容器主要有两种类型:

BeanFactory:

是Spring容器的基础接口,提供了基本的IoC功能。它延迟加载对象,即在需要使用对象时才进行实例化。

ApplicationContext:

是BeanFactory的扩展,提供了更多的功能,如事件传播、AOP支持、国际化等。ApplicationContext在容器启动时就实例化了所有的Bean,因此在应用程序运行期间,可以直接从ApplicationContext获取Bean实例。

因为ApplicationContext是BeanFactory的子接口。所以BeanFactory的基础功能ApplicationContext都有,所以我们平常接触的都是ApplicationContext。就好比Object和其派生类一样,Object很重要,但我们平时不用它

在Spring Boot应用程序中,实际上是使用 ApplicationContext 接口的具体实现类,通常是 AnnotationConfigApplicationContext(基于注解配置)或 ClassPathXmlApplicationContext(基于XML配置)等。这些实现类继承了 BeanFactory 接口,因此在使用Spring Boot时,你实际上是得到了一个ApplicationContext,而ApplicationContext本身就包含了BeanFactory的功能。

在典型的Spring Boot应用中,你通常不直接使用 BeanFactory 接口,而是使用 ApplicationContext,因为它提供了更多的功能,如自动配置、组件扫描、AOP等。

Spring Boot会在启动时自动创建一个ApplicationContext实例,其中包含了应用程序中所有需要管理的Bean的定义。这个ApplicationContext实例负责实例化和管理这些Bean。

所以说遇到它们的报错如:

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled。

一般就处在被注解标注的组件上了

实例化的注意事项

Spring容器(对于Springboot而言就是ApplicationContext)它对于每一个类,只会实例化一个对象

当Spring容器扫描到一个类时,它会根据配置信息创建一个Bean的实例,并将这个实例注册到容器中。之后,如果在应用程序的其他地方需要引用这个类,Spring容器会直接提供之前创建的Bean实例,而不会重新实例化新的对象。

这种单例模式是Spring默认的作用域(scope)。Spring框架提供了不同的作用域,包括默认的单例(Singleton)作用域,以及其他如原型(Prototype)、请求(Request)、会话(Session)等作用域。但是,大多数情况下,Bean都是以单例模式存在的。

需要注意的是,虽然大多数Bean是单例的,但这并不是绝对的。可以通过@Scope注解或在XML配置中明确指定Bean的作用域,来改变Bean的创建和管理方式。

被@Scope标注的会有不同

`@Scope`: 用于指定Bean的作用域,如"singleton"(默认)、"prototype"等。

(冷知识,上面两个单词,一个是单例模式中的单例,一个是原型模式中的原型)

如果选择singleton,那就和没写一样。但如果写了prototype。那么每一次的@Resource或者是@Autowired,都会重新实例化一个对象

而且ApplicationContext其实是懒加载,并不是程序一开始就把所有被标注的类全部都实例化的,而是会等,等到这个bean被用到的时候才会实例化并进行注册。注意,当程序第一次调用bean的时候会进行实例化,这个过程需要用到Java的反射机制(Java的反射机制后面会单独开一期).

这里简单说一下关于bean和Javabean的区别。Javabean是一种规范(专题里面有),是一个抽象的概念,遵守对应规则写成的Class就是JavaBean。而bean单独指的是被spring容器实例化的对象。是具体的。

还有一点提前先说下,后面注解哪里还会强调。这些Class的实例化对象其实也都是有名称的。

比如我们可以这样写

@Component("model")

public class MyModel {

 // ...

}

其实就类似于 Mymodel model = new Mymode();

如果没有写名字的话,就会默认给一个myModel的变量名(类名的小写)

所以说之前说的

@AutoWired和@Resource中依赖导入时

@Autowired是通过类型来进行导入的,也就是上例中的MyModel(毕竟每一个MyModel类型在当前包只有一个,所以可以锁定)

@Resource则是通过名称来导入的,也就是model。最开始我有点误区,我以为

image.png

这个被红箭头指向的是对象名。其实不是的,它只是引用的别名。这个对象本身的名字在它的注解里面标明,这里只是引用时会用到的别名。

@Resource其实应该写(name=""),比如说:

  @Resource(name = "myBean123")

 private MyBean myBean; 

那么就会去查myBean123。但如果什么都没写,比如说:

@Resource

private MyBean myBean;

那就回去查找名称为myBean的bean类,如果没有找到的话就会报异常了

@Resource有两个重要的属性:name和type

Spring将@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。

@Resource装配顺序:

(1)如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

(2)如果指定了name,则从Spring上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常

(3)如果指定了type,则从Spring上下文中找到类型匹配的唯一bean进行装配,找不到或找到多个,都抛出异常

(4)如果既没指定name,也没指定type,则自动按照byName方式进行装配。如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入。

再多说一句,bean,也就是IOC创建的对象是单例的(一般来说是),也就是说依赖注入的对像有且只有一个,且它是在程序启动时创建,程序结束时销毁的。它的存在贯穿整个程序运行始终。所以如果修改了它的值,那么其他地方引用他的时候也会修改它的值。这其实也是IOC和DI的特点之一,实现数据的实时共享(不过这也是需要注意的点,这也是为什么一般都是只有方法没有成员变量的类作为bean存在)。

4.为什么我们会选择使用IOC

1.松耦合

类A需要使用类B的一个对象,但是我们不需要初始化这个类B。就可以直接用。代码完全不会受到影响。只要在最外层注入他,那什么时候想用了,直接拿来用就可以了。有点像静态变量,一次声明,一直使用。其他的什么都不用管。这耦合度不就低了嘛。

bean对象是在springboot启动的时候就创建了的。所以本质上bean对象的创建和当前用它的类没有任何关系。(再次强调一下,DI的过程相当于只是给了个引用地址)

2.省空间

因为不需要一直初始化,我们知道,每次初始化一个对象的时候,都会赐予它工作空间。所以如果我们只是使用它的方法的话,大可以直接DI它。只用了一个对象自然不会浪费工作空间了。

3.实现数据的实时共享

极其类似于静态资源(静态资源部分有专题),如果在某一个时候改变了这个bean对象的值,那么在程序进行的过程中任何时候调用它得到的都是修改后的值。而且这种DI导入的类容易分不清(不想静态资源有诸多限制和提示)所以建议是需要用到数据共享的时候,创建一个新的静态资源。IOC一般只用于没有成员变量,只有成员方法的类型(因为方法只能被调用,无法被修改,所以一般被DI的都是这种类型)。

5.IOC相关注解详细介绍

spring的注解分为两类:

1、一类是使用Bean,即是把已经在xml文件中配置好的Bean拿来用,完成属性、方法的组装;比如@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean;

 

2、一类是注册Bean,@Component , @Repository , @ Controller , @Service , @Configration这些注解都是把你要实例化的对象转化成一个Bean,放在IoC容器中,等你要用的时候,它会和上面的@Autowired , @Resource配合到一起,把对象、属性、方法完美组装。

详情如下:

在Spring Boot中,用于实现控制反转(IoC)和依赖注入的注解有很多,每个注解都有不同的用途。以下是一些常用的IoC相关注解:

1. `@Component`: 基本的注解,用于标识一个类为Spring组件,可以被Spring容器扫描并管理。

2. `@Service`: 用于标识服务层组件,通常用于服务类。

3. `@Repository`: 用于标识数据访问组件,通常用于DAO(Data Access Object)类。

4. `@Controller`: 用于标识控制器组件,通常用于Spring MVC中的控制器类。

5. `@RestController`: 类似于`@Controller`,但专门用于RESTful风格的控制器。

6. `@Configuration`: 用于标识配置类,通常用于定义Spring Bean的配置。

7. `@Bean`: 用于定义一个Spring Bean,通常在`@Configuration`类中使用。

8. `@Autowired`: 用于依赖注入,可以标记在字段、构造函数、Setter方法上,以将其他Bean注入到当前Bean中。

9. `@Qualifier`: 用于指定依赖注入时的具体Bean名称,当存在多个候选Bean时使用。

10. `@Value`: 用于注入外部配置属性的值。

11. `@Lazy`: 延迟初始化,用于标记Bean为延迟加载,只有在首次使用时才会被初始化。

12. `@Scope`: 用于指定Bean的作用域,如"singleton"(默认)、"prototype"等。

13. `@Primary`: 用于标记首选的Bean,当多个Bean满足条件时,被标记为`@Primary`的Bean将被优先选择。

这些注解允许你在Spring Boot应用程序中轻松配置和管理Bean,以实现IoC和依赖注入。根据不同的用途和需求,你可以选择合适的注解来标记你的类和组件。

@Resource和@AutoWired的区别:

image.png

其余的都比较了解,@Qualifier见的少,这里着重的用代码解释一下

public interface Animal {

void makeSound();

}

@Component("dog")

public class Dog implements Animal {

@Override

public void makeSound() {

System.out.println("Woof Woof!");

}

}

@Component("cat")

public class Cat implements Animal {

@Override

public void makeSound() {

System.out.println("Meow Meow!");

}

}

@Component

public class Zoo {

private final Animal animal;

// 使用@Qualifier注解指定要注入的bean的名称

@Autowired

public Zoo(@Qualifier("dog") Animal animal) {

 this.animal = animal;

}

public void perform() {

 animal.makeSound();

}

}

能看到吧,有多个可选的Bean存在时,可以通过名称来指定是哪一个。不过上述这种情况其实可以用@Resource来解决的。其他的比如作为参数对象传入的话就不太能用@Resource来替代了

而且其实一般来讲,除了像上述这种,因为父类出现导致问题外一般不会有其他的情况出现,毕竟类名对于一个程序包而言是唯一的

public Mybean2 car(@Qualifier("myBean123")MyBean myBean)

而这种写法往往出现在哎@Bean使用的情景下,@Bean和@Configuration经常连用,这里需要好好解释一下为什么。

首先需要介绍一下@Bean是什么。@Bean注解可以用于方法上,该方法返回一个对象,该对象将被Spring容器管理和提供给其他程序组件使用。

类似:

@Configuration

public class MyConfig {

 @Bean

 public MyBean myBean() {

 return new MyBean();

 }

}

这样的话,就会返回一个MyBean的实例对象,且这个实例对象也会放在Spring容器当中。就跟使用@Component这种情况得到的结果是等价的

不过@Component通常更适用于类级别的声明,而@Bean更适用于方法级别的声明。而且在这个方法里面是可以对这个Bean做各种处理的。

@Bean可以单独用,不过要求限制很多。而且大多数时候都和@Configure连用。其实有一点是比较好理解的,容器创建实例的过程理论上来说是先于其他程序的。所以如果用@Bean创建bean的话,应该是在其他程序之前,所以需要一个@Component的派生注解比如说专门和@Bean搭配的@Configuration。

这里有一个是实例,是前不久刚做过的一个

@Slf4j

@Configuration

public class WebSocketConfig {

 @Bean

 public ServerEndpointExporter serverEndpointExporter() {

 log.info("webSocket配置已加载");

 return new ServerEndpointExporter();

 }

前段时间梳理webSocket的时候,有一个配置文件。其实这个的作用也就是把ServerEndpointExporter,变成一个Spring容器的Bean。其实把bean放入容器不仅仅是供我们@Resource(DI)的,它们还相当于变成了官方的公共的工具类。可以完成一些内部的工作(这些不是我们能看到的了)。理解这个的本质就好了

这里有一个自己写的demo:

首先创建一个Javabean(对象)里面的成员变量,属性方法等都是比较全的

截图.png

然后写一个config来调用它:

@Configuration

public class MyConfig {

 @Bean

 public MyBean myBean() {

 MyBean myBean = new MyBean(10, "JYT");

 return myBean;

 }

}

这个时候,相当于myBean,已经是一个bean放入容器了。然后使用它:

  @Resource

 private MyBean myBean;

 /** 

 

结果自然是(下图),因为这里被注入的是已经被赋过值的对象了

截图.png

为什么要有@Bean注解?

原因很简单:类似@Component , @Repository , @ Controller , @Service 这些注册Bean的注解存在局限性,只能局限作用于自己编写的类,如果是一个jar包第三方库要加入IOC容器的话,这些注解就手无缚鸡之力了,是的,@Bean注解就可以做到这一点!当然除了@Bean注解能做到还有@Import也能把第三方库中的类实例交给spring管理,而且@Import更加方便快捷,只是@Import注解并不在本篇范围内,这里就不再概述。

因为@Bean的注解是加在方法上的

使用@Bean注解的另一个好处就是能够动态获取一个Bean对象,能够根据环境不同得到不同的Bean对象。

关于@Bean的名字在这里用:

@Configuration

public class MyConfig {

 @Bean (name = "myBean123")

 public MyBean myBean() {

 MyBean myBean = new MyBean(10, "JYT");

 return myBean;

 }

 /**

 * @Bean注解标注的方法参数位置:应用启动时,会前往Spring的IOC容器中寻找该类型的bean,并注入到方法的参数上,当然也可以结合@Qualifier注解完成指定bean的注入

 * 另外,如果获取配置文件中的参数值,可以结合@Value注解

 * @return

 */

 @Bean

// public Car car(@Qualifier("phone1") Phone phone, @Value("${data.username}") String name){

 public Mybean2 car(@Qualifier("myBean123")MyBean myBean){

 Mybean2 mybean2 = new Mybean2();

 mybean2.setAge(myBean.getAge()+1000);

 mybean2.setName(myBean.getName());

 return mybean2;

 }

}

说几个比较关键的点,这里涉及到了一个写名字的地方,@bean也可以作为参数参与,这里就不用导入了。注意这种写法

 

@Resource

 private MyBean myBean;

 @Resource

 private Mybean2 myBean2;

 /**

 * 测试输出结果

 */

 @RequestMapping(value = "", method = RequestMethod.POST)

 public ResponseResult<Void> test() {

 System.out.println(myBean.getName());

 System.out.println(myBean.getAge());

 System.out.println(myBean2.getName());

 System.out.println(myBean2.getAge());

 return new ResponseResult<>(SUCCESS);

 }

注意,如果@Bean没有设置名字的话,默认的名字是它的方法名。

这里的结果是:

image.png

6.为什么说Sprinboot的实现是基于IOC的呢

image.png

详情请见springboot特性及其启动器注解(就在本专题中,详细介绍了springboot的几大特性)可以看得出来,springboot的几大特性多多少少都是在自动配置的基础上才得以完成的。而自动配置则是IOC最彻底的体现

7.实例中出现的问题

有两个类,简称为A类和B类吧。现在呢,我在A类里面使用@Resource导入了一个C对象。然后再A类中写了一个方法D,方法D使用了C对象。然后我现在在B中想直接用A类的D方法,我当前的做法是直接在B类的程序中new了一个A对象,然后调用了A对象的D方法,运行的时候发现C对象报空了。

原因:

问题的根本原因是,当你直接在B类中使用new A()创建A类的对象时,这个对象并不由Spring容器管理,因此其中的依赖关系(如通过@Resource导入的C对象)没有得到正确的注入。所以,当你调用A类的方法D时,涉及到的C对象为null,导致空指针异常。

要解决这个问题,你可以让Spring容器来管理A类的对象,确保依赖关系得到正确注入。

@Component

public class B {@Component

public class A {

@Resource

private C c;

public void methodD() {

 // 使用C对象的逻辑

}

}

@Autowired

private A a;

public void someMethod() {

 // 调用A类的方法D

 a.methodD();

}

}

核心只有一个,如果一个类中用到了DI的方法注入了Bean,那么最好也用DI的方式来处理这个类

标签:容器,依赖,Resource,Spring,Bean,反转,注解,public
From: https://www.cnblogs.com/052160fh/p/18084432

相关文章

  • Java 编程实例:相加数字、计算单词数、字符串反转、元素求和、矩形面积及奇偶判断
    Java如何相加两个数字相加两个数字示例intx=5;inty=6;intsum=x+y;System.out.println(sum);//打印x+y的和输出11解释首先,声明两个int类型的变量x和y,并分别赋值为5和6。然后,使用+运算符将x和y相加,并将结果赋给变量sum。最后,使用Sy......
  • docker——容器的基本操作
    docker容器的基本操作run格式dockerrun[选项]镜像[命令][参数...]选项选项解释-d后台运行-i交互模式-t分配一个伪终端-p设置端口--rm运行完命令后,删除容器--name指定名称--dns指定dns(默认dns与主机一致)实例1.启动容器后自动终......
  • 视频转换容器格式
    容器格式视频容器格式是一种封装格式,用于存储在单一文件中的多种类型的数据,这通常包括视频和音频轨道、元数据(比如标题、作者等信息)、字幕和其他可能的数据流。容器格式定义了如何封装这些数据,但它不定义这些数据的编码方式。编码由视频和音频编解码器决定,而容器格式负责存储编码......
  • 在 Docker 容器中运行 ASP.NET Core 应用
    创建Docker支持生成Dockerfile文件在解决方案asp.netcore项目下右键添加Dockerfile支持、选择目标OS为Linux,生成Dockerfile文件#Seehttps://aka.ms/customizecontainertolearnhowtocustomizeyourdebugcontainerandhowVisualStudiousesthisDockerfiletobuild......
  • 轻量化部署工具Docker:参数化启动容器与数据卷挂载的综合应用
    dockerrun中的常见参数以mysql安装为例子dockerrun-d\--namemysql\-p3306:3306\-eTZ=Asia/Shanghai\-eMYSQL_ROOT_PASSWORD=123456\mysql-d表示后台执行–name容器的名字-p宿主机和容器映射的端口-e环境变量的设置此处设置了时区和密码......
  • 从零开始写 Docker(七)---实现 mydocker commit 打包容器成镜像
    本文为从零开始写Docker系列第七篇,实现类似dockercommit的功能,把运行状态的容器存储成镜像保存下来。完整代码见:https://github.com/lixd/mydocker欢迎Star推荐阅读以下文章对docker基本实现有一个大致认识:核心原理:深入理解Docker核心原理:Namespace、Cgroups......
  • 今天去面试,面试官问我什么是容器编排工具?Kubernetes
    今天去面试,面试官问我什么是容器编排工具?KubernetesKubernetes(简称k8s)是一个开源的容器编排平台,用于自动化应用程序部署、扩展和管理。它提供了一种高效的方式来管理容器化应用程序,使得开发人员和运维人员可以更好地协同工作。本文将介绍Kubernetes的集群架构和组件,并通过......
  • RHCE(podman容器)
    一:容器的基础1:为什么会出现容器? 开发和运维的矛盾:就是开发人员有个环境来专门进行开发使用的,运维人员的服务器上面没有开发的环境,因为,不是开发人员,如果添加了,服务器的占用的内存就大了起来,所以就出现了一个问题,当开发的东西放到运维的上面时,因为没有环境,这个就部署不上去,就会报......
  • .Net依赖注入神器Scrutor(上)
    前言从.NetCore开始,.Net平台内置了一个轻量,易用的IOC的框架,供我们在应用程序中使用,社区内还有很多强大的第三方的依赖注入框架如:AutofacDryIOCGraceLightInjectLamarStashboxSimpleInjector内置的依赖注入容器基本可以满足大多数应用的需求,除非你需要的特定功能......
  • MvvmLight中,两个依赖属性的值发生变化时影响第三个控件属性的用法
    使用数据绑定配合IValueConverter(值转换器)创建一个自定义转换器,该转换器接收两个输入值,并根据他们是否相等返回相应的输出值。然后将这个转换器应用到第三个控件的属性上1publicclassEqualityToTextConverter:IValueConverter2{3publicobjectConvert(o......