【Spring】详解Spring IOC&DI

目录

前言

IoC和DI的使用

IoC详解

Bean存储

@Controller(控制器存储)

获取Bean

根据Bean类型获取Bean

根据Bean名称获取Bean

根据Bean的名称和Bean类型获取Bean

@Service(业务逻辑)

@Repository(仓库存储)

@Component(组件存储)

@Configuration(配置存储)

额外小话题

为什么要有这么多类注解?

方法注解

同一个类创建多个对象

扫描路径

DI详解

属性注入

构造方法注入

Setter方法注入

三种注入方式的优缺点

Autowired存在的问题

@Primary

@Qualifier

@Resource

@Autowired自动装配流程图

@Autowired和@Resouce的区别

总结

[Spring、Spring Boot和Spring MVC的关系及差别](#Spring、Spring Boot和Spring MVC的关系及差别)

区别

关系

Bean的命名

五大注解存储Bean

@Bean注解存储Bean

Spring中常见注解


前言

在前面中,我们讲解了Spring MVC是什么以及如何使用。那么本篇我们就来讲Spirng Framework,我们一般讲的Spring就指的是Spring Framework。

在前面中,我们已经讲解了什么是Spring,IOC和AOP是Spring框架的核心思想

本篇我们主要讲IOC容器的使用。

想了解什么是Spirng可以看这篇【Spring】什么是Spring?-CSDN博客

回顾一下IOC和DI。

IOC(控制反转)是Spring框架中的核心思想

控制反转(Inversion of Control,IoC)是一种设计思想 ,其核心是将对象的创建和管理的控制权从使用方(客户端)转移到一个专门的容器(如Spring容器)

在软件开发中,传统设计都是将对象的创建和管理交给使用方来进行创建;而Ioc则是将对象的创建和管理交给IOC,在需要使用的时候,我们只需要告诉Ioc容器所需的对象,那么它就可以为我们创建好。

Ioc很好的让每个模块进行了解耦,实现了高内聚低耦合。
DI(Dependency Injection)依赖注入,是Ioc的一种具体方式,将对象的创建和管理权交给外部容器。

依赖注入将对象的依赖关系通过外部容器注入的方式来传递给对象, 而不是让对象自己去创建或查找依赖。

IoC和DI的使用

通过前面的学习,相信对IoC和DI有初步的认识,那么接下来我们就来具体学习IoC和DI的代码实现。

既然Spring是一个IoC(控制反转)容器,作为容器,那么就具备两个功能:存和取

Sring容器管理的主要是对象,这些对象,我们称为"Bean",把这些对象交给Spring管理,由Spring来负责对象的创建和销毁,我们只需要告诉Spring,哪些要存,以及如何从Spring中取出对象。

那么在Spring中如何存取Bean?

  1. 将类存储进Spring Ioc容器我们需要通过 @Component注解(不止这个)。
  2. 取出对象需要使用注解 @Autowired

示例:

java 复制代码
package com.example.demo.model;

import org.springframework.stereotype.Component;

@Component
public class UserInfo {
    private String userName;
    public void hi(){
        System.out.println("hi");
    }
}
java 复制代码
package com.example.demo.component;

import com.example.demo.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserComponent {

    @Autowired
    private UserInfo userInfo;

    @RequestMapping("/hi")
    public String hi(){
        userInfo.hi();
        return "调用成功";
    }
}

可以看到,确实能显现出我们想要的结果,说明这两个注解能够实现堆Bean的存取。

注意:我们在使用 @Autowired 注解时,需要使用该注解的类有 @Controller 注解修饰 ,要使用Spring的IoC容器,就要保证这个类是由Spring管理的

以上展示的就是Ioc和DI的基本使用,接下来,我们来深入学习IoC和DI。

IoC详解

Bean存储

在前面的案例中,我们要把某个对象交给 IoC 容器管理,那么就需要在类上添加一个注解:@Component。而Spring框架为了更好的服务Wen应用程序,提供了丰富的注解。

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

  • 类注解@Controller、@Service、@Repository、@Component、@Configuration
  • 方法注解@Bean

我们先来讲下类注解。

@Controller(控制器存储)

使用 @Controller 存储Bean:

java 复制代码
package com.example.demo.controller;

import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    
    public void say(){
        System.out.println("UserController ...");
    }
}

既然我们已经将Bean存储到IoC容器中了,那么如何获取Bean?

获取Bean

先上代码:

java 复制代码
@SpringBootApplication
public class BeantextApplication {
	
	public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);

	    // 从应用上下文中获取UserController bean的实例
	    UserController bean = context.getBean(UserController.class);

	    // 调用UserController实例的say方法,执行相关逻辑
	    bean.say();
	}

}

代码是由 @SpringBootApplication 注解修饰的类中写的,并且我们用 ApplicationContext接口来接收run()方法的返回值。

当我们运行一个带有 @SpringBootApplication 注解修饰的类,会启动Spring项目,并且SpringApplication.run() 是有返回值的,这个返回值可以接收,也可以不接收。

那为什么能用 ApplicationContext 类来接收?为什么要用这个类?

我们可以先查看一下run()方法的返回值是什么:

我们可以看到,run()的返回值类型是一个 ConfigurableApplicationContext接口,那这个接口内部是什么样?

可以看到, ConfigurableApplicationContext 接口ApplicationContext 接口 的一个子接口,根据java的多态性,子接口的实例可以被父接口类型接收,因此这里可以用 ApplicationContext来接收。

至于为什么要使用该接口?

ApplicationContext 是Spring框架中管理Bean的核心接口,提供了访问Spring容器中所有Bean的基本功能,几乎所有与Spring 容器的交互都可以通过 ApplicationContext 完成。

而在此处,我们是要获取Bean,那就需要使用 ApplicationContext 。

在BeanFactory在,我们可以看到getBean() 方法,顾名思义这个方法就是用来获取Bean的。

在这里,我们只介绍1、2、4种。

根据Bean类型获取Bean

在上面中,我们就是根据Bean的类型来获取到Bean。

这种方式是Sprinig利用反射机制实现的。Spring能通过类型找到对应的Bean,是因为在容器初始化的时候,Spring会通过反射机制扫描并注册所有Bean的定义信息

反射是Spring实现依赖注入和Bean管理的核心技术,允许Spring在运行时动态地创建对象、访问和修改对象的字段、调用对象的方法。

java 复制代码
@SpringBootApplication
public class BeantextApplication {
	
	public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);

	    // 从应用上下文中获取UserController bean的实例
	    UserController bean = context.getBean(UserController.class);

	    // 调用UserController实例的say方法,执行相关逻辑
	    bean.say();
	}

}

根据Bean名称获取Bean

Bean名称应该如何写?

我们可以看下官方回答:

简单来说,就是如果类名前两个字母中大写字母个数小于2(也就是只有首字母大写),那么该类在交给Ioc后,就会以第一个字母小写的小驼峰形式命名,当类名前两个字母都大写,那么该类型就作Bean名称

java 复制代码
public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);

	    // 从应用上下文中获取UserController bean的实例
		UserController bean = (UserController) context.getBean("userController");

	    // 调用UserController实例的say方法,执行相关逻辑
	    bean.say();
	}

根据Bean的名称和Bean类型获取Bean

java 复制代码
public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);

	    // 从应用上下文中获取UserController bean的实例
		UserController bean = (UserController) context.getBean("userController", UserController.class);

	    // 调用UserController实例的say方法,执行相关逻辑
	    bean.say();
	}

@Service(业务逻辑)

这个注解也可以存储Bean。

java 复制代码
package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void say(){
        System.out.println("UserService say");
    }
}

读取:

java 复制代码
public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);

		UserService userService = (UserService) context.getBean("userService");
		userService.say();

	}

@Repository(仓库存储)

java 复制代码
package com.example.demo.controller;

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public void say() {
        System.out.println("UserRepository say");
    }
}
java 复制代码
	public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);

		UserRepository userRepository = (UserRepository)context.getBean("userRepository");
		userRepository.say();
    }

@Component(组件存储)

java 复制代码
package com.example.demo.component;

import org.springframework.stereotype.Component;

@Component
public class UserComponents {
    public void say() {
        System.out.println("UserComponents say");
    }
}
java 复制代码
public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);
		UserComponents bean = context.getBean(UserComponents.class);
		bean.say();
    }

@Configuration(配置存储)

java 复制代码
package com.example.demo.configuration;

import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfiguration {
    public void say() {
        System.out.println("UserConfiguration");
    }
}
java 复制代码
	public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);
		UserConfiguration bean = context.getBean(UserConfiguration.class);
		bean.say();
    }

额外小话题

下面的代码在最后判断对象是否相等?那会不会相等呢?

java 复制代码
	public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);
		UserConfiguration bean = context.getBean(UserConfiguration.class);
		bean.say();

		UserConfiguration bean1 = (UserConfiguration) context.getBean("userConfiguration");
		bean1.say();

		UserConfiguration bean2 = context.getBean("userConfiguration", UserConfiguration.class);
		bean2.say();

		System.out.println(bean);
		System.out.println(bean1);
		System.out.println(bean2);
		System.out.println(bean == bean1);
		System.out.println(bean == bean2);
}

我们来看下结果:

我们可以看到,利用三种方法获取Bean,但最后返回的都是同一个实例,这是因为Spring中,默认情况下,Bean的作用域是单例

为什么要有这么多类注解?

这个和前面讲的应用分层相呼应的,当开发者看到类注解的时候,就能直接了解当前类的用途。

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

我们可以看到,@Component都有在其他四个注解中,说明@Component 是其他注解的"父类",即其他注解是@Component注解的衍生注解。

不过其他四个注解用于更具体的用例,如三层结构,@Controller对应控制层、@Service对于业务逻辑层、@Repository对应持久层。

方法注解

类注解是加上类上的,但是存在两个问题:

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

对于这种场景,我们就需要用方法注解@Bean。

我们假设User类是一个外包中的类,不能加类注解,所以这就需要使用方法注解。

java 复制代码
package com.example.demo.model;

import lombok.Data;

@Data
public class User {
    private String name;
    private int age;
}
java 复制代码
package com.example.demo.controller;

import com.example.demo.model.User;
import org.springframework.context.annotation.Bean;


public class UserController {

    @Bean
    public User user(){
        User user = new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }
}

测试一下代码:

可以看到报了异常说没有找到Bean,这是因为UserController没有加类注解,Spring没有扫描到。所以要加上类注解:

java 复制代码
package com.example.demo.controller;

import com.example.demo.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    @Bean
    public User user(){
        User user = new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }
}

同一个类创建多个对象

在前面中,我们已经知道了在Spring容器中,Bean默认是单例,那么如果想要获取多个Bean,应该如何做?

使用@Bean时指定Bean名称,这里的名称是方法名称

java 复制代码
package com.example.demo.controller;

import com.example.demo.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    @Bean
    public User user(){
        User user = new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @Bean("user1")
    public User user1(){
        User user = new User();
        user.setName("李四");
        user.setAge(20);
        return user;
    }
}

此外,我们观察@Bean注解的属性,可以看到:

name字段是一个字符串数组,说明一个Bean可以有多个名称

例如:

java 复制代码
    @Bean(name={"user1","lisi"})

需要注意:有多个名称的情况下,需要用大括号括起来,并指定字段

当然,类注解也可以指定名称,但只能指定一个

扫描路径

在项目中,不是所有的文件都会被扫描的,只有在 @SpringBootApplication注解所在的包中才会被扫描。

假如我们把 @SpringBootApplication的类移动一下。

运行一下:

该错误是由于Spring容器无法找到类型为com.example.demo.model.UserInfo的Bean,导致依赖注入失败。具体来说,UserComponent类中的userInfo字段需要一个UserInfo类型的Bean,但Spring上下文中没有定义该Bean。

这是因为UserInfo类没有被扫描的,因为UserInfo类不在SpringBoot启动类所在的包中。所以无法注入。

默认扫描的范围是SpringBoot启动类所在包以及其子包

推荐:把启动类放在我们希望扫描的包的路径下,这样我们定义的Bean就都能被扫描到。

DI详解

依赖注入是一个过程,指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。在前面IoC学习中,我们使用了 @Autowired 注解来完成注入。

对于依赖注入,Spring给我们提供了三种方式:

  • 属性注入(Field Injection)
  • 构造方法注入(Constructor Injection)
  • Setter 注入(Setter Injection)

属性注入

属性注入是用 @Autowired 实现的,直接在属性上加上该注解。

java 复制代码
package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void say(){
        System.out.println("UserService say");
    }
}
java 复制代码
package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class DiController {
    //属性注入
    @Autowired
    private UserService userService;

    public void say(){
        System.out.println("DiController say");
        userService.say();
    }
}

测试一下;

java 复制代码
	public static void main(String[] args) {
	    // 启动Spring Boot应用,并将应用上下文注入到变量context中
	    ApplicationContext context=SpringApplication.run(BeantextApplication.class, args);
		DiController bean = context.getBean(DiController.class);
		bean.say();
    }

构造方法注入

java 复制代码
package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class DiController {
    //属性注入
//    @Autowired
    private UserService userService;
    
    //构造方法注入
    public DiController(UserService userService){
        this.userService = userService;
    } 
    public void say(){
        System.out.println("DiController say");
        userService.say();
    }
}

注意:如果类中只有一个构造方法,那么 @Autowired 注解就可以省略,但如果类中有多个构造方法,那么就需要用 @Autowired 来明确要使用哪个构造方法。

java 复制代码
package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class DiController {
    //属性注入
//    @Autowired
    private UserService userService;

    public DiController(){
        System.out.println("DiController 构造函数");
    }

    @Autowired
    public DiController(UserService userService){
        this.userService = userService;
    }
    public void say(){
        System.out.println("DiController say");
        userService.say();
    }
}

Setter方法注入

java 复制代码
@Controller
public class DiController {
    //属性注入
//    @Autowired
    private UserService userService;

    //set注入
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    public void say(){
        System.out.println("DiController say");
        userService.say();
    }
}

这里报错的原因是由于set方法没有被调用到,所以我们需要用 @Autowired注解修饰。

java 复制代码
package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class DiController {
    //属性注入
//    @Autowired
    private UserService userService;

    //set注入
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    public void say(){
        System.out.println("DiController say");
        userService.say();
    }
}

三种注入方式的优缺点

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

示例:

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

如果一个属性被final修饰,那么这个属性就变成了一个常量,不可被修改,而依赖注入的属性需要动态地修改属性的值,所以不能对final修饰的属性进行依赖注入。

但在构造方法中,final修饰的属性是可以被赋值的,这是因为构造方法是在对象创建时执行,此时final属性还没有被赋值,所以在构造方法中可以对final属性进行赋值。

总的来说**,final修饰的属性不能直接通过依赖注入赋值,是因为其不可变性和依赖注入的动态赋值机制相冲突**;但构造方法注入是一种合法且符合final语义的方案。

Autowired存在的问题

当同一个类类型存在多个Bean,使用 @Autowired 会存在问题。

java 复制代码
package com.example.demo.controller;

import com.example.demo.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    @Bean
    public User user1(){
        User user = new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @Bean("u2")
    public User user2(){
        User user = new User();
        user.setName("李四");
        user.setAge(20);
        return user;
    }
}

那么应该如何解决上面这种情况呢?

在下面有给出了建议:

在Spring中,提供了以下几种方法:

  • @Primary
  • @Qualifier
  • @Resource

@Primary

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

java 复制代码
package com.example.demo.controller;

import com.example.demo.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    @Bean
    public User user1(){
        User user = new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @Bean("u2")
    @Primary
    public User user2(){
        User user = new User();
        user.setName("李四");
        user.setAge(20);
        return user;
    }
}

@Qualifier

@Qualifier注解:指定当前要注入的Bean对象,在@Qualifier的value属性中,指定要注入Bean的名称

注意@Qualifier注解不能单独使用,必须配合@Autowired使用

@Resource

@Resource:按照Bean的名称来进行注入。通过name属性来指定要注入的bean名称

java 复制代码
    @Resource(name = "user1")
    private User user;

@Autowired自动装配流程图

@Autowired和@Resouce的区别

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

总结

Spring、Spring Boot和Spring MVC的关系及差别

区别

SpringSpring是一个轻量级、一站式、模块化的开发应用框架,其目的是用于简化企业应用程序开发。

Spring MVCSpring MVC是Spring的一个子框架,Spring诞生之后,那要如何使用?所以就需要提供接口,于是按照MVC设计模式设计了一个MVC框架(一些用Spring解耦的组件),主要用于Web开发和网络接口,简单来说,Spring MVC是一个Web框架。

Spring BootSpring Boot是对Spring的一个封装,为了简化Spring应用开发而出现的,使用Spring Boot能更加快速的搭建框架,降低成本,让开发者更加专注Spring应用开发,而无需过多关注XML的配置和一些底层实现。

关系

Spring的主要功能是管理对象,以及对象之间的依赖关系,AOP、数据库事务管理、数据访问、Wen框架等。

而Spring MVC是基于Spring开发的,与Spring框架集成,可以让我们更加简洁的进行Wen开发。

而Spring相当于一个脚手架,插拔式搭建项目,可以快速集成其他框架进来。

比如我们想要使用Spring Boot开发Web项目,那么只需要引入 Spring MVC即可,Wen开发的工作是Spring MVC完成的,而不是Spring Boot,想要完成数据访问,就只需要引入MyBatis框架。

用一句话总结:Spring MVC和Spring Boot都属于SprIng,Spring MVC是基于Spring的一个Web MVC框架,而Spring Boot是基于Spring的一套快速开发整合包

Bean的命名

五大注解存储Bean

  • 前两位字母均为大写,则Bean名称为类型

  • 其他的为类名首字母小写

  • 通过value属性设置,如@Controller(value="user")。

@Bean注解存储Bean

  • bean名称为方法名
  • 通过name属性设置

Spring中常见注解

  • web url映射:@RequestMapping
  • 参数接收和接口响应:@RequestParam、@RequestBody、@ResponseBody
  • bean的存储:@Controller、@Service、@Repository、@Component、@Configuration、@Bean
  • bean的获取:@Autowired、@Qualifier、@Resource

以上就是有关IoC和DI的相关知识。

若有不足,欢迎指正~

相关推荐
yngsqq2 小时前
c# —— StringBuilder 类
java·开发语言
Asthenia04122 小时前
浏览器缓存机制深度解析:电商场景下的性能优化实践
后端
星星点点洲2 小时前
【操作幂等和数据一致性】保障业务在MySQL和COS对象存储的一致
java·mysql
xiaolingting3 小时前
JVM层面的JAVA类和实例(Klass-OOP)
java·jvm·oop·klass·instanceklass·class对象
风口上的猪20153 小时前
thingboard告警信息格式美化
java·服务器·前端
databook4 小时前
『Python底层原理』--Python对象系统探秘
后端·python
追光少年33224 小时前
迭代器模式
java·迭代器模式
超爱吃士力架5 小时前
MySQL 中的回表是什么?
java·后端·面试
扣丁梦想家5 小时前
设计模式教程:装饰器模式(Decorator Pattern)
java·前端·装饰器模式
drebander5 小时前
Maven 构建中的安全性与合规性检查
java·maven