Spring IoC&DI

Spring IoC&DI

1.IoC & DI介绍

Spring是什么?

前面将的Spring 是一个开源框架,让开发更加简单,支持很多应用场景.........

简单来说,Spring是包含众多工具方法的IoC容器

IoC容器是什么?

容器:就是可以存放东西,生活中容器用来存放东西,Tomcat就是Web容器

IOC(Inversion of Control,控制反转),也就是Spring中"控制反转"的容器

控制反转 :就是控制权发生了反转,以前是谁使用(new 新对象)谁来管理,这里是同一让Spring管理对象,可以解决对象依赖混乱、耦合度高的问题

https://juejin.cn/post/7541032822355083315

案例:制造一辆汽车

原始思路:先设计轮子( Tire),根据轮子设计地盘(Bottom ),根据地盘设计车身(Framework ),最后根据车身设计整个车(Car )

java 复制代码
public class Tire {
    private int size = 17;

    public Tire(){
        System.out.println("Tire init" + "size:" + size);
    }
}
java 复制代码
public class Bottom {
    private Tire tire;

    public Bottom(){
        this.tire = new Tire();

        System.out.println("Bottom init");
    }
}
java 复制代码
public class Framework {
    private Bottom bottom;

    public Framework(){
        this.bottom = new Bottom();
        System.out.println("Framework init");
    }
}
java 复制代码
public class Car {
    private Framework framework;

    public Car(){
        this.framework = new Framework();
        System.out.println("Car init");
    }
    public void run(){
        System.out.println("car run");
    }
}
java 复制代码
public class Main {

    public static void main(String[] args) {
        Car car = new Car();
        car.run();
    }

}

此时汽车依赖于车身设计,车身设计依赖地盘,地盘依赖于轮胎,这样耦合度太高,导致维护性较低,如果这里对其轮胎设计可以传入对应尺寸,此时四个类都需要修改

此时这里只是给轮胎的构造方法添加了一个size参数,导致这里其关联的所有类的构造方法都进行了修改,因为需要这个size参数,这样耦合度非常高 不利于后期维护

这里可以换一种思路,先设计汽车的大概样子,根据汽车样子设计车身,根据车身设计地盘,根据地盘设计轮子,这里将依赖关系进行了反转

上面相当于一个汽车的所有配件都由自己来构造,这里相当于使用代理工厂来解决,告诉其轮胎尺寸,工厂会做好,我们无需关心

java 复制代码
public class Tire {
    private int size;
    public Tire(int size){
        this.size = size;
        System.out.println("Tire init......" + "size:" + size);
    }
}
java 复制代码
public class Bottom {
    private Tire tire;

    public Bottom(Tire tire){
       this.tire = tire;

        System.out.println("Bottom init");
    }
}
java 复制代码
public class Framework {
    private Bottom bottom;
    public Framework(Bottom bottom){
        this.bottom = bottom;
        System.out.println("Frameword inint......");
    }
}
java 复制代码
public 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");
    }
}
java 复制代码
public class Main {
    public static void main(String[] args) {
        Tire tire = new Tire(17);
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}

这里不关心如何构造,只需要给他成品就行(对象),此时如果Tire类添加一些参数只需要修改这个类,及其使用即可,这样就起到了解耦的作用

IoC容器就是做上面这些工作

IoC容器优点

资源不是由使用资源的双方进行管理,而是第三方进行同一管理,这样资源就容易管理,降低了使用资源双方依赖程度(耦合度)

1.资源集中管理:IoC容器帮助管理一些资源(对象),需要的时直接从IoC容器中直接取即可

2.创建实例不需要要了解细节,降低资源双方依赖关系,降低耦合度
DI介绍

DI:Dependency Injection(依赖注⼊)

依赖注入 :程序运行过程期间,动态的为应用程序提供运行时候所依赖的资源

程序需要什么资源,容器为其提供这个资源

可以将依赖注入DI当作为IoC控制反转的一种实现,通过依赖注入的方式实现对象之间的解耦

使用之前已将将对象创建(注入)了,直接使用即可

2.IoC详解

IoC控制反转,将对象的控制权交给Spring的IoC容器,由IoC容器创建及管理对象

也叫Bean存储

Bean存储

将对象交给Spring中 IoC容器管理,需要使用@Component 注解

当然也有

类注解 :@Controller、@Service、@Repository、@Component@Configuration

⽅法注解:@Bean

java 复制代码
@RestController
public class UserController {
    public void hello(){
        System.out.println("UserController");
    }
}
java 复制代码
//启动类
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);

		UserController bean = context.getBean(UserController.class);
		System.out.println(bean);
	}

}

这里启动类是有返回值的

ApplicationContext就是Spring上下文,对象都是由Spring管理,先获取到Spring上下文

如果把这里

@RestController注解移除,再进行获取对象,会出现在Bean中找不到的错误

java 复制代码
Object getBean(String name) throws BeansException;

<T> T getBean(String name, Class<T> requiredType) throws BeansException;

Object getBean(String name, Object... args) throws BeansException;

<T> T getBean(Class<T> requiredType) throws BeansException;

<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

这里可以根据名称获取对象,bean的名称,Spring管理对象也会给管理的对象起一个名字,可以使用对应名字进行查找

Bean命名采用了Java命名的标准,使用小写字母开头,驼峰命名

例如:类名: UserController, Bean的名称为: userController

但是如果多个字符中第一个和第二个字符都是大写,则保持原本的大小写

例如:类名: USerController, Bean的名称为:USerController

这个方法可以找出知道其类名对应的Bean名

java 复制代码
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);

		System.out.println(Introspector.decapitalize("USerController"));
		System.out.println(Introspector.decapitalize("UserController"));
	}

}

结果如下

java 复制代码
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
		//使用类名称
		UserController bean = context.getBean(UserController.class);
		System.out.println(bean);
		//使用bean名称
		UserController userController = (UserController) context.getBean("userController");
		System.out.println(userController);
		//使用bean名称 + 类名
		UserController userController1 = context.getBean("userController",UserController.class);
		System.out.println(userController1);
	}

}

这里获取的都是同一个对象

上面使用ApplicationContext获取对应对象,但是使用BeanFactory也可以

ApplicationContext 和 BeanFactory的区别

1.关系:ApplicationContext 其实属于BeanFactory的子类,继承了其所有功能但是又新增类一些功能,支持国际化、资源访问等等

2.性能方便:ApplicationContext 是一次性将所有类都加载出来并初始化所有Bean对象,而BeanFactory是需要那个才去加载

java 复制代码
@Controller
public class UserController {
    public void controller(){
        System.out.println("Controller......");
    }
}
java 复制代码
@Service
public class UserService {

    public void service(){
        System.out.println("service......");
    }
}
java 复制代码
@Component
public class UserComponent {
    public void userComponnet(){
        System.out.println("Componnet......");
    }
}
java 复制代码
@Configuration
public class UserConfig {
    public void userConfig(){
        System.out.println("Configuration......");
    }
}
java 复制代码
@Repository
public class UserRepository {
    public void repository(){
        System.out.println("Repository......");
    }
}
java 复制代码
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);

		UserController userController = context.getBean(UserController.class);
		userController.controller();


		UserService userService = context.getBean(UserService.class);
		userService.service();

		UserComponent userComponent = context.getBean(UserComponent.class);
		userComponent.userComponnet();

		UserConfig userConfig = context.getBean(UserConfig.class);
		userConfig.userConfig();

		UserRepository userRepository = context.getBean(UserRepository.class);
		userRepository.repository();

	}
}

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

@Controller:控制层,接收数据,对请求进行处理,并进行响应

@Servie:业务逻辑层,处理具体业务逻辑

@Repository:数据访问层(持久层),负责数据访问操作

@Configuration:配置层,处理项目中的一些配置信息

@Component:一些组件信息

每个类对应主要功能不一样,这样根据注解不同,看到对应注解就知道这个类是干嘛的

这几个注解其实都是@Component的"子类",其作为元注解,上面这四个是其衍生注解

方法注解Bean

类注解是添加到某个类上,但是无法解决

1.使用外部包中的类,没办法添加类注解

2.一个类中有多个对象

这样就需要使用@Bean注解

java 复制代码
@NoArgsConstructor//无参构造
@AllArgsConstructor//全部参数构造
@Data
public class UserInfo {
    private String name;
    private int age;
}
java 复制代码
@Configuration
public class BeanConfig {
    //创建一个UserInfo对象
    public UserInfo userInfo(){
        return new UserInfo("zhangsan",18);
    }
}
java 复制代码
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
     //获取UserInfo对象
		UserInfo bean = context.getBean(UserInfo.class);
		System.out.println(bean);
		
	}
}

理想情况下,会加载BeanConfig 类,并创建一个UserInfo对象

但是这里出现了未定义UserInfo对象的异常

是因为其@Configuration创建的UserInfo并没有放到Spring容器中,这里在其创建的方法上加上@Bean注解

java 复制代码
@Configuration
public class BeanConfig {
    
    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan",18);
    }
}

这样就可以获取到了

如果这里有多个呢?

java 复制代码
@Configuration
public class BeanConfig {

    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan",18);
    }
    @Bean
    public UserInfo userInfo2(){
        return new UserInfo("lisi",17);
    }
}

这里依旧使用上面进行获取UserInfo对象

java 复制代码
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
     //获取UserInfo对象
		UserInfo bean = context.getBean(UserInfo.class);
		System.out.println(bean);
		
	}
}

这里出现了异常,因为这里UserInfo匹配到了两个,此时这里通过类型名称进行获取多个对象会出现问题,此时这里有userInfo,userInfo2两个对象,其bean名称就是方法名,这里通过类型就不行了,此时需要使用bean名称进行获取

NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.UserInfo' available: expected single matching bean but found 2: userInfo,userInfo2

通过bean名进行获取

java 复制代码
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);

		UserInfo bean = (UserInfo) context.getBean("userInfo");
		System.out.println(bean);

		UserInfo bean2 = (UserInfo) context.getBean("userInfo2");
		System.out.println(bean2);

	}
}

这样可以正常获取

这里也可以对其bean进行重命名

java 复制代码
一个名字
@Bean("重命名")
@Bean(name = "重命名")
//多个名字
@Bean({"重命名","重命名2"})
@Bean(name = {"重命名","重命名2"})
java 复制代码
@Configuration
public class BeanConfig {

    @Bean(name = {"u1","user1"})
    public UserInfo userInfo(){
        return new UserInfo("zhangsan",18);
    }
    @Bean("u2")
    public UserInfo userInfo2(){
        return new UserInfo("lisi",17);
    }
}
java 复制代码
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);

		UserInfo bean = context.getBean("u1", UserInfo.class);
		UserInfo bean2 = context.getBean("user1", UserInfo.class);
		UserInfo bean3 = context.getBean("u2", UserInfo.class);
		System.out.println(bean);
		System.out.println(bean2);
		System.out.println(bean3);
	}
}

扫描路径

此时将其启动类移动位置,这里他们不在同一个包中,此时启动进行扫描的时候就找不到这里的BeanConfig中的内容

这里是通过@ComponentScan进行扫描,因此这里可以通过这个注解进行配置

java 复制代码
//配置扫描路径
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);

		UserInfo bean = context.getBean("u1", UserInfo.class);
		UserInfo bean2 = context.getBean("user1", UserInfo.class);
		UserInfo bean3 = context.getBean("u2", UserInfo.class);
		System.out.println(bean);
		System.out.println(bean2);
		System.out.println(bean3);
	}
}

这里就可以扫描到了

通常不使用这个配置扫描路径,通常是将其放到同一个包中 ,这样所有的bean都可以扫描到

3. DI详解

依赖注入是一个过程,IoC容器创建Bean时,会提供所有的依赖的资源(对象),这里通过使用这个@Autowired注解完成相对应操作

java 复制代码
1. 属性注入(Field Injection)  
2. 构造方法注⼊(Constructor Injection) 
3. Setter 注⼊(Setter Injection) 

此时这里没有从Bean中获取对象,此时可以使用@Autowired属性注入

属性注入

java 复制代码
@Configuration
public class BeanConfig {

    @Bean(name = {"u1","user1"})
    public UserInfo userInfo(){
        return new UserInfo("zhangsan",18);
    }

}
java 复制代码
@Service
public class UserService {
//获取到Bean中对象
    @Autowired
    private UserInfo userInfo;

    public void service(){
        System.out.println(userInfo);
        System.out.println("service......");
    }
}
java 复制代码
@Controller
public class UserController {
// 获取到Bean中对象
    @Autowired
    private UserService userService;
    public void controller(){
        userService.service();
        System.out.println("Controller......");
    }
}
java 复制代码
@SpringBootApplication
public class SpringIocApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
		UserController bean = context.getBean(UserController.class);
		bean.controller();

	}
}

构造方法注入

有对应参数的构造方法加上@Autowired,其会自动调用

java 复制代码
@Controller
public class UserController {
//    @Autowired
//    private UserService userService;

    private UserService userService;

    public UserController(){
        System.out.println("执行UserController 无参构造");
    }
    @Autowired
    public UserController(UserService userService){
        System.out.println("执行UserController 有参构造");
        this.userService = userService;
    }
    public void controller(){
        userService.service();
        System.out.println("Controller......");
    }
}

多个方法需要在使用的方法上加上 @Autowired

如果只有一个构造方法可以不加上这个注解

如果有多个,并且都没有加@Autowired注解,其会默认执行无参构造

因此这里userService没有正确初始化,执行了无参构造没有初始化

如果都加上@Autowired注解,会直接编译出错

Setter注入

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

//    private UserService userService;
//
//
      //类注入
//    public UserController(){
//        System.out.println("执行UserController 无参构造");
//    }
//    @Autowired
//    public UserController(UserService userService){
//        System.out.println("执行UserController 有参构造");
//        this.userService = userService;
//    }
    //setter注入
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void controller(){
        userService.service();
        System.out.println("Controller......");
    }
}

这里不加 @Autowired会出错,没有初始化

三种方式对比

1.属性注入

优点 :简洁,使用方便

缺点 :只适用于IoC容器,非IoC容器不可用,并且只有当使用这个对象才会出现空指针错误

不能注入一个final修饰的变量

2.构造方法注入

优点 :可以注入final修改的、注入对象不会被修改

依赖对象使用前一定会被完全初始化,因为依赖是构造方法中执行,构造方法在类加载阶段执行

通用性好,各种框架都适用,构造方法是JDK提供的

缺点 :可能参数过多,构造方法代码较繁琐

3.Setter方法注入

优点:类实例过之后,可以重新进行对象配置/注入

缺点:不能注入一个Final修改的属性

注入对象可以使用其改变,因此修改就会经常调用可能会出现风险

这里如果添加构造方法,还不如使用构造方法进行初始化

如果直接初始化那@Autowired注解没啥意思了

存在问题

这里如果有多个UserInfo对象呢

java 复制代码
@Configuration
public class BeanConfig {

    @Bean(name = {"u1","user1"})
    public UserInfo userInfo(){
        return new UserInfo("zhangsan",18);
    }

    // @Bean("u2")
    public UserInfo userInfo2(){
        return new UserInfo("lisi",17);
    }
}

非唯一的Bean对象 ,这里直接编译出错

可以使用下面三个注解进行解决

java 复制代码
@Primary
@Qualifier
@Resource

这里建议使用@Primary注解 / @Qualifier进行解决

@Primary注解:当有相同类型的Bean注入时候,加上这个注解,是默认的

java 复制代码
@Configuration
public class BeanConfig {

    @Primary//默认这个Bean
    @Bean(name = {"u1","user1"})
    public UserInfo userInfo(){
        return new UserInfo("zhangsan",18);
    }

    @Bean
    public UserInfo userInfo2(){
        return new UserInfo("lisi",17);
    }
}

@Qualifier注解:注入之前,@Qualifier的value属性中,指定注⼊的bean

的名称

注入还是需要@Autowired注解属性注入

java 复制代码
@Configuration
public class BeanConfig {

    @Bean(name = {"u1","user1"})
    public UserInfo userInfo(){
        return new UserInfo("zhangsan",18);
    }

    @Bean
    public UserInfo userInfo2(){
        return new UserInfo("lisi",17);
    }
}
java 复制代码
@Service
public class UserService {
    @Qualifier("u1")
    @Autowired
    private UserInfo userInfo;

    public void service(){
        System.out.println(userInfo);
        System.out.println("service......");
    }
}
java 复制代码
@Controller
public class UserController {
    //属性注入
    @Autowired
    private UserService userService;

    public void controller(){
        userService.service();
        System.out.println("Controller......");
    }
}

@Resource注解:通过其bean名注入,其name属性指定注入名称

java 复制代码
@Service
public class UserService {
    //@Qualifier("u1")
    @Resource(name = "userInfo2")
    @Autowired
    private UserInfo userInfo;

    public void service(){
        System.out.println(userInfo);
        System.out.println("service......");
    }
}

@Autowird 与 @Resource的区别

1.来源不同: @Autowird是Spring框架提供的注解,而@Resource是JDK提供的注解

2.查找方式不同 @Autowird先通过类型进行注入再根据名称,@Resource先通过名称再通过类型进行注入,有name属性获取到对应的Bean

3.支持参数不同:@Autowird只支持一个而@Resource支持7个

4.支持的用法不同:@Autowird迟迟属性注入、构造方法注入和Setter注入而@Resource不支持使用构造方法

5.编译器IDEA提示不同:当注入 Mapper 对象时,使用 @Autowired 注解编译器会提示错误,而使用 @Resource 注解则不会提示错误

参考:https://developer.aliyun.com/article/1003903#comment

@Autowired 查找顺序

先查找是否有这个类型,看是否存在,存在的看是否有Qualifile有的化,根据参数进行查找bean,没找到 / 有两个但是没有使用Qualifile指定名称 / 其参数对应的bean名找不到会抛异常

4. Spring, Spring Boot 和Spring MVC的关系以及区别

Spring:Spring是一个应用开发框架,轻量级、一站式、模块化目的为了简化企业级应用开发

主要功能:管理对象,以及对象之间的依赖关系, ⾯向切⾯编程, 数据库事务管理, 数据访问, web框架⽀持等

并且Spring具有高度开放性,开发者可以使用其Spring部分东西,并且可以衔接/继承第三方框架

Spring MVC:是Spring中的一个子框架,按照MVC模式设计了一个MVC框架,是一个Web框架,主要用于Web开发和网络接口

Spring MVC是基于Spring开发,所以Spring框架集成,可以更简单、简洁的进行Web开发

Spring Boot:是对Spring封装,简化Spring应用开发诞生的,可以使用其快速搭建框架,降低开放成本,减少对底层的XML配置和底层实现,更加注重开发

Spring Boot是一个脚手架,快速开发

只是辅助简化开发,让开发更简单

例如:使用Spring Boot 开放Web项目,只需要引入Spring MVC框架即可,Web工作由Spring MVC来完成,想要数据访问,只需要引入Mybatis框架等等,需要什么可以进行引入

总结:

SpringBoot和SpringMVC都属于Spring,Spring MVC是一个MVC框架,而Spring Boot更像是一个基于Spring快速开放整合

web url映射: @RequestMapping

参数接收和接 应: @RequestParam, @RequestBody, @ResponseBody

bean的存储: @Controller, @Service,@Repository, @Component, @Configuration,

@Bean

bean的获取: @Autowired, @Qualifier, @Resource

相关推荐
我是一颗柠檬1 小时前
【Redis】数据类型详解Day2(2026年)
数据库·redis·后端·缓存
KANGBboy1 小时前
java知识二(数组)
java·开发语言·python
爱笑的源码基地1 小时前
智慧班牌源码:从后端SpringBoot到前端Vue2的全栈实现
java·大数据·云计算·源码·程序代码·智慧校园源码·智慧班牌源码
土狗TuGou1 小时前
SQL内功笔记 · 第7篇:CTE&临时表&递归
数据库·笔记·后端·sql·mysql
XiYang-DING1 小时前
【Spring】日志
java·数据库·spring
Reisentyan2 小时前
[Pro]GoLang Learn Data Day 5
开发语言·后端·golang
雪度娃娃2 小时前
转向现代C++——优先选用删除函数而非private未定义函数
java·jvm·c++
Kurisu5752 小时前
深度拆解:从 Linux 内核 Namespace 与 Cgroups 洞察容器技术的底层本质
java·linux·运维